[
  {
    "path": ".gitignore",
    "content": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\n**/.xcode.env.local\n\n# Android/IntelliJ\n#\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n*.hprof\n.cxx/\n*.keystore\n!debug.keystore\n\n\n# node.js\n#\nnode_modules/\nnpm-debug.log\nyarn-error.log\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/\n\n**/fastlane/report.xml\n**/fastlane/Preview.html\n**/fastlane/screenshots\n**/fastlane/test_output\n\n# Bundle artifact\n*.jsbundle\n\n# Ruby / CocoaPods\n**/Pods/\n/vendor/bundle/\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# testing\n/coverage\n\n# Yarn\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n\n# Ignore android and ios folders in SampleAppExpo\nexamples/SampleAppExpo/android/\nexamples/SampleAppExpo/ios/"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img alt=\"CometChat\" src=\"https://assets.cometchat.io/website/images/logos/banner.png\">\n</p>\n\n# CometChat React Native UI Kit\n\nCometChat React Native UIKit provides a pre-built user interface kit that developers can use to quickly integrate a reliable & fully-featured chat experience into an existing or a new mobile app.<br />\n\n<div style=\"\n    display: flex;\n    align-items: center;\n    justify-content: center;\">\n   <img src=\"./screenshots/overview_cometchat_screens.png\" />\n</div>\n\n## 🚀 Explore the Sample Apps!\n\nDive straight into our Sample Apps to see CometChat UI Kit in action! Whether you're building a messaging app or enhancing your existing project, this sample app showcases the full potential of our React Native UI components.\n- [Sample App ](examples/SampleApp#readme)\n- [Sample App AI](examples/SampleAppAI#readme)\n- [Sample App Expo](examples/SampleAppExpo#readme)\n- [Sample App with Push Notifications](examples/SampleAppWithPushNotifications#readme)\n\n## Prerequisites\n\n- **Node.js** 18 or higher\n- **React Native** Version 0.77 or later (up to the latest version)  \n\n**iOS**\n- XCode\n- Pod (CocoaPods) for iOS\n- An iOS device or emulator with iOS 12.0 or above.\n\n**Android**\n- Android Studio\n- Android device or emulator with Android version 5.0 or above.\n\n## Getting Started\n\nTo set up React Native Chat UIKit and utilize CometChat for your chat functionality, you'll need to follow these steps:\n1. Registration: Go to the [CometChat Dashboard](https://app.cometchat.com/) and sign up for an account.\n2. After registering, log into your CometChat account and create a new app. Once created, CometChat will generate an Auth Key and App ID for you. Keep\n   these credentials secure as you'll need them later.\n3. Check the [Key Concepts](https://www.cometchat.com/docs/fundamentals/key-concepts) to understand the basic components of CometChat.\n4. Refer to the [Integration Steps](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) in our documentation to integrate the UI Kit into your iOS app.\n\n## Help and Support\n\nFor issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview)\nor create a [support ticket](https://help.cometchat.com/hc/en-us) or seek real-time support via the [CometChat Dashboard](https://app.cometchat.com/)."
  },
  {
    "path": "examples/SampleApp/.bundle/config",
    "content": "BUNDLE_PATH: \"vendor/bundle\"\nBUNDLE_FORCE_RUBY_PLATFORM: 1\n"
  },
  {
    "path": "examples/SampleApp/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: '@react-native',\n};\n"
  },
  {
    "path": "examples/SampleApp/.gitignore",
    "content": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\n**/.xcode.env.local\n\n# Android/IntelliJ\n#\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n*.hprof\n.cxx/\n*.keystore\n!debug.keystore\n.kotlin/\n\n# node.js\n#\nnode_modules/\nnpm-debug.log\nyarn-error.log\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/\n\n**/fastlane/report.xml\n**/fastlane/Preview.html\n**/fastlane/screenshots\n**/fastlane/test_output\n\n# Bundle artifact\n*.jsbundle\n\n# Ruby / CocoaPods\n**/Pods/\n/vendor/bundle/\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# testing\n/coverage\n\n# Yarn\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n"
  },
  {
    "path": "examples/SampleApp/.prettierrc.js",
    "content": "module.exports = {\n  arrowParens: 'avoid',\n  singleQuote: true,\n  trailingComma: 'all',\n};\n"
  },
  {
    "path": "examples/SampleApp/.vscode/settings.json",
    "content": "{\n    \"workbench.colorCustomizations\": {\n        \"activityBar.activeBackground\": \"#fa1b49\",\n        \"activityBar.background\": \"#fa1b49\",\n        \"activityBar.foreground\": \"#e7e7e7\",\n        \"activityBar.inactiveForeground\": \"#e7e7e799\",\n        \"activityBarBadge.background\": \"#155e02\",\n        \"activityBarBadge.foreground\": \"#e7e7e7\",\n        \"commandCenter.border\": \"#e7e7e799\",\n        \"sash.hoverBorder\": \"#fa1b49\",\n        \"statusBar.background\": \"#dd0531\",\n        \"statusBar.foreground\": \"#e7e7e7\",\n        \"statusBarItem.hoverBackground\": \"#fa1b49\",\n        \"statusBarItem.remoteBackground\": \"#dd0531\",\n        \"statusBarItem.remoteForeground\": \"#e7e7e7\",\n        \"titleBar.activeBackground\": \"#dd0531\",\n        \"titleBar.activeForeground\": \"#e7e7e7\",\n        \"titleBar.inactiveBackground\": \"#dd053199\",\n        \"titleBar.inactiveForeground\": \"#e7e7e799\"\n    },\n    \"peacock.color\": \"#dd0531\"\n}"
  },
  {
    "path": "examples/SampleApp/.watchmanconfig",
    "content": "{}\n"
  },
  {
    "path": "examples/SampleApp/App.tsx",
    "content": "import './gesture-handler';\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n  Platform,\n  View,\n  PlatformColor,\n  AppState,\n  AppStateStatus,\n} from 'react-native';\nimport { enableScreens } from 'react-native-screens';\nenableScreens();\nimport {\n  CometChatI18nProvider,\n  CometChatIncomingCall,\n  CometChatTheme,\n  CometChatThemeProvider,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  UIKitSettings,\n} from '@cometchat/chat-uikit-react-native';\n\nimport { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';\nimport { GestureHandlerRootView } from 'react-native-gesture-handler';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport RootStackNavigator from './src/navigation/RootStackNavigator';\nimport { AppConstants } from './src/utils/AppConstants';\nimport { requestAndroidPermissions } from './src/utils/helper';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { useConfig } from './src/config/store';\nimport { DeepPartial } from '@cometchat/chat-uikit-react-native/src/shared/helper/types';\nimport { createTypography } from './src/utils/themeTypography';\n\n// Listener ID for registering and removing CometChat listeners.\nconst listenerId = 'app';\n\nconst App = (): React.ReactElement => {\n  const [callReceived, setCallReceived] = useState(false);\n  const incomingCall = useRef<CometChat.Call | CometChat.CustomMessage | null>(\n    null,\n  );\n  const [isInitializing, setIsInitializing] = useState(true);\n  const [isLoggedIn, setIsLoggedIn] = useState(false);\n  const [userLoggedIn, setUserLoggedIn] = useState(false);\n  const [currentToken, setCurrentToken] = useState('');\n  const [isTokenRegistered, setIsTokenRegistered] = useState(false);\n  const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);\n  const styleConfig = useConfig(state => state?.settings?.style);\n\n  const theme : { light:  DeepPartial<CometChatTheme>; dark: DeepPartial<CometChatTheme> } = {\n  light: {\n    color: {\n      primary: styleConfig.color.brandColor,\n      textPrimary: styleConfig.color.primaryTextLight,\n      textSecondary: styleConfig.color.secondaryTextLight,\n    },\n    typography: createTypography(styleConfig.typography.font),\n  },\n  dark: {\n    color: {\n      primary: styleConfig.color.brandColor,\n      textPrimary: styleConfig.color.primaryTextDark,\n      textSecondary: styleConfig.color.secondaryTextDark,\n    },\n    typography: createTypography(styleConfig.typography.font),\n  },\n};\n\n  /**\n   * Initialize CometChat UIKit and configure Google Sign-In.\n   * Retrieves credentials from AsyncStorage and uses fallback constants if needed.\n   */\n  useEffect(() => {\n    async function init() {\n      try {\n        // Retrieve stored app credentials or default to an empty object.\n        const AppData = (await AsyncStorage.getItem('appCredentials')) || '{}';\n        const storedCredentials = JSON.parse(AppData);\n\n        // Determine the final credentials (from AsyncStorage or AppConstants).\n        const finalAppId = storedCredentials.appId || AppConstants.appId;\n        const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;\n        const finalRegion = storedCredentials.region || AppConstants.region;\n\n        // Set hasValidAppCredentials based on whether all values are available.\n        if (finalAppId && finalAuthKey && finalRegion) {\n          setHasValidAppCredentials(true);\n        } else {\n          setHasValidAppCredentials(false);\n        }\n\n        await CometChatUIKit.init({\n          appId: finalAppId,\n          authKey: finalAuthKey,\n          region: finalRegion,\n          subscriptionType: CometChat.AppSettings\n            .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n        });\n\n        // If a user is already logged in, update the state.\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        if (loggedInUser) {\n          setIsLoggedIn(true);\n        }\n      } catch (error) {\n        console.log('Error during initialization', error);\n      } finally {\n        // Mark initialization as complete.\n        setIsInitializing(false);\n      }\n    }\n    init();\n  }, []);\n\n  /**\n   * Monitor app state changes to verify the logged-in status and clear notifications.\n   * When the app becomes active, it cancels Android notifications and checks the login status.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android') {\n      // Request required Android permissions for notifications.\n      requestAndroidPermissions();\n    }\n    const handleAppStateChange = async (nextState: AppStateStatus) => {\n      if (nextState === 'active') {\n        try {\n          // Verify if there is a valid logged-in user.\n          const chatUser = await CometChat.getLoggedinUser();\n          setIsLoggedIn(!!chatUser);\n        } catch (error) {\n          console.log('Error verifying CometChat user on resume:', error);\n        }\n      }\n    };\n    const subscription = AppState.addEventListener(\n      'change',\n      handleAppStateChange,\n    );\n    return () => subscription.remove();\n  }, []);\n\n  /**\n   * Attach CometChat login listener to handle login and logout events.\n   * Updates user login status accordingly.\n   */\n  useEffect(() => {\n    CometChat.addLoginListener(\n      listenerId,\n      new CometChat.LoginListener({\n        loginSuccess: () => {\n          setUserLoggedIn(true);\n        },\n        loginFailure: (e: CometChat.CometChatException) => {\n          console.log('LoginListener :: loginFailure', e.message);\n        },\n        logoutSuccess: () => {\n          setUserLoggedIn(false);\n        },\n        logoutFailure: (e: CometChat.CometChatException) => {\n          console.log('LoginListener :: logoutFailure', e.message);\n        },\n      }),\n    );\n\n    // Clean up the login listener on component unmount.\n    return () => {\n      CometChat.removeLoginListener(listenerId);\n    };\n  }, []);\n\n  /**\n   * Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.\n   * Also handles UI events for call end.\n   */\n  useEffect(() => {\n    // Listener for call events.\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onIncomingCallReceived: (call: CometChat.Call) => {\n          // Check if there's already an active call\n          try {\n            const activeCall = CometChat.getActiveCall();\n            if (activeCall) {\n              // If there's an active call, reject the incoming call with busy status\n              setTimeout(() => {\n                CometChat.rejectCall(\n                  call.getSessionId(),\n                  CometChat.CALL_STATUS.BUSY,\n                )\n                  .then(() => {\n                    console.log('Incoming call rejected due to active call');\n                  })\n                  .catch(error => {\n                    console.error(\n                      'Error rejecting call with busy status:',\n                      error,\n                    );\n                  });\n              }, 2000);\n            } else {\n              // No active call, proceed with normal incoming call handling\n              // Hide any bottom sheet UI before showing the incoming call screen.\n              CometChatUIEventHandler.emitUIEvent(\n                CometChatUIEvents.ccToggleBottomSheet,\n                {\n                  isBottomSheetVisible: false,\n                },\n              );\n              // Store the incoming call and update state.\n              incomingCall.current = call;\n              setCallReceived(true);\n            }\n          } catch (error) {\n            console.error('Error getting active call:', error);\n            // If error getting active call, proceed with normal handling\n            CometChatUIEventHandler.emitUIEvent(\n              CometChatUIEvents.ccToggleBottomSheet,\n              {\n                isBottomSheetVisible: false,\n              },\n            );\n            incomingCall.current = call;\n            setCallReceived(true);\n          }\n        },\n        onOutgoingCallRejected: () => {\n          // Clear the call state if outgoing call is rejected.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n        onIncomingCallCancelled: () => {\n          // Clear the call state if the incoming call is cancelled.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n      }),\n    );\n\n    // Additional listener to handle call end events.\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallEnded: () => {\n        incomingCall.current = null;\n        setCallReceived(false);\n      },\n    });\n\n    // Remove call listeners on cleanup.\n    return () => {\n      CometChatUIEventHandler.removeCallListener(listenerId);\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [userLoggedIn]);\n\n  // Show a blank/splash screen while the app is initializing.\n  if (isInitializing) {\n    return (\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: Platform.select({\n            ios: PlatformColor('systemBackgroundColor'),\n            android: PlatformColor('?android:attr/colorBackground'),\n          }),\n        }}\n      />\n    );\n  }\n\n  // Once initialization is complete, render the main app UI.\n  return (\n    <GestureHandlerRootView style={{ flex: 1 }}>\n    <SafeAreaProvider>\n      {/* Render the incoming call UI if the user is logged in and a call is received */}\n      <CometChatThemeProvider theme={theme}>\n        <CometChatI18nProvider>\n            {isLoggedIn && callReceived && incomingCall.current ? (\n              <CometChatIncomingCall\n                call={incomingCall.current}\n                onDecline={() => {\n                  // Handle call decline by clearing the incoming call state.\n                  incomingCall.current = null;\n                  setCallReceived(false);\n                }}\n              />\n            ) : null}\n          <SafeAreaView edges={['top', 'bottom']} style={{ flex: 1 }}>\n            {/* Render the main navigation stack, passing the login status as a prop */}\n            <RootStackNavigator\n              isLoggedIn={isLoggedIn}\n              hasValidAppCredentials={hasValidAppCredentials}\n            />\n          </SafeAreaView>\n        </CometChatI18nProvider>\n      </CometChatThemeProvider>\n    </SafeAreaProvider>\n    </GestureHandlerRootView>\n  );\n};\n\nexport default App;\n"
  },
  {
    "path": "examples/SampleApp/AppErrorBoundary.tsx",
    "content": "import React from 'react';\nimport { View, Text, StyleSheet, Appearance, Button } from 'react-native';\n\ninterface State {\n  hasError: boolean;\n  error: Error | null;\n  colorScheme: 'light' | 'dark' | null;\n}\n\ninterface Props {\n  children: React.ReactNode;\n}\n\nclass AppErrorBoundary extends React.Component<Props, State> {\n  constructor(props: Props) {\n    super(props);\n    const colorScheme = Appearance.getColorScheme() ?? null;\n    this.state = { hasError: false, error: null, colorScheme };\n  }\n\n  static getDerivedStateFromError(error: Error) {\n    // Update state so the next render shows the fallback UI.\n    return { hasError: true, error };\n  }\n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    // Log error to an error reporting service here if needed.\n    console.log('ErrorBoundary caught an error', error, errorInfo);\n  }\n\n  componentDidMount() {\n    // Listen for changes in color scheme.\n    Appearance.addChangeListener(({ colorScheme }) => {\n      this.setState({ colorScheme: colorScheme ?? null });\n    });\n  }\n\n  // Reset error state to allow retrying\n  handleRetry = () => {\n    this.setState({ hasError: false, error: null });\n  };\n\n  render() {\n    if (this.state.hasError) {\n      const styles = createStyles(this.state.colorScheme);\n      return (\n        <View style={styles.container}>\n          <View style={styles.card}>\n            <Text style={styles.title}>Something went wrong</Text>\n             {/* Uncomment the next line to show the error message */}\n            {/* <Text style={styles.errorText}>\n              {this.state.error ? this.state.error.toString() : 'Unknown error'}\n            </Text> */}\n            <View style={styles.buttonContainer}>\n              <Button title=\"Retry\" onPress={this.handleRetry} color={this.state.colorScheme === 'dark' ? \"#bbbbbb\" : \"#333333\"} />\n            </View>\n          </View>\n        </View>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst createStyles = (colorScheme: 'light' | 'dark' | null) => {\n  const isDark = colorScheme === 'dark';\n  return StyleSheet.create({\n    container: {\n      flex: 1,\n      backgroundColor: isDark ? '#121212' : '#f2f2f2',\n      justifyContent: 'center',\n      alignItems: 'center',\n      padding: 16,\n    },\n    card: {\n      backgroundColor: isDark ? '#1e1e1e' : '#ffffff',\n      padding: 24,\n      borderRadius: 12,\n      alignItems: 'center',\n      shadowColor: isDark ? '#000000' : '#aaa',\n      shadowOffset: { width: 0, height: 2 },\n      shadowOpacity: 0.3,\n      shadowRadius: 4,\n      elevation: 5,\n      width: '100%',\n      maxWidth: 400,\n    },\n    title: {\n      fontSize: 20,\n      fontWeight: '700',\n      marginBottom: 12,\n      color: isDark ? '#ffffff' : '#333333',\n    },\n    errorText: {\n      fontSize: 16,\n      textAlign: 'center',\n      color: isDark ? '#cccccc' : '#666666',\n      marginBottom: 20,\n    },\n    buttonContainer: {\n      width: '100%',\n    },\n  });\n};\n\nexport default AppErrorBoundary;\n"
  },
  {
    "path": "examples/SampleApp/Gemfile",
    "content": "source 'https://rubygems.org'\n\n# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version\nruby \">= 2.6.10\"\n\n# Exclude problematic versions of cocoapods and activesupport that causes build failures.\ngem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'\ngem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'\ngem 'xcodeproj', '< 1.26.0'\ngem 'concurrent-ruby', '< 1.3.4'\n\n# Ruby 3.4.0 has removed some libraries from the standard library.\ngem 'bigdecimal'\ngem 'logger'\ngem 'benchmark'\ngem 'mutex_m'\n"
  },
  {
    "path": "examples/SampleApp/README.md",
    "content": "\n<p align=\"center\">\n  <img alt=\"CometChat\" src=\"https://assets.cometchat.io/website/images/logos/banner.png\">\n</p>\n\n# React Native Sample App by CometChat\n\nThis is a reference application showcasing the integration of [CometChat's React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) in a React Native project. It demonstrates how to implement real-time messaging and voice/video calling features with ease.\n\n<div style=\"display: flex; align-items: center; justify-content: center\">\n   <img src=\"../../screenshots/overview_cometchat_screens.png\" />\n</div>\n\n\n## Prerequisites\n\nSign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_\n\n- **Node.js** 18 or higher\n- **React Native** Version 0.77 or later (up to the latest version) \n\n**iOS**\n- XCode\n- Pod (CocoaPods) for iOS\n- An iOS device or emulator with iOS 12.0 or above.\n- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.\n\n**Android**\n- Android Studio\n- Android device or emulator with Android version 5.0 or above.\n\n\n## Installation\n\n1. Clone the repository:\n   ```sh\n   git clone https://github.com/cometchat/cometchat-uikit-react-native.git\n   ```\n\n1. Change into the specific app's directory (e.g., SampleApp).\n   ```sh\n     cd examples/SampleApp\n   ```\n1. Run `npm install` to install the dependencies.\n\n1. `[Optional]` Configure CometChat credentials:\n    - Open the `AppConstants.tsx` file located at `examples/SampleApp/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:\n      ```ts\n      export const AppConstants = {\n          appId: 'YOUR_APP_ID',\n          authKey: 'YOUR_AUTH_KEY',\n          region: 'REGION',\n          //other properties\n      }\n      ```\n\n1. For iOS, install dependencies after navigating to ios:\n   ```sh\n    cd ios\n    pod install\n   ```\n\n1. Run the app on a device or emulator from the repo root.\n   ```sh\n    npm start\n    npm run android\n    npm run ios\n   ```\n\n\n## Help and Support\n\nFor issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/)."
  },
  {
    "path": "examples/SampleApp/android/app/build.gradle",
    "content": "apply plugin: \"com.android.application\"\napply plugin: \"org.jetbrains.kotlin.android\"\napply plugin: \"com.facebook.react\"\n\n/**\n * This is the configuration block to customize your React Native Android app.\n * By default you don't need to apply any configuration, just uncomment the lines you need.\n */\nreact {\n    /* Folders */\n    //   The root of your project, i.e. where \"package.json\" lives. Default is '../..'\n    // root = file(\"../../\")\n    //   The folder where the react-native NPM package is. Default is ../../node_modules/react-native\n    // reactNativeDir = file(\"../../node_modules/react-native\")\n    //   The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen\n    // codegenDir = file(\"../../node_modules/@react-native/codegen\")\n    //   The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js\n    // cliFile = file(\"../../node_modules/react-native/cli.js\")\n\n    /* Variants */\n    //   The list of variants to that are debuggable. For those we're going to\n    //   skip the bundling of the JS bundle and the assets. By default is just 'debug'.\n    //   If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.\n    // debuggableVariants = [\"liteDebug\", \"prodDebug\"]\n\n    /* Bundling */\n    //   A list containing the node command and its flags. Default is just 'node'.\n    // nodeExecutableAndArgs = [\"node\"]\n    //\n    //   The command to run when bundling. By default is 'bundle'\n    // bundleCommand = \"ram-bundle\"\n    //\n    //   The path to the CLI configuration file. Default is empty.\n    // bundleConfig = file(../rn-cli.config.js)\n    //\n    //   The name of the generated asset file containing your JS bundle\n    // bundleAssetName = \"MyApplication.android.bundle\"\n    //\n    //   The entry file for bundle generation. Default is 'index.android.js' or 'index.js'\n    // entryFile = file(\"../js/MyApplication.android.js\")\n    //\n    //   A list of extra flags to pass to the 'bundle' commands.\n    //   See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle\n    // extraPackagerArgs = []\n\n    /* Hermes Commands */\n    //   The hermes compiler command to run. By default it is 'hermesc'\n    // hermesCommand = \"$rootDir/my-custom-hermesc/bin/hermesc\"\n    //\n    //   The list of flags to pass to the Hermes compiler. By default is \"-O\", \"-output-source-map\"\n    // hermesFlags = [\"-O\", \"-output-source-map\"]\n\n    /* Autolinking */\n    autolinkLibrariesWithApp()\n}\n\n/**\n * Set this to true to Run Proguard on Release builds to minify the Java bytecode.\n */\ndef enableProguardInReleaseBuilds = false\n\n/**\n * The preferred build flavor of JavaScriptCore (JSC)\n *\n * For example, to use the international variant, you can use:\n * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`\n *\n * The international variant includes ICU i18n library and necessary data\n * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that\n * give correct results when using with locales other than en-US. Note that\n * this variant is about 6MiB larger per architecture than default.\n */\ndef jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'\n\nandroid {\n    ndkVersion rootProject.ext.ndkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n    compileSdk rootProject.ext.compileSdkVersion\n\n    namespace \"com.cometchat.sampleapp.reactnative.android\"\n    defaultConfig {\n        applicationId \"com.cometchat.sampleapp.reactnative.android\"\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"5.3.4\"\n    }\n    signingConfigs {\n        debug {\n            storeFile file('debug.keystore')\n            storePassword 'android'\n            keyAlias 'androiddebugkey'\n            keyPassword 'android'\n        }\n    }\n    buildTypes {\n        debug {\n            signingConfig signingConfigs.debug\n        }\n        release {\n            // Caution! In production, you need to generate your own keystore file.\n            // see https://reactnative.dev/docs/signed-apk-android.\n            signingConfig signingConfigs.debug\n            minifyEnabled enableProguardInReleaseBuilds\n            proguardFiles getDefaultProguardFile(\"proguard-android.txt\"), \"proguard-rules.pro\"\n        }\n    }\n}\n\ndependencies {\n    // The version of react-native is set by the React Native Gradle Plugin\n    implementation(\"com.facebook.react:react-android\")\n\n    if (hermesEnabled.toBoolean()) {\n        implementation(\"com.facebook.react:hermes-android\")\n    } else {\n        implementation jscFlavor\n    }\n\n    implementation 'com.facebook.fresco:animated-gif:3.6.0' //gif android\n}\n"
  },
  {
    "path": "examples/SampleApp/android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package='com.cometchat.sampleapp.reactnative.android'> \n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />\n    <uses-permission android:name=\"android.permission.BIND_TELECOM_CONNECTION_SERVICE\"/>\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.CALL_PHONE\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n\n    <application\n      android:name=\".MainApplication\"\n      android:label=\"@string/app_name\"\n      android:icon=\"@mipmap/ic_launcher\"\n      android:roundIcon=\"@mipmap/ic_launcher_round\"\n      android:allowBackup=\"false\"\n      android:theme=\"@style/AppTheme\"\n      android:usesCleartextTraffic=\"${usesCleartextTraffic}\"\n      android:supportsRtl=\"true\">\n      <activity\n        android:name=\".MainActivity\"\n        android:label=\"@string/app_name\"\n        android:configChanges=\"keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|fontScale\"\n        android:launchMode=\"singleTask\"\n        android:windowSoftInputMode=\"adjustResize\"\n        android:exported=\"true\">\n        <intent-filter>\n            <action android:name=\"android.intent.action.MAIN\" />\n            <category android:name=\"android.intent.category.LAUNCHER\" />\n        </intent-filter>\n      </activity>\n    </application>\n</manifest>\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/java/com/sampleapp/MainActivity.kt",
    "content": "package com.cometchat.sampleapp.reactnative.android\nimport android.os.Bundle\n\nimport com.facebook.react.ReactActivity\nimport com.facebook.react.ReactActivityDelegate\nimport com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled\nimport com.facebook.react.defaults.DefaultReactActivityDelegate\n\nclass MainActivity : ReactActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n    // REQUIRED for react-native-screens\n    super.onCreate(null)\n  }\n\n\n  /**\n   * Returns the name of the main component registered from JavaScript. This is used to schedule\n   * rendering of the component.\n   */\n  override fun getMainComponentName(): String = \"sampleapp\"\n\n  /**\n   * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]\n   * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]\n   */\n  override fun createReactActivityDelegate(): ReactActivityDelegate =\n      DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)\n}\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/java/com/sampleapp/MainApplication.kt",
    "content": "package com.cometchat.sampleapp.reactnative.android\n\nimport android.app.Application\nimport com.facebook.react.PackageList\nimport com.facebook.react.ReactApplication\nimport com.facebook.react.ReactHost\nimport com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative\nimport com.facebook.react.ReactNativeHost\nimport com.facebook.react.ReactPackage\nimport com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost\nimport com.facebook.react.defaults.DefaultReactNativeHost\n\nclass MainApplication : Application(), ReactApplication {\n\n  override val reactNativeHost: ReactNativeHost =\n      object : DefaultReactNativeHost(this) {\n        override fun getPackages(): List<ReactPackage> =\n            PackageList(this).packages.apply {\n              // Packages that cannot be autolinked yet can be added manually here, for example:\n              // add(MyReactNativePackage())\n            }\n\n        override fun getJSMainModuleName(): String = \"index\"\n\n        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG\n\n        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED\n        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED\n      }\n\n  override val reactHost: ReactHost\n    get() = getDefaultReactHost(applicationContext, reactNativeHost)\n\n  override fun onCreate() {\n    super.onCreate()\n    loadReactNative(this)\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/res/drawable/rn_edit_text_material.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<inset xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:insetLeft=\"@dimen/abc_edit_text_inset_horizontal_material\"\n       android:insetRight=\"@dimen/abc_edit_text_inset_horizontal_material\"\n       android:insetTop=\"@dimen/abc_edit_text_inset_top_material\"\n       android:insetBottom=\"@dimen/abc_edit_text_inset_bottom_material\"\n       >\n\n    <selector>\n        <!--\n          This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).\n          The item below with state_pressed=\"false\" and state_focused=\"false\" causes a NullPointerException.\n          NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'\n\n          <item android:state_pressed=\"false\" android:state_focused=\"false\" android:drawable=\"@drawable/abc_textfield_default_mtrl_alpha\"/>\n\n          For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.\n        -->\n        <item android:state_enabled=\"false\" android:drawable=\"@drawable/abc_textfield_default_mtrl_alpha\"/>\n        <item android:drawable=\"@drawable/abc_textfield_activated_mtrl_alpha\"/>\n    </selector>\n\n</inset>\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/res/values/colors.xml",
    "content": "<resources>\n    <!-- Other color definitions -->\n    <color name=\"notification_color\">#FFFFFF</color> <!-- Replace with your desired color -->\n</resources>\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">sampleapp</string>\n</resources>\n"
  },
  {
    "path": "examples/SampleApp/android/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"android:editTextBackground\">@drawable/rn_edit_text_material</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "examples/SampleApp/android/build.gradle",
    "content": "buildscript {\n    ext {\n        buildToolsVersion = \"36.0.0\"\n        minSdkVersion = 24\n        compileSdkVersion = 36\n        targetSdkVersion = 36\n        ndkVersion = \"27.1.12297006\"\n        kotlinVersion = \"2.1.20\"\n        version = \"V5.3.4\"\n    }\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath(\"com.android.tools.build:gradle\")\n        classpath(\"com.facebook.react:react-native-gradle-plugin\")\n        classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin\")\n    }\n}\n\napply plugin: \"com.facebook.react.rootproject\"\n"
  },
  {
    "path": "examples/SampleApp/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/SampleApp/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m\norg.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n\n# Use this property to specify which architecture you want to build.\n# You can also override it from the CLI using\n# ./gradlew <task> -PreactNativeArchitectures=x86_64\nreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64\n\n# Use this property to enable support to the new architecture.\n# This will allow you to use TurboModules and the Fabric render in\n# your application. You should enable this flag either if you want\n# to write custom TurboModules/Fabric components OR use libraries that\n# are providing them.\nnewArchEnabled=true\n\n# Use this property to enable or disable the Hermes JS engine.\n# If set to false, you will be using JSC instead.\nhermesEnabled=true\n\n# Use this property to enable edge-to-edge display support.\n# This allows your app to draw behind system bars for an immersive UI.\n# Note: Only works with ReactActivity and should not be used with custom Activity.\nedgeToEdgeEnabled=false\n"
  },
  {
    "path": "examples/SampleApp/android/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/SampleApp/android/gradlew.bat",
    "content": "@REM Copyright (c) Meta Platforms, Inc. and affiliates.\n@REM\n@REM This source code is licensed under the MIT license found in the\n@REM LICENSE file in the root directory of this source tree.\n\n@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "examples/SampleApp/android/settings.gradle",
    "content": "pluginManagement { includeBuild(\"../node_modules/@react-native/gradle-plugin\") }\nplugins { id(\"com.facebook.react.settings\") }\nextensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }\nrootProject.name = 'SampleApp'\ninclude ':app'\nincludeBuild('../node_modules/@react-native/gradle-plugin')\n"
  },
  {
    "path": "examples/SampleApp/app.json",
    "content": "{\n  \"name\": \"sampleapp\",\n  \"displayName\": \"sampleapp\"\n}\n"
  },
  {
    "path": "examples/SampleApp/babel.config.js",
    "content": "module.exports = {\n  presets: ['module:@react-native/babel-preset'],\n};\n"
  },
  {
    "path": "examples/SampleApp/gesture-handler.js",
    "content": ""
  },
  {
    "path": "examples/SampleApp/gesture-handler.native.js",
    "content": "// Only import react-native-gesture-handler on native platforms\nimport 'react-native-gesture-handler';"
  },
  {
    "path": "examples/SampleApp/index.js",
    "content": "import {AppRegistry} from 'react-native';\nimport App from './App';\nimport {name as appName} from './app.json';\nimport AppErrorBoundary from './AppErrorBoundary';\nimport {ActiveChatProvider} from './src/utils/ActiveChatContext';\n\nif (global?.ErrorUtils) {\n  const defaultHandler = global.ErrorUtils.getGlobalHandler();\n\n  function globalErrorHandler(error, isFatal) {\n    console.log(\n      '[GlobalErrorHandler]:',\n      isFatal ? 'Fatal:' : 'Non-Fatal:',\n      error,\n    );\n    defaultHandler?.(error, isFatal);\n  }\n\n  global.ErrorUtils.setGlobalHandler(globalErrorHandler);\n}\n\nif (typeof process === 'object' && process.on) {\n  process.on('unhandledRejection', (reason, promise) => {\n    console.log('[Unhandled Promise Rejection]:', reason);\n  });\n}\n\nconst Root = () => (\n  <AppErrorBoundary>\n    <ActiveChatProvider>\n      <App />\n    </ActiveChatProvider>\n  </AppErrorBoundary>\n);\n\nAppRegistry.registerComponent(appName, () => Root);\n"
  },
  {
    "path": "examples/SampleApp/ios/.xcode.env",
    "content": "# This `.xcode.env` file is versioned and is used to source the environment\n# used when running script phases inside Xcode.\n# To customize your local environment, you can create an `.xcode.env.local`\n# file that is not versioned.\n\n# NODE_BINARY variable contains the PATH to the node executable.\n#\n# Customize the NODE_BINARY variable here.\n# For example, to use nvm with brew, add the following line\n# . \"$(brew --prefix nvm)/nvm.sh\" --no-use\nexport NODE_BINARY=$(command -v node)\n"
  },
  {
    "path": "examples/SampleApp/ios/Podfile",
    "content": "# Resolve react_native_pods.rb with node to allow for hoisting\nrequire Pod::Executable.execute_command('node', ['-p',\n  'require.resolve(\n    \"react-native/scripts/react_native_pods.rb\",\n    {paths: [process.argv[1]]},\n  )', __dir__]).strip\n\nplatform :ios, min_ios_version_supported\nprepare_react_native_project!\n\nuse_frameworks! :linkage => :static\n\ntarget 'SampleApp' do\n  config = use_native_modules!\n\n  pod 'SPTPersistentCache', :modular_headers => true\n  pod 'DVAssetLoaderDelegate', :modular_headers => true\n\n  use_react_native!(\n    :path => config[:reactNativePath],\n    # An absolute path to your application root.\n    :app_path => \"#{Pod::Config.instance.installation_root}/..\"\n  )\n\n  post_install do |installer|\n    # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202\n    react_native_post_install(\n      installer,\n      config[:reactNativePath],\n      :mac_catalyst_enabled => false,\n      # :ccache_enabled => true\n    )\n\n    # Fix fmt consteval compilation error with Xcode 26+\n    # https://github.com/expo/expo/issues/44229\n    fmt_base = File.join(installer.sandbox.pod_dir('fmt'), 'include', 'fmt', 'base.h')\n    if File.exist?(fmt_base)\n      content = File.read(fmt_base)\n      patched = content.gsub(/^#\\s*define FMT_USE_CONSTEVAL 1$/, '# define FMT_USE_CONSTEVAL 0')\n      if patched != content\n        File.chmod(0644, fmt_base)\n        File.write(fmt_base, patched)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp/AppDelegate.swift",
    "content": "import UIKit\nimport React\nimport React_RCTAppDelegate\nimport ReactAppDependencyProvider\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n  var window: UIWindow?\n\n  var reactNativeDelegate: ReactNativeDelegate?\n  var reactNativeFactory: RCTReactNativeFactory?\n\n  func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n  ) -> Bool {\n    let delegate = ReactNativeDelegate()\n    let factory = RCTReactNativeFactory(delegate: delegate)\n    delegate.dependencyProvider = RCTAppDependencyProvider()\n\n    reactNativeDelegate = delegate\n    reactNativeFactory = factory\n\n    window = UIWindow(frame: UIScreen.main.bounds)\n\n    factory.startReactNative(\n      withModuleName: \"sampleapp\",\n      in: window,\n      launchOptions: launchOptions\n    )\n\n    return true\n  }\n}\n\nclass ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {\n  override func sourceURL(for bridge: RCTBridge) -> URL? {\n    self.bundleURL()\n  }\n\n  override func bundleURL() -> URL? {\n#if DEBUG\n    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: \"index\")\n#else\n    Bundle.main.url(forResource: \"main\", withExtension: \"jsbundle\")\n#endif\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CometChat_React_Native_40x40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_60x60.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_58x58.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_87x87.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_80x80.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_120x120 1.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_120x120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_180x180.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"CometChat React native.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp/Images.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>sampleapp</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(MARKETING_VERSION)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t<key>NSAllowsArbitraryLoads</key>\n\t<false/>\n\t<key>NSAllowsLocalNetworking</key>\n\t<true/>\n\t</dict>\n\t<key>NSCameraUsageDescription</key>\n\t<string>This is for Camera permission</string>\n\t<key>NSLocationWhenInUseUsageDescription</key>\n\t<string></string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>This is for Mic permission</string>\n\t<key>RCTNewArchEnabled</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>arm64</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n\t<key>UIAppFonts</key>\n  \t<array>\n\t\t<string>inter_regular.ttf</string>\n  \t\t<string>inter_medium.ttf</string>\n  \t\t<string>inter_bold.ttf</string>\n  \t\t<string>roboto_regular.ttf</string>\n  \t\t<string>roboto_medium.ttf</string>\n  \t\t<string>roboto_bold.ttf</string>\n  \t\t<string>times_new_roman_regular.ttf</string>\n  \t\t<string>times_new_roman_medium.otf</string>\n  \t\t<string>times_new_roman_bold.ttf</string>\n  \t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"23504\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina6_1\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"23506\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"414\" height=\"896\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView clipsSubviews=\"YES\"\n                                       userInteractionEnabled=\"NO\"\n                                       contentMode=\"scaleAspectFit\"\n                                       horizontalHuggingPriority=\"251\"\n                                       verticalHuggingPriority=\"251\"\n                                       image=\"CometChat React native.png\"\n                                       translatesAutoresizingMaskIntoConstraints=\"NO\"\n                                       id=\"CtN-Dg-ods\">\n                                <!-- The rect is now just an initial placeholder -->\n                                <rect key=\"frame\" x=\"0\" y=\"0\" width=\"414\" height=\"128\"/>\n                            </imageView>\n                        </subviews>\n                        <!-- Auto Layout constraints for centering and sizing the image view -->\n                        <constraints>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"centerXConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"centerYConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"width\" constant=\"414\" id=\"widthConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"height\" constant=\"128\" id=\"heightConstraint\"/>\n                        </constraints>\n                        <viewLayoutGuide key=\"safeArea\" id=\"Bcu-3y-fUS\"/>\n                        <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"52.173913043478265\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"CometChat React native.png\" width=\"1024\" height=\"1024\"/>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp/PrivacyInfo.xcprivacy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrivacyAccessedAPITypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>C617.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryUserDefaults</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>CA92.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategorySystemBootTime</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>35F9.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSPrivacyCollectedDataTypes</key>\n\t<array/>\n\t<key>NSPrivacyTracking</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };\n\t\t295B57B00587C81464D7BEFE /* Pods_SampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D33CE56D95E35081CB0ADFD /* Pods_SampleApp.framework */; };\n\t\t761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };\n\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };\n\t\tBDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-SampleApp.debug.xcconfig\"; path = \"Target Support Files/Pods-SampleApp/Pods-SampleApp.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = SampleApp/Images.xcassets; sourceTree = \"<group>\"; };\n\t\t13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleApp/Info.plist; sourceTree = \"<group>\"; };\n\t\t13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = \"<group>\"; };\n\t\t24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-SampleApp.release.xcconfig\"; path = \"Target Support Files/Pods-SampleApp/Pods-SampleApp.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t5D33CE56D95E35081CB0ADFD /* Pods_SampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = SampleApp/AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SampleApp/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t295B57B00587C81464D7BEFE /* Pods_SampleApp.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t13B07FAE1A68108700A75B9A /* SampleApp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FB51A68108700A75B9A /* Images.xcassets */,\n\t\t\t\t761780EC2CA45674006654EE /* AppDelegate.swift */,\n\t\t\t\t13B07FB61A68108700A75B9A /* Info.plist */,\n\t\t\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,\n\t\t\t\t13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,\n\t\t\t);\n\t\t\tname = SampleApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */,\n\t\t\t\t5D33CE56D95E35081CB0ADFD /* Pods_SampleApp.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t832341AE1AAA6A7D00B99B32 /* Libraries */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Libraries;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t83CBB9F61A601CBA00E9B192 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FAE1A68108700A75B9A /* SampleApp */,\n\t\t\t\t832341AE1AAA6A7D00B99B32 /* Libraries */,\n\t\t\t\t83CBBA001A601CBA00E9B192 /* Products */,\n\t\t\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */,\n\t\t\t\tBBD78D7AC51CEA395F1C20DB /* Pods */,\n\t\t\t);\n\t\t\tindentWidth = 2;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 2;\n\t\t\tusesTabs = 0;\n\t\t};\n\t\t83CBBA001A601CBA00E9B192 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07F961A680F5B00A75B9A /* SampleApp.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBBD78D7AC51CEA395F1C20DB /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */,\n\t\t\t\t24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t13B07F861A680F5B00A75B9A /* SampleApp */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"SampleApp\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t13B07F871A680F5B00A75B9A /* Sources */,\n\t\t\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */,\n\t\t\t\t13B07F8E1A680F5B00A75B9A /* Resources */,\n\t\t\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,\n\t\t\t\tB26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */,\n\t\t\t\t313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = SampleApp;\n\t\t\tproductName = SampleApp;\n\t\t\tproductReference = 13B07F961A680F5B00A75B9A /* SampleApp.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t83CBB9F71A601CBA00E9B192 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1210;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t13B07F861A680F5B00A75B9A = {\n\t\t\t\t\t\tLastSwiftMigration = 1120;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"SampleApp\" */;\n\t\t\tcompatibilityVersion = \"Xcode 12.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 83CBB9F61A601CBA00E9B192;\n\t\t\tproductRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t13B07F861A680F5B00A75B9A /* SampleApp */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t13B07F8E1A680F5B00A75B9A /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,\n\t\t\t\tBDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"$(SRCROOT)/.xcode.env.local\",\n\t\t\t\t\"$(SRCROOT)/.xcode.env\",\n\t\t\t);\n\t\t\tname = \"Bundle React Native code and images\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"set -e\\n\\nWITH_ENVIRONMENT=\\\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\\\"\\nREACT_NATIVE_XCODE=\\\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\\\"\\n\\n/bin/sh -c \\\"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\\\"\\n\";\n\t\t};\n\t\t313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-SampleApp-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"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\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tB26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t13B07F871A680F5B00A75B9A /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t13B07F941A680F5B00A75B9A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = T9F7QDSC2S;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = SampleApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = v5.3.4;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.cometchat.internal.reactnative.ios;\n\t\t\t\tPRODUCT_NAME = SampleApp;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t13B07F951A680F5B00A75B9A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = T9F7QDSC2S;\n\t\t\t\tINFOPLIST_FILE = SampleApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = v5.3.4;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.cometchat.internal.reactnative.ios;\n\t\t\t\tPRODUCT_NAME = SampleApp;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t83CBBA201A601CBA00E9B192 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphonesimulator*]\" = \"\";\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\",\n\t\t\t\t);\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(SDKROOT)/usr/lib/swift\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tOTHER_CPLUSPLUSFLAGS = (\n\t\t\t\t\t\"$(OTHER_CFLAGS)\",\n\t\t\t\t\t\"-DFOLLY_NO_CONFIG\",\n\t\t\t\t\t\"-DFOLLY_MOBILE=1\",\n\t\t\t\t\t\"-DFOLLY_USE_LIBCPP=1\",\n\t\t\t\t\t\"-DFOLLY_CFG_NO_COROUTINES=1\",\n\t\t\t\t\t\"-DFOLLY_HAVE_CLOCK_GETTIME=1\",\n\t\t\t\t);\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"$(inherited) DEBUG\";\n\t\t\t\tUSE_HERMES = true;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t83CBBA211A601CBA00E9B192 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphonesimulator*]\" = \"\";\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\",\n\t\t\t\t);\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(SDKROOT)/usr/lib/swift\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tOTHER_CPLUSPLUSFLAGS = (\n\t\t\t\t\t\"$(OTHER_CFLAGS)\",\n\t\t\t\t\t\"-DFOLLY_NO_CONFIG\",\n\t\t\t\t\t\"-DFOLLY_MOBILE=1\",\n\t\t\t\t\t\"-DFOLLY_USE_LIBCPP=1\",\n\t\t\t\t\t\"-DFOLLY_CFG_NO_COROUTINES=1\",\n\t\t\t\t\t\"-DFOLLY_HAVE_CLOCK_GETTIME=1\",\n\t\t\t\t);\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tUSE_HERMES = true;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"SampleApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t13B07F941A680F5B00A75B9A /* Debug */,\n\t\t\t\t13B07F951A680F5B00A75B9A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"SampleApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t83CBBA201A601CBA00E9B192 /* Debug */,\n\t\t\t\t83CBBA211A601CBA00E9B192 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;\n}\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1210\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n               BuildableName = \"SampleApp.app\"\n               BlueprintName = \"SampleApp\"\n               ReferencedContainer = \"container:SampleApp.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"00E356ED1AD99517003FC87E\"\n               BuildableName = \"SampleAppTests.xctest\"\n               BlueprintName = \"SampleAppTests\"\n               ReferencedContainer = \"container:SampleApp.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"SampleApp.app\"\n            BlueprintName = \"SampleApp\"\n            ReferencedContainer = \"container:SampleApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"SampleApp.app\"\n            BlueprintName = \"SampleApp\"\n            ReferencedContainer = \"container:SampleApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:SampleApp.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "examples/SampleApp/ios/SampleApp.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "examples/SampleApp/jest.config.js",
    "content": "module.exports = {\n  preset: 'react-native',\n};\n"
  },
  {
    "path": "examples/SampleApp/metro.config.js",
    "content": "const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('@react-native/metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);\n"
  },
  {
    "path": "examples/SampleApp/package.json",
    "content": "{\n  \"name\": \"sample-app\",\n  \"version\": \"5.3.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"android\": \"react-native run-android\",\n    \"ios\": \"react-native run-ios\",\n    \"lint\": \"eslint .\",\n    \"start\": \"react-native start\",\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"@cometchat/calls-sdk-react-native\": \"^4.4.0\",\n    \"@cometchat/chat-sdk-react-native\": \"^4.0.21\",\n    \"@cometchat/chat-uikit-react-native\": \"^5.3.4\",\n    \"@react-native-async-storage/async-storage\": \"^2.2.0\",\n    \"@react-native-clipboard/clipboard\": \"^1.16.3\",\n    \"@react-native-community/datetimepicker\": \"^8.4.5\",\n    \"@react-native-community/netinfo\": \"^11.4.1\",\n    \"@react-native/new-app-screen\": \"0.81.4\",\n    \"@react-navigation/bottom-tabs\": \"^7.4.7\",\n    \"@react-navigation/native\": \"^7.1.17\",\n    \"@react-navigation/native-stack\": \"^7.14.12\",\n    \"@react-navigation/stack\": \"^7.4.8\",\n    \"dayjs\": \"^1.11.18\",\n    \"react\": \"19.1.0\",\n    \"react-native\": \"0.81.4\",\n    \"react-native-background-timer\": \"^2.4.1\",\n    \"react-native-callstats\": \"^3.73.22\",\n    \"react-native-gesture-handler\": \"^2.28.0\",\n    \"react-native-localize\": \"^3.5.2\",\n    \"react-native-safe-area-context\": \"^5.5.2\",\n    \"react-native-screens\": \"^4.16.0\",\n    \"react-native-svg\": \"^15.13.0\",\n    \"react-native-video\": \"^6.16.1\",\n    \"react-native-vision-camera\": \"^4.7.2\",\n    \"react-native-webrtc\": \"^124.0.7\",\n    \"zustand\": \"^5.0.8\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.25.2\",\n    \"@babel/preset-env\": \"^7.25.3\",\n    \"@babel/runtime\": \"^7.25.0\",\n    \"@react-native-community/cli\": \"20.0.0\",\n    \"@react-native-community/cli-platform-android\": \"20.0.0\",\n    \"@react-native-community/cli-platform-ios\": \"20.0.0\",\n    \"@react-native/babel-preset\": \"0.81.4\",\n    \"@react-native/eslint-config\": \"0.81.4\",\n    \"@react-native/metro-config\": \"0.81.4\",\n    \"@react-native/typescript-config\": \"0.81.4\",\n    \"@types/jest\": \"^29.5.13\",\n    \"@types/react\": \"^19.1.0\",\n    \"@types/react-test-renderer\": \"^19.1.0\",\n    \"eslint\": \"^8.19.0\",\n    \"jest\": \"^29.6.3\",\n    \"prettier\": \"2.8.8\",\n    \"react-test-renderer\": \"19.1.0\",\n    \"typescript\": \"^5.8.3\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/AccountCircle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.743 17.438q1.485-1.047 3-1.609a9.3 9.3 0 0 1 3.256-.562q1.742 0 3.26.562 1.52.563 3.02 1.608 1.046-1.262 1.508-2.601a8.6 8.6 0 0 0 .462-2.835q0-3.497-2.377-5.874T12 3.75 6.126 6.127Q3.75 8.505 3.75 12.001q0 1.496.471 2.835.471 1.34 1.523 2.601m6.255-4.638q-1.44 0-2.428-.989-.987-.989-.987-2.429t.988-2.428 2.43-.987 2.428.989q.987.988.987 2.428t-.989 2.428-2.429.988m.005 9.033a9.5 9.5 0 0 1-3.822-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.124 9.5 9.5 0 0 1-.777-3.824q0-2.026.777-3.817a10 10 0 0 1 2.113-3.125 10 10 0 0 1 3.124-2.11 9.5 9.5 0 0 1 3.824-.776q2.025 0 3.818.777a10 10 0 0 1 3.124 2.113 10 10 0 0 1 2.11 3.125 9.5 9.5 0 0 1 .776 3.814q0 2.027-.777 3.822a10 10 0 0 1-2.112 3.129 10 10 0 0 1-3.126 2.11 9.5 9.5 0 0 1-3.814.776m-.004-1.583q1.342 0 2.586-.387 1.244-.388 2.447-1.296a10.3 10.3 0 0 0-2.455-1.277A7.7 7.7 0 0 0 12 16.85a7.9 7.9 0 0 0-2.584.433q-1.26.434-2.451 1.284 1.204.907 2.447 1.296A8.6 8.6 0 0 0 12 20.25m0-9.033q.795 0 1.315-.519.518-.519.518-1.315t-.518-1.314-1.315-.519-1.314.519-.519 1.314.519 1.315 1.314.519'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/AddComment.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.212 10.783v2.338q0 .333.228.564.228.232.562.232t.564-.232a.77.77 0 0 0 .229-.564v-2.338h2.338q.333 0 .564-.232a.77.77 0 0 0 .232-.562.76.76 0 0 0-.232-.56.77.77 0 0 0-.564-.229h-2.338V6.854a.76.76 0 0 0-.232-.556.77.77 0 0 0-.567-.231.75.75 0 0 0-.56.231.77.77 0 0 0-.224.556V9.2H8.866a.75.75 0 0 0-.56.234.78.78 0 0 0-.227.56q0 .327.227.558a.76.76 0 0 0 .56.231zm-5.15 7.067L3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/AiIcon.tsx",
    "content": "import Svg, { Path, Defs, LinearGradient, Stop } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst AiIcon = ({ width = 24, height = 24 }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 14 14\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M4.06663 7.53366L4.485 8.37042C4.6266 8.6536 4.6974 8.7952 4.79198 8.9179C4.8759 9.02678 4.97351 9.12438 5.08239 9.20831C5.20509 9.30289 5.34668 9.37369 5.62987 9.51528L6.46663 9.93366L5.62987 10.352C5.34668 10.4936 5.20509 10.5644 5.08239 10.659C4.97351 10.7429 4.8759 10.8405 4.79198 10.9494C4.6974 11.0721 4.6266 11.2137 4.485 11.4969L4.06663 12.3337L3.64825 11.4969C3.50665 11.2137 3.43586 11.0721 3.34128 10.9494C3.25735 10.8405 3.15974 10.7429 3.05086 10.659C2.92816 10.5644 2.78657 10.4936 2.50338 10.352L1.66663 9.93366L2.50338 9.51528C2.78657 9.37369 2.92816 9.30289 3.05086 9.20831C3.15974 9.12438 3.25735 9.02678 3.34128 8.9179C3.43586 8.7952 3.50665 8.6536 3.64825 8.37041L4.06663 7.53366Z\"\n      fill=\"url(#paint0_linear)\"\n    />\n    <Path\n      d=\"M8.59996 1.66699L9.22856 3.30135C9.37896 3.6924 9.45417 3.88793 9.57111 4.0524C9.67476 4.19817 9.80212 4.32552 9.94788 4.42917C10.1124 4.54612 10.3079 4.62132 10.6989 4.77173L12.3333 5.40033L10.6989 6.02892C10.3079 6.17933 10.1124 6.25453 9.94788 6.37148C9.80212 6.47513 9.67476 6.60248 9.57111 6.74825C9.45417 6.91272 9.37896 7.10825 9.22856 7.4993L8.59996 9.13366L7.97136 7.4993C7.82095 7.10825 7.74575 6.91272 7.62881 6.74825C7.52516 6.60248 7.3978 6.47513 7.25204 6.37148C7.08757 6.25453 6.89204 6.17933 6.50098 6.02892L4.86663 5.40033L6.50098 4.77173C6.89204 4.62132 7.08757 4.54612 7.25203 4.42917C7.3978 4.32552 7.52516 4.19817 7.62881 4.0524C7.74575 3.88793 7.82095 3.6924 7.97136 3.30135L8.59996 1.66699Z\"\n      fill=\"url(#paint1_linear)\"\n    />\n    <Defs>\n      <LinearGradient\n        id=\"paint0_linear\"\n        x1=\"6.99996\"\n        y1=\"1.66699\"\n        x2=\"6.99996\"\n        y2=\"19.2003\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <Stop stopColor=\"#AD94F2\" />\n        <Stop offset=\"1\" stopColor=\"#3302B8\" />\n      </LinearGradient>\n      <LinearGradient\n        id=\"paint1_linear\"\n        x1=\"6.99996\"\n        y1=\"1.66699\"\n        x2=\"6.99996\"\n        y2=\"19.2003\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <Stop stopColor=\"#AD94F2\" />\n        <Stop offset=\"1\" stopColor=\"#3302B8\" />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\n\nexport default AiIcon;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/ArrowBack.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m7.193 12.787 5.366 5.367a.8.8 0 0 1 .24.563.75.75 0 0 1-.236.554.79.79 0 0 1-.562.243.74.74 0 0 1-.554-.243L4.73 12.554a.75.75 0 0 1-.185-.256.8.8 0 0 1-.057-.298.8.8 0 0 1 .057-.298.8.8 0 0 1 .185-.265l6.717-6.716a.76.76 0 0 1 .552-.234q.318 0 .564.234a.8.8 0 0 1 .238.564q0 .319-.238.557l-5.37 5.362h11.854a.76.76 0 0 1 .558.23.77.77 0 0 1 .229.566.76.76 0 0 1-.229.562.77.77 0 0 1-.558.225z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Block.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.002 21.833a9.6 9.6 0 0 1-3.834-.774 10 10 0 0 1-3.123-2.105 9.9 9.9 0 0 1-2.104-3.12 9.6 9.6 0 0 1-.773-3.833q0-2.043.775-3.835a10 10 0 0 1 2.104-3.122 9.9 9.9 0 0 1 3.12-2.104A9.6 9.6 0 0 1 12 2.166q2.043 0 3.835.775a10 10 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.105 3.12 9.6 9.6 0 0 1 .772 3.833q0 2.043-.774 3.835a10 10 0 0 1-2.104 3.122 9.9 9.9 0 0 1-3.12 2.105 9.6 9.6 0 0 1-3.834.772m0-1.583q1.469 0 2.816-.488a8 8 0 0 0 2.45-1.412L5.655 6.733a8.6 8.6 0 0 0-1.408 2.46A8 8 0 0 0 3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399m6.33-2.983a8.8 8.8 0 0 0 1.407-2.449A7.9 7.9 0 0 0 20.25 12q0-3.452-2.399-5.851t-5.85-2.399q-1.464 0-2.807.496a8.4 8.4 0 0 0-2.46 1.42z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Builder.tsx",
    "content": "import React from \"react\";\nimport Svg, { Path, SvgProps } from \"react-native-svg\";\n\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d=\"M2.30775 17.5C1.81058 17.5 1.385 17.323 1.031 16.969C0.677 16.615 0.5 16.1894 0.5 15.6923V2.30775C0.5 1.81058 0.677 1.385 1.031 1.031C1.385 0.677 1.81058 0.5 2.30775 0.5H15.6923C16.1894 0.5 16.615 0.677 16.969 1.031C17.323 1.385 17.5 1.81058 17.5 2.30775V15.6923C17.5 16.1894 17.323 16.615 16.969 16.969C16.615 17.323 16.1894 17.5 15.6923 17.5H2.30775ZM2.30775 16H15.6923C15.7821 16 15.8558 15.9712 15.9135 15.9135C15.9712 15.8558 16 15.7821 16 15.6923V4H2V15.6923C2 15.7821 2.02883 15.8558 2.0865 15.9135C2.14417 15.9712 2.21792 16 2.30775 16ZM9 13.5C7.77433 13.5 6.67533 13.1757 5.703 12.527C4.7305 11.8782 4.01217 11.0358 3.548 10C4.01217 8.96417 4.7305 8.12183 5.703 7.473C6.67533 6.82433 7.77433 6.5 9 6.5C10.2257 6.5 11.3247 6.82433 12.297 7.473C13.2695 8.12183 13.9878 8.96417 14.452 10C13.9878 11.0358 13.2695 11.8782 12.297 12.527C11.3247 13.1757 10.2257 13.5 9 13.5ZM9 12.3077C9.86283 12.3077 10.6568 12.1045 11.3818 11.698C12.1068 11.2917 12.6955 10.7257 13.148 10C12.6955 9.27433 12.1068 8.70833 11.3818 8.302C10.6568 7.8955 9.86283 7.69225 9 7.69225C8.13717 7.69225 7.34325 7.8955 6.61825 8.302C5.89325 8.70833 5.3045 9.27433 4.852 10C5.3045 10.7257 5.89325 11.2917 6.61825 11.698C7.34325 12.1045 8.13717 12.3077 9 12.3077ZM9.00225 11.3077C9.36608 11.3077 9.67458 11.1803 9.92775 10.9255C10.1811 10.6708 10.3077 10.3616 10.3077 9.99775C10.3077 9.63392 10.1803 9.32542 9.9255 9.07225C9.67083 8.81892 9.36158 8.69225 8.99775 8.69225C8.63392 8.69225 8.32542 8.81967 8.07225 9.0745C7.81892 9.32917 7.69225 9.63842 7.69225 10.0022C7.69225 10.3661 7.81967 10.6746 8.0745 10.9277C8.32917 11.1811 8.63842 11.3077 9.00225 11.3077Z\"\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/CallFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Chat.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.062 17.85 3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275zm1.5-2.334h6.15a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.229h-6.15a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.557q0 .335.232.564a.77.77 0 0 0 .564.23m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.23H6.895a.77.77 0 0 0-.564.234.76.76 0 0 0-.232.557q0 .335.232.564.231.23.564.229m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.563.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.229H6.895a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.558q0 .334.232.564.231.229.564.229'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Chatfill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.039 17.5-2.002 2.002q-.427.426-.982.192-.555-.235-.555-.84V4.308q0-.758.525-1.283T4.308 2.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zM7 13.75h6q.319 0 .534-.216A.73.73 0 0 0 13.75 13a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 13.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 10a.73.73 0 0 0-.216-.534A.73.73 0 0 0 17 9.25H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 10.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 7a.73.73 0 0 0-.216-.535A.73.73 0 0 0 17 6.25H7a.73.73 0 0 0-.534.216A.73.73 0 0 0 6.25 7q0 .32.216.535A.73.73 0 0 0 7 7.75'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/CheckFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m9.551 15.516 8.639-8.639a.73.73 0 0 1 .522-.228q.299-.005.532.228a.74.74 0 0 1 .232.535q0 .302-.232.534l-9.06 9.075a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.27l-4.175-4.176a.71.71 0 0 1-.22-.53.75.75 0 0 1 .236-.539.74.74 0 0 1 .534-.233q.303 0 .535.233z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Close.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 13.117 6.895 18.22a.763.763 0 0 1-1.117-.004.766.766 0 0 1 .004-1.1l5.1-5.117-5.1-5.117a.74.74 0 0 1-.229-.544q0-.315.23-.556a.74.74 0 0 1 .55-.244.78.78 0 0 1 .562.232l5.104 5.112 5.112-5.112a.75.75 0 0 1 .555-.232.78.78 0 0 1 .562.244.76.76 0 0 1 .223.556.77.77 0 0 1-.235.544L13.116 12l5.1 5.117a.74.74 0 0 1 .229.543q0 .314-.23.557a.74.74 0 0 1-.55.243.74.74 0 0 1-.553-.24z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Delete.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.734 20.833q-.655 0-1.12-.464a1.53 1.53 0 0 1-.464-1.119V5.55h-.204a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.562q0-.33.232-.56a.77.77 0 0 1 .564-.23h3.89v-.012a.76.76 0 0 1 .23-.558.77.77 0 0 1 .564-.23h4.766q.324 0 .556.232.231.232.232.556v.013h3.9q.32 0 .552.232a.76.76 0 0 1 .231.558.76.76 0 0 1-.232.564.76.76 0 0 1-.555.229h-.213v13.7q0 .655-.464 1.119-.465.464-1.12.464zM17.284 5.55H6.734v13.7h10.55zm-7.29 11.62a.77.77 0 0 0 .559-.226.76.76 0 0 0 .23-.56V8.4a.77.77 0 0 0-.232-.565.76.76 0 0 0-.558-.23.76.76 0 0 0-.563.23.77.77 0 0 0-.23.565v7.983q0 .335.234.56a.78.78 0 0 0 .56.228m4.034 0a.77.77 0 0 0 .558-.226.76.76 0 0 0 .231-.56V8.4a.77.77 0 0 0-.233-.565.76.76 0 0 0-.557-.23.76.76 0 0 0-.564.23.77.77 0 0 0-.23.565v7.983q0 .335.235.56a.78.78 0 0 0 .56.228'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Flash.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ width = 24, height = 24, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M18 2H6V8L8 11V22H16V11L18 8V2ZM16 4V5H8V4H16ZM14 10.4V20H10V10.39L8 7.39V7H16V7.39L14 10.4Z\"\n      fill={color || \"#F9F8FD\"}\n    />\n    <Path\n      d=\"M12 15.5C12.8284 15.5 13.5 14.8284 13.5 14C13.5 13.1716 12.8284 12.5 12 12.5C11.1716 12.5 10.5 13.1716 10.5 14C10.5 14.8284 11.1716 15.5 12 15.5Z\"\n      fill={color || \"#F9F8FD\"}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Group.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M1.21 17.381q0-.85.431-1.54.43-.69 1.2-1.04 1.72-.776 3.2-1.137 1.478-.36 3.024-.36t3.013.36q1.468.361 3.186 1.136.771.35 1.211 1.04t.44 1.541v.819q0 .653-.465 1.118t-1.118.465H2.794q-.66 0-1.122-.465a1.53 1.53 0 0 1-.461-1.118zm20 2.402h-3.1q.159-.383.274-.77.114-.386.114-.813v-.817q0-1.41-.654-2.384-.654-.975-1.979-1.628 1.537.191 2.89.535 1.351.344 2.29.831.82.463 1.285 1.121.464.66.464 1.473v.865q0 .66-.465 1.123-.465.464-1.118.464M9.066 12q-1.62 0-2.677-1.056T5.332 8.267 6.388 5.59t2.677-1.057q1.62 0 2.677 1.057t1.056 2.677-1.056 2.677Q10.685 12 9.065 12m9.033-3.743q0 1.61-1.056 2.67t-2.677 1.06q-.255 0-.596-.042a3 3 0 0 1-.604-.13 4.4 4.4 0 0 0 .944-1.565 5.9 5.9 0 0 0 .323-1.986q0-1.08-.323-1.962a5.2 5.2 0 0 0-.944-1.602q.271-.084.596-.126a5 5 0 0 1 .604-.04q1.62 0 2.677 1.059t1.056 2.664M2.794 18.2h12.538v-.815q0-.364-.209-.69a1.3 1.3 0 0 0-.512-.474q-1.655-.755-2.9-1.044-1.246-.29-2.642-.29-1.4 0-2.662.29-1.262.289-2.909 1.044-.312.15-.508.476-.195.327-.196.686zm6.27-7.783q.922 0 1.536-.613.615-.614.615-1.536t-.613-1.537-1.535-.614-1.537.613-.615 1.535q0 .922.613 1.537t1.535.615'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/GroupAdd.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.596 11.642q.638-.694.944-1.594t.306-1.856-.306-1.856a4.4 4.4 0 0 0-.944-1.594q1.317.151 2.187 1.138t.87 2.312q0 1.327-.87 2.313a3.33 3.33 0 0 1-2.187 1.137m4.863 7.666q.188-.345.288-.733.099-.389.099-.787v-.827q0-.817-.333-1.556a3.6 3.6 0 0 0-.944-1.267q1.15.384 2.117 1.033.967.65.967 1.79v.827q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm2.194-8.558h-1.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.25V8q0-.319.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v1.25h1.25q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .319-.215.534a.73.73 0 0 1-.535.216h-1.25V12q0 .318-.215.534a.73.73 0 0 1-.535.216.72.72 0 0 1-.534-.216.73.73 0 0 1-.216-.534zm-11.307.942q-1.444 0-2.472-1.028T4.846 8.192 5.874 5.72t2.472-1.028 2.472 1.028 1.028 2.472-1.028 2.472-2.472 1.028m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.991-1.09 12.95 12.95 0 0 1 6.087 0q1.509.364 2.991 1.09.667.337 1.066.963.4.625.4 1.36v.704q0 .633-.444 1.076a1.47 1.47 0 0 1-1.075.444H2.364q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/GroupFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M1.797 17.085q0-.774.399-1.38a2.7 2.7 0 0 1 1.066-.944q1.426-.697 2.866-1.075t3.169-.378 3.168.377 2.867 1.076q.666.338 1.066.944.399.606.399 1.38v.703q0 .605-.444 1.062a1.44 1.44 0 0 1-1.076.458H3.316q-.633 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076zm16.613 2.223q.189-.345.288-.733t.099-.787v-.826q0-.985-.482-1.877a4.54 4.54 0 0 0-1.368-1.531q1.005.15 1.91.464.903.315 1.724.743.775.414 1.197.975t.422 1.226v.826q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm-9.113-7.616q-1.444 0-2.472-1.028T5.797 8.192q0-1.443 1.028-2.471t2.472-1.029 2.472 1.029 1.028 2.471-1.028 2.472q-1.029 1.028-2.472 1.028m8.634-3.5q0 1.444-1.028 2.472t-2.472 1.028a3 3 0 0 1-.43-.038 4 4 0 0 1-.431-.085q.59-.711.91-1.578a5.2 5.2 0 0 0-.007-3.593 5.8 5.8 0 0 0-.903-1.582q.215-.078.43-.1.216-.024.431-.024 1.444 0 2.472 1.029t1.028 2.471'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Info.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/InfoIcon.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Logout.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h5.952q.318 0 .534.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.952q.318 0 .534.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215zm12.31-7.75H9.845a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h7.771l-1.923-1.923a.7.7 0 0 1-.212-.507.74.74 0 0 1 .212-.531.72.72 0 0 1 .527-.241.72.72 0 0 1 .543.225l3.094 3.094q.27.271.27.633 0 .361-.27.633l-3.094 3.094a.71.71 0 0 1-.53.22.75.75 0 0 1-.54-.236.73.73 0 0 1-.21-.534.74.74 0 0 1 .226-.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Person.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06q1.628 0 2.689 1.06 1.06 1.06 1.06 2.69t-1.06 2.69-2.69 1.06m-7.85 6.2v-.817q0-.9.451-1.568a2.9 2.9 0 0 1 1.185-1.018 18 18 0 0 1 3.14-1.116 12.8 12.8 0 0 1 3.073-.377q1.545 0 3.065.381a18.4 18.4 0 0 1 3.134 1.118q.75.348 1.201 1.012.45.664.45 1.568v.821q0 .648-.465 1.114-.465.465-1.118.465H5.732q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.533v-.814q0-.366-.208-.686a1.36 1.36 0 0 0-.513-.48q-1.512-.728-2.831-1.03A12 12 0 0 0 12 14.887q-1.403 0-2.732.303-1.328.302-2.825 1.03-.312.16-.512.481-.2.322-.2.686zm6.266-7.783q.925 0 1.546-.621t.621-1.546-.62-1.546-1.547-.62-1.545.62-.621 1.546.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/PersonAdd.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.13 10.817h-2.345a.75.75 0 0 1-.56-.234.78.78 0 0 1-.228-.561q0-.327.227-.558a.76.76 0 0 1 .56-.23h2.347V6.887q0-.325.227-.556a.76.76 0 0 1 .563-.232q.334 0 .564.232t.229.556v2.345h2.337q.333 0 .565.233a.76.76 0 0 1 .231.558q0 .334-.231.564a.77.77 0 0 1-.565.229h-2.337v2.337q0 .333-.232.565a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.565zM9.019 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06 2.69 1.06 1.06 2.69-1.06 2.69T9.018 12m-7.85 6.2v-.818q0-.85.427-1.539a2.67 2.67 0 0 1 1.21-1.04q1.746-.795 3.22-1.147a12.8 12.8 0 0 1 5.973 0q1.467.352 3.22 1.144.78.363 1.215 1.046a2.8 2.8 0 0 1 .435 1.537v.821q0 .648-.465 1.114-.465.465-1.118.465H2.75q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.534v-.813q0-.364-.196-.686a1.23 1.23 0 0 0-.525-.48q-1.63-.763-2.882-1.048a12 12 0 0 0-2.662-.286q-1.419 0-2.677.286t-2.88 1.048q-.337.158-.524.479a1.33 1.33 0 0 0-.188.683zm6.267-7.783q.925 0 1.546-.621.62-.621.62-1.546t-.62-1.546q-.622-.62-1.546-.62-.925 0-1.546.62-.62.621-.62 1.546t.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/PersonFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 11.692q-1.448 0-2.474-1.026A3.37 3.37 0 0 1 8.5 8.192q0-1.448 1.026-2.474A3.37 3.37 0 0 1 12 4.692q1.448 0 2.474 1.026A3.37 3.37 0 0 1 15.5 8.192q0 1.449-1.026 2.474A3.37 3.37 0 0 1 12 11.692m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.992-1.09 12.95 12.95 0 0 1 6.086 0q1.509.364 2.992 1.09.667.337 1.066.963t.399 1.36v.704q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444H6.019q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/PersonOff.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill={color ?? \"#none\"}\n    viewBox=\"0 0 24 24\"\n  >\n    <Path\n    fill={color ?? \"#000\"}\n      d=\"m18.777 20.6-2.108-2.092H6.622q-.552 0-.937-.385a1.27 1.27 0 0 1-.385-.935v-.404q0-.529.274-.982a2.1 2.1 0 0 1 .766-.74q1.171-.68 2.449-1.07 1.275-.39 2.61-.448h.158q.081 0 .159.01L3.385 5.223a.6.6 0 0 1-.195-.45.66.66 0 0 1 .21-.47.64.64 0 0 1 .464-.207q.255 0 .455.208l15.375 15.39a.65.65 0 0 1 .205.454.6.6 0 0 1-.203.452.62.62 0 0 1-.455.208.64.64 0 0 1-.464-.208M6.6 17.208h8.77l-2.328-2.352q-.27-.02-.52-.034a9.93 9.93 0 0 0-3.109.326 9.5 9.5 0 0 0-2.384 1.002.9.9 0 0 0-.315.278.6.6 0 0 0-.114.356zm11.296-2.04q.222.14.358.34.136.202.177.46l-1.187-1.187.33.18q.165.09.322.206m-4.18-4.009-.975-.95q.481-.22.77-.662.29-.44.289-.964 0-.741-.526-1.266a1.73 1.73 0 0 0-1.264-.525q-.522 0-.963.29-.442.288-.664.77l-.95-.975a2.85 2.85 0 0 1 1.112-1.027q.69-.358 1.455-.358 1.298 0 2.199.901.9.9.901 2.2 0 .764-.358 1.454-.357.69-1.027 1.112\"\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/Reset.tsx",
    "content": "import React from \"react\";\nimport Svg, { Path, SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 90 90\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M75.702 53.014c-2.142 7.995-7.27 14.678-14.439 18.816-7.168 4.138-15.519 5.239-23.514 3.095-16.505-4.423-26.335-21.448-21.913-37.953C20.258 20.467 37.286 10.64 53.79 15.06c4.213 1.129 8.076 3.118 11.413 5.809l-8.349 8.35h26.654V2.565l-8.354 8.354c-5.1-4.405-11.133-7.61-17.74-9.381C33.451-4.882 8.735 9.389 2.314 33.35c-6.42 23.961 7.851 48.678 31.811 55.098C38.001 89.486 41.934 90 45.842 90c7.795 0 15.488-2.044 22.42-6.046 10.407-6.008 17.851-15.709 20.962-27.317L75.702 53.014z\"\n      fill={color || \"#000\"}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/UserEmptyIcon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Mask, Path, Rect } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 120 120'>\n    <Mask\n      id='mask0_5082_129886'\n      style={{ maskType: \"alpha\" }}\n      maskUnits='userSpaceOnUse'\n      x='-20'\n      y='-20'\n      width='160'\n      height='160'\n    >\n      <Rect x='-20' y='-20' width='160' height='160' fill='#D9D9D9' />\n    </Mask>\n    <G mask='url(#mask0_5082_129886)'>\n      <Path\n        d='M60 57.9479C53.5644 57.9479 48.0667 55.6679 43.5067 51.1079C38.9467 46.549 36.6667 41.0512 36.6667 34.6146C36.6667 28.179 38.9467 22.6825 43.5067 18.1235C48.0667 13.5644 53.5644 11.2844 60 11.2844C66.4356 11.2844 71.9333 13.5644 76.4933 18.1235C81.0533 22.6825 83.3333 28.179 83.3333 34.6146C83.3333 41.0512 81.0533 46.549 76.4933 51.1079C71.9333 55.6679 66.4356 57.9479 60 57.9479ZM19.3166 105.007C16.1811 105.007 13.4568 103.856 11.1437 101.555C8.8306 99.2539 7.67344 96.5196 7.67344 93.3513V91.0288C7.67344 86.7545 8.70044 82.9018 10.7544 79.4706C12.8084 76.0385 15.6466 73.4095 19.268 71.5826C24.3797 69.1016 29.6071 67.2153 34.95 65.9235C40.2929 64.6317 46.0113 63.9858 52.105 63.9858H67.8949C73.987 63.9858 79.7055 64.6317 85.0491 65.9235C90.3928 67.2153 95.6215 69.1016 100.735 71.5826C104.357 73.4095 107.195 76.0385 109.249 79.4706C111.303 82.9018 112.33 86.7545 112.33 91.0288V93.3513C112.33 96.5196 111.173 99.2539 108.86 101.555C106.547 103.856 103.823 105.007 100.687 105.007H19.3166Z'\n        fill={color}\n      />\n    </G>\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/assets/icons/VideoCam.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.779 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h12.537q.627 0 1.104.471.475.471.475 1.112v5.1L21.13 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.104.47zm0-1.583h12.533V5.733H3.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/AIAgent/AIAgents.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUsers, useTheme} from '@cometchat/chat-uikit-react-native';\nimport React, {useCallback, useLayoutEffect} from 'react';\nimport {SafeAreaView} from 'react-native';\nimport {useFocusEffect, useNavigation} from '@react-navigation/native';\nimport {RootStackParamList} from '../../navigation/types';\nimport {StackNavigationProp} from '@react-navigation/stack';\n\ntype AIAgentNavigationProp = StackNavigationProp<RootStackParamList, 'AIAgents'>;\n\nconst AIAgents: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<AIAgentNavigationProp>();\n  const [shouldHide, setShouldHide] = React.useState(false);\n\n  // Focus effect to manage component visibility\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  // Configure header with back button\n  useLayoutEffect(() => {\n    navigation.setOptions({\n      headerShown: false,\n      title: 'AI Assistants',\n      headerStyle: {\n        backgroundColor: theme.color.background1,\n      },\n      headerTitleStyle: {\n        color: theme.color.textPrimary,\n      },\n    });\n  }, [navigation, theme]);\n\n  const handleUserPress = (user: CometChat.User) => {\n    navigation.navigate('Messages', { user });\n  };\n\n  return shouldHide ? null : (\n    <SafeAreaView style={{flex: 1, backgroundColor: theme.color.background1}}>\n      <CometChatUsers\n        onItemPress={handleUserPress}\n        title='Assistants'\n        showBackButton={true}\n        onBack={() => navigation.goBack()}\n        usersRequestBuilder={new CometChat.UsersRequestBuilder()\n          .setLimit(30)\n          .hideBlockedUsers(false)\n          .setRoles(['@agentic'])\n          .friendsOnly(false)\n          .setStatus('')\n          .setTags([])\n          .sortBy('name')\n          .setUIDs([])}\n      />\n    </SafeAreaView>\n  );\n};\n\nexport default AIAgents;"
  },
  {
    "path": "examples/SampleApp/src/components/calls/CallDetailHelper.tsx",
    "content": "import {CometChatTheme, CometChatUIKit} from '@cometchat/chat-uikit-react-native';\nimport {CallMade, CallMissedOutgoingFill, CallReceived} from './icons';\nimport {JSX} from 'react';\nimport { getCometChatTranslation } from '@cometchat/chat-uikit-react-native';\nconst t = getCometChatTranslation();\n\ntype CallDirection = 'incoming' | 'outgoing';\n\nexport type CallStatus =\n  | 'incoming'\n  | 'outgoing'\n  | 'incomingCallEnded'\n  | 'outgoingCallEnded'\n  | 'cancelledByMe'\n  | 'cancelledByThem'\n  | 'incomingRejected'\n  | 'outgoingRejected'\n  | 'incomingBusy'\n  | 'outgoingBusy'\n  | 'unansweredByMe'\n  | 'unansweredByThem';\n\nexport class CallDetailHelper {\n  static getFormattedInitiatedAt = (call: any): string => {\n    const date = new Date(call.getInitiatedAt() * 1000);\n    const now = new Date();\n\n    // Extracting parts\n    const day = date.getDate();\n    const month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(\n      date,\n    );\n    const year = date.getFullYear();\n    const time = date.toLocaleTimeString('en-US', {\n      hour: 'numeric',\n      minute: '2-digit',\n      hour12: true,\n    });\n\n    // Determine if the year should be included\n    const includeYear = now.getFullYear() !== year;\n\n    return `${day} ${month}${includeYear ? `, ${year}` : ''}, ${time}`;\n  };\n\n  /** Returns the UI-facing callStatus plus the direction */\n  static getCallType = (\n    call: any,\n  ): {type: CallDirection; callStatus: CallStatus} => {\n    const myUid = CometChatUIKit.loggedInUser?.getUid();\n    const type: CallDirection =\n      call.getInitiator().getUid() === myUid ? 'outgoing' : 'incoming';\n\n    const statusMap: Record<\n      string,\n      {incoming: CallStatus; outgoing: CallStatus}\n    > = {\n      ended: {\n        incoming: 'incomingCallEnded',\n        outgoing: 'outgoingCallEnded',\n      },\n      rejected: {\n        incoming: 'incomingRejected',\n        outgoing: 'outgoingRejected',\n      },\n      cancelled: {\n        incoming: 'unansweredByMe',\n        outgoing: 'cancelledByMe',\n      },\n      unanswered: {\n        incoming: 'unansweredByMe',\n        outgoing: 'unansweredByThem',\n      },\n      initiated: {\n        incoming: 'incoming',\n        outgoing: 'outgoing',\n      },\n      busy: {\n        incoming: 'incomingBusy',\n        outgoing: 'outgoingBusy',\n      },\n    };\n\n    return {\n      type,\n      callStatus:\n        statusMap[call.getStatus() as keyof typeof statusMap]?.[type] ??\n        (type === 'incoming' ? 'incoming' : 'outgoing'),\n    };\n  };\n\n  /** Which SVG to render for a given callStatus */\n  static getCallStatusDisplayIcon = (\n    callStatus: CallStatus,\n    theme: CometChatTheme,\n  ): JSX.Element | undefined => {\n    const icons: Record<CallStatus, JSX.Element> = {\n      outgoing: <CallMade height={24} width={24} color={theme.color.success} />,\n      outgoingCallEnded: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      cancelledByMe: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      outgoingRejected: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      outgoingBusy: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      unansweredByThem: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n\n      incoming: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      incomingCallEnded: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      cancelledByThem: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n      incomingRejected: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      incomingBusy: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n      unansweredByMe: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n    };\n\n    return icons[callStatus];\n  };\n\n  static getCallStatusDisplayText = (callStatus: CallStatus): string => {\n    const labels: Record<CallStatus, string> = {\n      outgoing: t('OUTGOING_CALL'),\n      outgoingCallEnded: t('OUTGOING_CALL'),\n      cancelledByMe: t('OUTGOING_CALL'),\n      outgoingRejected: t('OUTGOING_CALL'),\n      outgoingBusy: t('OUTGOING_CALL'),\n      unansweredByThem: t('OUTGOING_CALL'),\n\n      incoming: t('INCOMING_CALL'),\n      incomingCallEnded: t('INCOMING_CALL'),\n      cancelledByThem: t('MISSED_CALL'),\n      incomingRejected: t('INCOMING_CALL'),\n      incomingBusy: t('MISSED_CALL'),\n      unansweredByMe: t('MISSED_CALL'),\n    };\n\n    return labels[callStatus];\n  };\n}\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/CallDetails.tsx",
    "content": "import React, {\n  JSX,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {View, TouchableOpacity, Text, TextStyle, ViewStyle} from 'react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatListItem,\n  useCometChatTranslation,\n  useTheme,\n  useLocalizedDate,\n  LocalizedDateHelper\n} from '@cometchat/chat-uikit-react-native';\nimport {CallHistory} from './CallHistory';\nimport {CallLogDetailHeader} from './CallLogDetailHeader';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CallParticipants} from './CallParticipants';\nimport {CallDetailHelper, CallStatus} from './CallDetailHelper';\nimport {CallRecordings} from './CallRecordings';\nimport {StackScreenProps} from '@react-navigation/stack';\nimport {ICONS} from '@cometchat/chat-uikit-react-native/src/shared/icons/icon-mapping';\nimport {RootStackParamList} from '../../navigation/types';\n\n\nconst listenerId = 'userListener_' + new Date().getTime();\nconst TABS = {\n  DETAILS: 'Details',\n  PARTICIPANTS: 'Participants',\n  RECORDINGS: 'Recordings',\n  HISTORY: 'History',\n};\n\ntype Props = StackScreenProps<RootStackParamList, 'CallDetails'>;\n\nexport const CallDetails: React.FC<Props> = ({route, navigation}) => {\n  const {call} = route.params;\n\n  const theme = useTheme();\n  const {t, language} = useCometChatTranslation()\n  const {formatDate}= useLocalizedDate()\n  const [group, setGroup] = useState<CometChat.Group | null>(null);\n  const [user, setUser] = useState<CometChat.User | null>(null);\n  const loggedInUser = useRef<CometChat.User | any>(null);\n  const [selectedTab, setSelectedTab] = useState(TABS.PARTICIPANTS);\n  const [tabStyle, setTableStyle] = useState<{\n    containerStyle: ViewStyle;\n    itemStyle: ViewStyle;\n    selectedItemStyle: ViewStyle;\n    itemEmojiStyle: TextStyle;\n    selectedItemEmojiStyle: TextStyle;\n    itemTextStyle: TextStyle;\n    selectedItemTextStyle: TextStyle;\n  }>();\n  const BackIcon = ICONS['arrow-back'];\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  useEffect(() => {\n    console.log('CALL RECEIVER: ', call);\n    CometChat.getLoggedinUser().then((loggedUser: CometChat.User | any) => {\n      loggedInUser.current = loggedUser;\n      let user =\n        call?.getReceiverType() == 'user'\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      let group =\n        call?.getReceiverType() == 'group'\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      if (user) {\n        CometChat.getUser(user.getUid()).then((userObject: CometChat.User) => {\n          setUser(userObject);\n        });\n        return;\n      }\n\n      CometChat.getGroup(group.getGuid()).then(\n        (groupObject: CometChat.Group) => {\n          setGroup(groupObject);\n        },\n      );\n    });\n  }, [call]);\n\n  useEffect(() => {\n    CometChat.addUserListener(\n      listenerId,\n      new CometChat.UserListener({\n        onUserOnline: (userDetails: any) => {\n          if (user?.getUid() === userDetails?.getUid()) {\n            setUser(userDetails);\n          }\n        },\n        onUserOffline: (userDetails: any) => {\n          if (user?.getUid() === userDetails?.getUid()) {\n            setUser(userDetails);\n          }\n        },\n      }),\n    );\n    return () => {\n      CometChat.removeUserListener(listenerId);\n    };\n  }, [user]);\n\n  useEffect(() => {\n    setTableStyle({\n      containerStyle: {\n        //flex: 1,\n        backgroundColor: theme.color.background1,\n        flexDirection: 'row',\n        //justifyContent: 'space-evenly', // ✅ Ensures even spacing\n        alignItems: 'center', // Optional, ensures vertical alignment\n        width: '100%', // ✅ Ensures full width for proper spacing\n        borderBottomWidth: 1,\n        borderColor: theme.color.borderDefault,\n        justifyContent: 'space-evenly',\n      },\n      itemStyle: {\n        // paddingHorizontal: theme.spacing.padding.p4,\n        paddingVertical: theme.spacing.padding.p2,\n        flexDirection: 'row',\n        borderBottomWidth: theme.spacing.spacing.s0_5,\n        borderBottomColor: 'transparent',\n        alignItems: 'center',\n        justifyContent: 'center',\n        flex: 1,\n      },\n      selectedItemStyle: {\n        // paddingHorizontal: theme.spacing.padding.p4,\n        paddingVertical: theme.spacing.padding.p2,\n        flexDirection: 'row',\n        borderBottomWidth: theme.spacing.spacing.s0_5,\n        borderBottomColor: theme.color.primary,\n        alignItems: 'center',\n        justifyContent: 'center',\n        flex: 1,\n      },\n      itemEmojiStyle: {\n        color: theme.color.textSecondary,\n        borderColor: 'transparent',\n        ...theme.typography.body.medium,\n      },\n      selectedItemEmojiStyle: {\n        color: theme.color.textSecondary,\n        borderColor: 'transparent',\n        ...theme.typography.body.medium,\n      },\n      itemTextStyle: {\n        color: theme.color.textSecondary,\n        marginLeft: theme.spacing.margin.m1,\n        ...theme.typography.body.medium,\n      },\n      selectedItemTextStyle: {\n        color: theme.color.primary,\n        marginLeft: theme.spacing.margin.m1,\n        ...theme.typography.body.medium,\n      },\n    });\n  }, [theme]);\n\n  const getUserToFetchHistory = useCallback(() => {\n    if (call?.getInitiator().getUid() === loggedInUser.current.getUid()) {\n      return call?.getReceiver();\n    }\n    call?.getInitiator();\n  }, [call]);\n\n \n  const callTypeAndStatus = useMemo(\n    (): {\n      type: 'incoming' | 'outgoing';\n      callStatus: CallStatus;\n    } => CallDetailHelper.getCallType(call),\n    [call],\n  );\n\n  /** Busy call toast: only when attempting a call and target is busy */\n  useEffect(() => {\n    const callListener = new CometChat.CallListener({\n      onOutgoingCallRejected: (rejectedCall: any) => {\n        try {\n          const status = rejectedCall?.getStatus?.() || rejectedCall?.status;\n          if (\n            status &&\n            status.toLowerCase() === CometChat.CALL_STATUS.BUSY.toLowerCase()\n          ) {\n            setToastMessage(t('CALL_BUSY'));\n            setTimeout(() => setToastMessage(null), 3000);\n          }\n        } catch {}\n      },\n    });\n    CometChat.addCallListener(listenerId, callListener);\n    return () => {\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [t]);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background2,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingRight: theme.spacing.padding.p4,\n          paddingLeft: theme.spacing.padding.p2,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          flex: 1,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.heading4.bold,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.caption1.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.bold,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n  const getFormattedInitiatedAt = useCallback(() => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [call]);\n\n  const callStatusDisplayString = useMemo(() => {\n    return CallDetailHelper.getCallStatusDisplayText(\n      callTypeAndStatus.callStatus,\n    );\n  }, [callTypeAndStatus]);\n\n  const CallStatusIcon = useMemo<JSX.Element | undefined>(\n    () =>\n      CallDetailHelper.getCallStatusDisplayIcon(\n        callTypeAndStatus.callStatus,\n        theme,\n      ),\n    [callTypeAndStatus, theme],\n  );\n\n  /** Local toast view component (shows only when toastMessage set; auto hides after 3s from effect) */\n  const ToastView = () => {\n    if (!toastMessage) return null;\n    return (\n      <View\n        style={{\n          alignSelf: 'center',\n          flexDirection: 'row',\n          alignItems: 'center',\n          borderRadius: 24,\n          backgroundColor: 'rgba(255,59,48,0.10)',\n          paddingVertical: theme.spacing.padding.p1,\n          paddingHorizontal: theme.spacing.padding.p2,\n          marginBottom: theme.spacing.margin.m3,\n          maxWidth: '80%',\n        }}>\n        <Icon name=\"info\" color={theme.color.error} size={18} />\n        <Text\n          style={[\n            theme.typography.caption1.bold,\n            {color: theme.color.error, marginLeft: theme.spacing.margin.m1},\n          ]}\n          accessibilityRole=\"alert\">\n          {toastMessage}\n        </Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      <View style={{flex: 1, paddingBottom: theme.spacing.padding.p3}}>\n        <View\n          style={{\n            flexDirection: 'row',\n            justifyContent: 'flex-start',\n            alignItems: 'center',\n            gap: theme.spacing.padding.p2,\n            paddingVertical: theme.spacing.padding.p2,\n            paddingHorizontal: theme.spacing.padding.p5,\n            minHeight: 64,\n          }}>\n          <TouchableOpacity onPress={() => navigation.goBack()}>\n            <Icon\n              imageStyle={{\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconPrimary,\n              }}\n              icon={\n                <BackIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.iconPrimary}></BackIcon>\n              }\n            />\n          </TouchableOpacity>\n          <Text\n            style={{\n              color: theme.color.textPrimary,\n              ...theme.typography.heading1.bold,\n              paddingTop: 35 - (35 * 0.75),\n            }}>\n            {t('CALL_DETAILS')}\n          </Text>\n        </View>\n\n        {(user || group) && (\n          <View style={{flexDirection: 'column'}}>\n            <CallLogDetailHeader\n              {...(user && {user})}\n              {...(group && {group})}></CallLogDetailHeader>\n            <View\n              style={{\n                flexDirection: 'row',\n                width: '100%',\n                alignItems: 'center',\n                backgroundColor: theme.color.background2,\n              }}>\n              <Icon\n                icon={CallStatusIcon}\n                containerStyle={{marginLeft: theme.spacing.margin.m4}}></Icon>\n              <CometChatListItem\n                id={call.sessionId}\n                avatarStyle={_style.itemStyle.avatarStyle}\n                containerStyle={_style.itemStyle.containerStyle}\n                headViewContainerStyle={{flexDirection: 'row'}}\n                titleStyle={_style.itemStyle.titleStyle}\n                title={callStatusDisplayString}\n                trailingViewContainerStyle={{\n                  alignSelf: 'center',\n                }}\n                SubtitleView={\n                  <Text style={_style.itemStyle.subtitleStyle}>\n                    {getFormattedInitiatedAt()}\n                  </Text>\n                }\n                TrailingView={\n                  <Text style={_style.itemStyle.tailViewTextStyle}>\n                    {convertMinutesToTime(call.getTotalDurationInMinutes())}\n                  </Text>\n                }\n              />\n            </View>\n\n            <View\n              style={{\n                flexDirection: 'row',\n                alignItems: 'center',\n                width: '100%',\n                borderBottomWidth: 1,\n                borderColor: theme.color.borderDefault,\n                justifyContent: 'space-evenly',\n                paddingHorizontal: 0,\n              }}>\n              {[\n                { key: TABS.PARTICIPANTS, title: t('PARTICIPANT') },\n                { key: TABS.RECORDINGS, title: t('RECORDING') },\n                { key: TABS.HISTORY, title: t('HISTORY') }\n              ].map((tab) => (\n                <TouchableOpacity\n                  key={tab.key}\n                  style={\n                    selectedTab === tab.key\n                      ? tabStyle!.selectedItemStyle\n                      : tabStyle!.itemStyle\n                  }\n                  onPress={() => setSelectedTab(tab.key)}>\n                  <Text\n                    style={\n                      selectedTab === tab.key\n                        ? tabStyle!.selectedItemTextStyle\n                        : tabStyle!.itemTextStyle\n                    }>\n                    {tab.title}\n                  </Text>\n                </TouchableOpacity>\n              ))}\n            </View>\n            {selectedTab === TABS.PARTICIPANTS && (\n              <CallParticipants\n                call={call}\n                data={call?.getParticipants()}></CallParticipants>\n            )}\n            {selectedTab === TABS.HISTORY && (\n              <CallHistory user={getUserToFetchHistory()}></CallHistory>\n            )}\n            {selectedTab === TABS.RECORDINGS &&\n              call.getRecordings()?.length && (\n                <CallRecordings call={call}></CallRecordings>\n              )}\n          </View>\n        )}\n      </View>\n      <View style={{width: '100%', paddingBottom: theme.spacing.padding.p3}}>\n        <ToastView />\n      </View>\n    </View>\n  );\n};\n\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/CallHistory.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CallingPackage,\n  CometChatListItem,\n  useTheme,\n  useCometChatTranslation,\n  useLocalizedDate,\n  LocalizedDateHelper\n} from '@cometchat/chat-uikit-react-native';\nimport React, { JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, FlatList, Text, View } from 'react-native';\nimport { CallDetailHelper } from './CallDetailHelper';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\nexport const CallHistory = (props: { user?: any; group?: any }) => {\n  const { user, group } = props;\n\n  const theme = useTheme();\n  const { language, t } = useCometChatTranslation();\n  const { formatDate } = useLocalizedDate();\n\n  const [list, setList] = useState<any[]>([]);\n  const [loading, setLoading] = useState<boolean>(true);\n  const [isFetchingMore, setIsFetchingMore] = useState<boolean>(false);\n\n  const loggedInUser = useRef<CometChat.User | any>(null);\n  const callRequestBuilderRef = useRef<any>(null);\n\n  function setRequestBuilder() {\n    callRequestBuilderRef.current;\n    let builder = new CometChatCalls.CallLogRequestBuilder()\n      .setLimit(30)\n      .setAuthToken(loggedInUser.current?.getAuthToken() || '')\n      .setCallCategory('call');\n    if (user) {\n      builder = builder.setUid(user?.getUid());\n    } else if (group) {\n      builder = builder.setGuid(group?.getGuid());\n    }\n    callRequestBuilderRef.current = builder.build();\n  }\n\n  const fetchCallLogHistory = () => {\n    if (!callRequestBuilderRef.current || isFetchingMore) {\n      return;\n    }\n    setIsFetchingMore(true);\n    callRequestBuilderRef.current\n      .fetchNext()\n      .then((CallLogHistory: any) => {\n        if (CallLogHistory.length > 0) {\n          setList(prev => [...prev, ...CallLogHistory]);\n        }\n      })\n      .catch((err: any) => {\n        // onError && onError(err);\n      })\n      .finally(() => {\n        setLoading(false);\n        setIsFetchingMore(false);\n      });\n  };\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: any) => {\n        loggedInUser.current = u;\n        setRequestBuilder();\n        fetchCallLogHistory();\n      })\n      .catch((e: any) => {\n        // onError && onError(e);\n        setLoading(false);\n      });\n  }, []);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          marginHorizontal: theme.spacing.margin.m4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          flex: 1,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.bold,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.caption1.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.bold,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  // Updated to use proper localization\n  const getFormattedInitiatedAt = useCallback((call: any) => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    // Use localizedDateHelper to format the date with proper localization\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [formatDate]);\n\n  const getCallType = useCallback((call: any) => {\n    return CallDetailHelper.getCallType(call);\n  }, []);\n\n  const getCallStatusIcon = useCallback((item: any): JSX.Element => {\n    const CallStatusIcon = CallDetailHelper.getCallStatusDisplayIcon(\n      getCallType(item).callStatus,\n      theme,\n    );\n    return CallStatusIcon || <View />;\n  }, []);\n\n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n  \n\n  // Added to get localized call status text\n  const getCallStatusText = useCallback((callStatus: any) => {\n    // Get translation key for this status\n    const translationKeys: Record<string, string> = {\n      outgoing: 'OUTGOING_CALL',\n      outgoingCallEnded: 'OUTGOING_CALL',\n      cancelledByMe: 'OUTGOING_CALL',\n      outgoingRejected: 'OUTGOING_CALL',\n      outgoingBusy: 'OUTGOING_CALL',\n      unansweredByThem: 'OUTGOING_CALL',\n      incoming: 'INCOMING_CALL',\n      incomingCallEnded: 'INCOMING_CALL',\n      cancelledByThem: 'MISSED_CALL',\n      incomingRejected: 'INCOMING_CALL',\n      incomingBusy: 'MISSED_CALL',\n      unansweredByMe: 'MISSED_CALL',\n    };\n\n    const key = translationKeys[callStatus] || 'UNKNOWN_CALL';\n    return t(key);\n  }, [t]);\n\n  const _render = ({ item, index }: any) => {\n    return (\n      <React.Fragment key={index}>\n        <View\n          style={{\n            flexDirection: 'row',\n            width: '100%',\n            alignItems: 'center',\n          }}>\n          <Icon\n            icon={getCallStatusIcon(item)}\n            containerStyle={{ marginLeft: theme.spacing.margin.m4 }}></Icon>\n          <CometChatListItem\n            id={item.sessionId}\n            containerStyle={_style.itemStyle.containerStyle}\n            headViewContainerStyle={{ flexDirection: 'row' }}\n            trailingViewContainerStyle={{\n              alignSelf: 'center',\n            }}\n            titleStyle={_style.itemStyle.titleStyle}\n           title={CallDetailHelper.getCallStatusDisplayText(\n              getCallType(item).callStatus,\n            )}\n            SubtitleView={\n              <Text\n                style={{\n                  ...theme.typography.body.regular,\n                  color: theme.color.textSecondary,\n                }}>\n                {getFormattedInitiatedAt(item)}\n              </Text>\n            }\n            TrailingView={\n              <Text style={_style.itemStyle.tailViewTextStyle}>\n              {convertMinutesToTime(item.getTotalDurationInMinutes())}\n              </Text>\n            }\n          />\n        </View>\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <FlatList\n      data={list}\n      keyExtractor={(item, index) => item.sessionId + '_' + index}\n      renderItem={_render}\n      onEndReached={fetchCallLogHistory}\n      onEndReachedThreshold={0.5}\n      ListEmptyComponent={\n        loading ? (\n          <View\n            style={{\n              flex: 1,\n              height: 300,\n              justifyContent: 'center',\n              alignItems: 'center',\n            }}\n          >\n            <ActivityIndicator size=\"large\" color={theme.color.primary} />\n          </View>\n        ) : null\n      }\n    />\n  );\n};\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/CallLogDetailHeader.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  useTheme,\n  CometChatAvatar,\n  CometChatCallButtons,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  GroupTypeConstants,\n  UserStatusConstants,\n} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport {CometChatCompThemeProvider} from '@cometchat/chat-uikit-react-native/src/theme/provider';\nimport React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';\nimport {Text, View} from 'react-native';\nimport { useConfig } from '../../config/store';\nexport type CallLogDetailHeaderInterface = {\n  user?: CometChat.User;\n  /**\n   *\n   * @type {CometChat.Group}\n   *   To pass group object\n   */\n  group?: CometChat.Group;\n};\n\nexport const CallLogDetailHeader = (props: CallLogDetailHeaderInterface) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const {user, group} = props;\n\n  const [groupObj, setGroupObj] = useState(group);\n  const [userStatus, setUserStatus] = useState(\n    user &&\n      !(user.getBlockedByMe() || user.getHasBlockedMe()) &&\n      user?.getStatus\n      ? user?.getStatus()\n      : '',\n  );\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n  const receiverTypeRef = useRef(\n    user\n      ? CometChat.RECEIVER_TYPE.USER\n      : group\n        ? CometChat.RECEIVER_TYPE.GROUP\n        : null,\n  );\n\n  useEffect(() => {\n    setGroupObj(group);\n  }, [group]);\n\n  useEffect(() => {\n    setUserStatus(\n      user && !(user.getBlockedByMe() || user.getHasBlockedMe())\n        ? user?.getStatus()\n        : '',\n    );\n  }, [user]);\n\n  const messageHeaderStyles = useMemo(() => {\n    return theme.messageHeaderStyles;\n  }, [theme.messageHeaderStyles]);\n\n  const statusIndicatorType = useMemo(() => {\n    if (groupObj?.getType() === GroupTypeConstants.password) {\n      return 'protected';\n    } else if (groupObj?.getType() === GroupTypeConstants.private) {\n      return 'private';\n    } else if (userStatus === 'online') {\n      return 'online';\n    }\n    return '';\n  }, [userStatus, groupObj]);\n\n  const AvatarWithStatusView = useCallback(() => {\n    return (\n      <View>\n        <CometChatAvatar\n          image={\n            user\n              ? user.getAvatar()\n                ? {uri: user.getAvatar()}\n                : undefined\n              : groupObj\n                ? groupObj.getIcon()\n                  ? {uri: groupObj.getIcon()}\n                  : undefined\n                : undefined\n          }\n          name={user?.getName() || groupObj?.getName() || ''}\n        />\n      </View>\n    );\n  }, [user, groupObj, statusIndicatorType]);\n\n  const SubtitleViewFnc = () => {\n    const statusTytle =\n      receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&\n      (groupObj?.['membersCount'] || groupObj?.['membersCount'] === 0)\n        ? `${groupObj['membersCount']} ${t('MEMBERS')}`\n        : receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER\n          ? userStatus === UserStatusConstants.online\n            ? t('ONLINE')\n            : userStatus === UserStatusConstants.offline\n              ? t('OFFLINE')\n              : ''\n          : '';\n\n    if(!statusTytle) return <></>;\n    return (\n      <Text\n        style={{\n          ...theme.typography.caption1.regular,\n          color: theme.color.textSecondary,\n        }}>\n        {statusTytle}\n      </Text>\n    );\n  };\n\n  return (\n    <CometChatCompThemeProvider\n      theme={{\n        callButtonStyles: messageHeaderStyles.callButtonStyle,\n        avatarStyle: messageHeaderStyles.avatarStyle,\n        statusIndicatorStyle: messageHeaderStyles.statusIndicatorStyle,\n      }}>\n      <View\n        style={{\n          paddingVertical: theme.spacing.padding.p5,\n          paddingHorizontal: theme.spacing.padding.p4,\n          backgroundColor: theme.color.background1,\n          flexDirection: 'row',\n          gap: theme.spacing.padding.p3,\n          alignItems: 'center',\n          borderTopWidth: 1,\n          borderBottomWidth: 1,\n          borderColor: theme.color.borderLight,\n        }}>\n        <AvatarWithStatusView />\n        <View style={{flex: 1}}>\n          <Text\n            numberOfLines={1}\n            ellipsizeMode=\"tail\"\n            style={{\n              ...theme.typography.heading4.medium,\n              color: theme.color.textPrimary,\n            }}>\n            {user ? user.getName() : groupObj ? groupObj.getName() : ''}\n          </Text>\n          {<SubtitleViewFnc />}\n        </View>\n        <View style={{marginLeft: 'auto'}}>\n          <CometChatCallButtons\n            user={user}\n            group={group}\n            hideVoiceCallButton={\n              (user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)\n            }\n            hideVideoCallButton={\n              (user && !oneOnOneVideoCalling) || (group && !groupVideoConference)\n            }\n            style={{\n              containerStyle: {\n                gap: 10,\n              },\n              audioCallButtonIconStyle: {\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconHighlight,\n              },\n              videoCallButtonIconStyle: {\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconHighlight,\n              },\n              audioCallButtonIconContainerStyle: {\n                height: 40,\n                width: 64,\n                paddingVertical: theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p5,\n                borderWidth: 1,\n                borderRadius: theme.spacing.radius.r2,\n                borderColor: theme.color.borderDefault,\n              },\n              videoCallButtonIconContainerStyle: {\n                height: 40,\n                width: 64,\n                paddingVertical: theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p5,\n                borderWidth: 1,\n                borderRadius: theme.spacing.radius.r2,\n                borderColor: theme.color.borderDefault,\n              },\n            }}\n          />\n        </View>\n      </View>\n    </CometChatCompThemeProvider>\n  );\n};\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/CallParticipants.tsx",
    "content": "import { CometChatListItem, useTheme, useCometChatTranslation, localizedDateHelperInstance, useLocalizedDate, LocalizedDateHelper } from '@cometchat/chat-uikit-react-native';\nimport React, { useCallback, useMemo } from 'react';\nimport { View, FlatList, Text } from 'react-native';\nimport { CallDetailHelper } from './CallDetailHelper';\n\nexport const CallParticipants = (props: {\n  /**\n   * Participant list\n   */\n  data: any[];\n  call: any;\n}) => {\n  const { call, data } = props;\n\n  const theme = useTheme();\n  const { language, t } = useCometChatTranslation();\n  const { formatDate } = useLocalizedDate();\n\n  const getCallDetails = (item: any) => {\n    return {\n      title: item['name'],\n      avatarUrl: item['avatar'],\n    };\n  };\n\n  // Updated to use proper localization\n  const formattedInitiatedAt = useMemo(() => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    // Use formatDate for proper localization of date/time\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [call, formatDate]);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingHorizontal: theme.spacing.padding.p4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.body.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.medium,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n  \n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n \n\n  const _render = ({ item, index }: any) => {\n    const { title, avatarUrl } = getCallDetails(item);\n\n    return (\n      <React.Fragment key={index}>\n        <CometChatListItem\n          id={item.sessionId}\n          avatarStyle={_style.itemStyle.avatarStyle}\n          containerStyle={_style.itemStyle.containerStyle}\n          headViewContainerStyle={{ flexDirection: 'row' }}\n          titleStyle={_style.itemStyle.titleStyle}\n          trailingViewContainerStyle={{\n            alignSelf: 'center',\n          }}\n          SubtitleView={\n            <Text\n              style={{\n                ...theme.typography.body.regular,\n                color: theme.color.textSecondary,\n              }}>\n              {formattedInitiatedAt}\n            </Text>\n          }\n          // avatarName={title}\n          title={title}\n          // subtitle={'8 August, 8:14 pm'}\n          avatarURL={avatarUrl}\n          TrailingView={\n            <Text style={_style.itemStyle.tailViewTextStyle}>\n              {convertMinutesToTime(item.getTotalDurationInMinutes())}\n            </Text>\n          }\n        />\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <View style={{ backgroundColor: theme.color.background1 }}>\n      {data.length && (\n        <FlatList\n          data={data}\n          keyExtractor={(item, index) => item.sessionId + '_' + index}\n          renderItem={_render}\n        />\n      )}\n    </View>\n  );\n};"
  },
  {
    "path": "examples/SampleApp/src/components/calls/CallRecordings.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {useTheme, CometChatListItem} from '@cometchat/chat-uikit-react-native';\nimport React, {useRef, useEffect, useMemo, useState} from 'react';\nimport {\n  View,\n  FlatList,\n  Text,\n  Pressable,\n  NativeEventEmitter,\n  NativeModules,\n  Platform,\n} from 'react-native';\nimport {PlayArrow} from './icons';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CallDetailHelper} from './CallDetailHelper';\n\nconst {FileManager} = NativeModules;\nconst eventEmitter = new NativeEventEmitter(FileManager);\n\nexport const CallRecordings = (props: {call: any}) => {\n  const {call} = props;\n  const [data, setData] = useState(call.getRecordings());\n\n  const theme = useTheme();\n\n  const loggedInUser = useRef(null);\n  const downloadIdRef = useRef(0);\n  const [processing, setProcessing] = React.useState(false);\n  const [fileExists, setFileExists] = React.useState(false); // State to track if the file exists\n  let listener: any = useRef(undefined);\n  const [fileUrl, setFileUrl] = useState();\n  const [reOpenCount, setReopenCount] = useState(0);\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: any) => {\n        loggedInUser.current = u;\n      })\n      .catch((e: any) => {\n        // onError && onError(e);\n      });\n  }, []);\n\n  useEffect(() => {\n    if (Platform.OS == 'android') {\n      listener.current = eventEmitter.addListener(\n        'downloadComplete',\n        (data: {downloadId: number}) => {\n          if (\n            data.downloadId &&\n            downloadIdRef.current &&\n            data.downloadId == downloadIdRef.current\n          ) {\n            setProcessing(false);\n            setFileExists(true);\n            openFile(fileUrl);\n          }\n        },\n      );\n    }\n    return () => {\n      if (Platform.OS == 'android') {\n        listener.current.remove();\n      }\n    };\n  }, []);\n\n  useEffect(() => {\n    if (!fileUrl) return;\n\n    const fileName = getFileName(fileUrl);\n    setProcessing(true);\n    FileManager.doesFileExist(fileName, (result: string) => {\n      setProcessing(false);\n      if (JSON.parse(result).exists) {\n        setFileExists(true);\n        openFile(fileUrl);\n      } else {\n        downloadFile(fileUrl);\n      }\n    });\n  }, [fileUrl, reOpenCount]);\n\n  const downloadFile = (fileUrl: any) => {\n    if (processing || fileExists) return; // Do not process if file already exists\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.checkAndDownload(\n      fileUrl,\n      getFileName(fileUrl),\n      (result: string) => {\n        if (Platform.OS == 'ios') {\n          let parsedResult = JSON.parse(result);\n          if (parsedResult.success == true) {\n            setProcessing(false);\n            setFileExists(true);\n          }\n          openFile(fileUrl);\n        } else if (Platform.OS == 'android') {\n          downloadIdRef.current = JSON.parse(result).downloadId;\n        }\n      },\n    );\n  };\n\n  const openFile = (fileUrl: any) => {\n    if (processing) return;\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.openFileWithOption(getFileName(fileUrl), (isOpened: string) => {\n      setProcessing(false);\n    });\n  };\n\n  const getFileName = (fileUrl: any) => {\n    return fileUrl.substring(fileUrl.lastIndexOf('/') + 1).replace(' ', '_');\n  };\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingHorizontal: theme.spacing.padding.p4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          minHeight: 72,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.body.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.medium,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  const formattedInitiatedAt = useMemo(() => {\n    return CallDetailHelper.getFormattedInitiatedAt(call);\n  }, [call]);\n\n  const _render = ({item, index}: any) => {\n    const title = item['rid'];\n\n    return (\n      <React.Fragment key={index}>\n        <CometChatListItem\n          id={item.sessionId}\n          title={title}\n          SubtitleView={\n            <Text\n              style={{\n                ...theme.typography.body.regular,\n                color: theme.color.textSecondary,\n              }}>\n              {formattedInitiatedAt}\n            </Text>\n          }\n          titleStyle={_style.itemStyle.titleStyle}\n          containerStyle={_style.itemStyle.containerStyle}\n          trailingViewContainerStyle={{\n            alignSelf: 'center',\n          }}\n          TrailingView={\n            <Pressable\n              onPress={() => {\n                setFileUrl(item.getRecordingURL());\n                setReopenCount(reOpenCount + 1);\n              }}>\n              <Icon\n                icon={\n                  <PlayArrow\n                    height={24}\n                    width={24}\n                    color={theme.color.iconHighlight}></PlayArrow>\n                }\n                containerStyle={{marginLeft: theme.spacing.margin.m4}}></Icon>\n            </Pressable>\n          }\n        />\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <View style={{backgroundColor: theme.color.background1}}>\n      {data.length && (\n        <FlatList\n          data={data}\n          keyExtractor={(item, index) => item.sessionId + '_' + index}\n          renderItem={_render}\n        />\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/Calls.tsx",
    "content": "import {CometChatCallLogs, useTheme, Icon} from '@cometchat/chat-uikit-react-native';\nimport {useFocusEffect, useNavigation} from '@react-navigation/native';\nimport React, {useCallback} from 'react';\nimport {View, TouchableOpacity} from 'react-native';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../navigation/types';\nimport { useConfig } from '../../config/store';\n\ntype CallNavigationProp = StackNavigationProp<RootStackParamList, 'CallLogs'>;\n\nconst Calls: React.FC = () => {\n  const [shouldHide, setShouldHide] = React.useState(false);\n  const navigation = useNavigation<CallNavigationProp>();\n\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  const theme = useTheme();\n  const onItemPress = (item: any) => {\n    navigation.navigate('CallDetails', {\n      call: item,\n    });\n  };\n  // Create TrailingView that respects visibility configuration\n  const TrailingView = useCallback((call?: any, onPress?: (call: any) => void) => {\n    if (!call || !onPress) return (<></>);\n    const receiverType = call?.getReceiverType?.();\n    const callType = call?.getType?.();\n    const isUser = receiverType === 'user';\n    const isAudio = callType === 'audio';\n\n    // Check if button should be hidden based on configuration\n    const shouldHide =\n      (isUser && isAudio && !oneOnOneVoiceCalling) ||\n      (isUser && !isAudio && !oneOnOneVideoCalling) ||\n      (!isUser && isAudio && !groupVoiceConference) ||\n      (!isUser && !isAudio && !groupVideoConference);\n\n    if (shouldHide) {\n      return <View />;\n    }\n\n    // Return call button - styling matches CometChatCallLogs default button\n    return (\n      <TouchableOpacity\n        onPress={() => onPress(call)}\n        style={{\n          marginLeft: \"auto\",\n        }}\n      >\n        <Icon\n          name={call.type === \"audio\" ? \"call\" : \"videocam\"}\n          size={24}\n          color={theme.callLogsStyles.itemStyle.callIconStyle.tintColor}\n          imageStyle={theme.callLogsStyles.itemStyle.callIconStyle}\n        />\n      </TouchableOpacity>\n    );\n  }, [oneOnOneVoiceCalling, oneOnOneVideoCalling, groupVoiceConference, groupVideoConference, theme]);\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {!shouldHide && (\n        <CometChatCallLogs \n          onItemPress={onItemPress}\n          TrailingView={TrailingView}\n        />\n      )}\n    </View>\n  );\n};\n\nexport default Calls;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-end-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 8.25q2.864 0 5.649 1.149a14.15 14.15 0 0 1 4.876 3.332q.308.31.314.724a.95.95 0 0 1-.305.714l-1.905 1.856q-.305.294-.681.329a1 1 0 0 1-.696-.2l-2.516-1.912a1.3 1.3 0 0 1-.367-.417 1.1 1.1 0 0 1-.12-.517v-2.822a15 15 0 0 0-2.113-.552A12 12 0 0 0 12 9.75q-1.107 0-2.137.184-1.029.186-2.113.553v2.82q0 .289-.12.518-.118.229-.367.417l-2.515 1.912a1 1 0 0 1-.696.2 1.1 1.1 0 0 1-.681-.329l-1.906-1.856a.95.95 0 0 1-.305-.714 1 1 0 0 1 .315-.724 14 14 0 0 1 4.868-3.332Q9.136 8.25 12 8.25'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-end.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.517 16.3 1.2 14.001a.74.74 0 0 1-.234-.551.84.84 0 0 1 .233-.567 13.8 13.8 0 0 1 4.917-3.587A14.7 14.7 0 0 1 12 8.066q3.045 0 5.88 1.23 2.833 1.23 4.92 3.587.229.255.233.567a.74.74 0 0 1-.235.551L20.5 16.3a.86.86 0 0 1-.573.24.86.86 0 0 1-.595-.158L16.5 14.254a.8.8 0 0 1-.237-.275.8.8 0 0 1-.08-.358v-3.27a11.3 11.3 0 0 0-2.077-.534 13.4 13.4 0 0 0-2.09-.167q-1.05 0-2.11.167-1.059.165-2.073.533v3.267a.8.8 0 0 1-.077.352.74.74 0 0 1-.24.281l-2.852 2.137q-.286.21-.598.183a.84.84 0 0 1-.55-.27m2.65-5.3q-.887.442-1.715 1.063-.826.62-1.602 1.32l1.317 1.35 2-1.5zm11.6-.05v2.183l2.066 1.584 1.334-1.317a12 12 0 0 0-1.619-1.367 17 17 0 0 0-1.781-1.083'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-log-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.44 21.5q-2.827 0-5.68-1.314t-5.242-3.709-3.703-5.242T2.5 5.56A1.03 1.03 0 0 1 3.55 4.5h3.262q.378 0 .668.247.289.247.368.61L8.421 8.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M12.75 4a.73.73 0 0 1-.534-.216A.73.73 0 0 1 12 3.25q0-.32.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 20.75 4zm0 3.692a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.319.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215zm0 3.692a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .319-.216.534a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-log.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.75 21.833q-2.893 0-5.863-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.457.31-.768.31-.31.773-.31h3.567q.35 0 .598.235T7.749 5l.666 3.194q.043.335-.026.616a1 1 0 0 1-.26.477L5.704 11.75q.603 1.041 1.304 1.953.702.91 1.546 1.735a17 17 0 0 0 1.818 1.626A16 16 0 0 0 12.4 18.4l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M4.92 10.283l1.9-1.916-.537-2.617H3.77q.059 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521zm-.842-15.4a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.234.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-made-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17 8.054 6.083 18.973a.72.72 0 0 1-.522.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.217-.527L15.947 7H9.751a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.535a.73.73 0 0 1 .535-.215h7.846q.383 0 .644.26.26.26.26.644v7.846q0 .319-.216.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-made.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.245 7.854 5.83 19.284a.73.73 0 0 1-.554.235.8.8 0 0 1-.559-.244.75.75 0 0 1 .002-1.106L16.133 6.75H9.766a.75.75 0 0 1-.56-.234.78.78 0 0 1-.227-.56q0-.327.227-.558a.76.76 0 0 1 .56-.231h8.267q.333 0 .565.232a.76.76 0 0 1 .23.555v8.267q0 .333-.231.565a.77.77 0 0 1-.567.23.75.75 0 0 1-.56-.23.78.78 0 0 1-.225-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-missed-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5 10.021v4.181a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V8.356q0-.387.259-.645a.88.88 0 0 1 .645-.259h5.846q.32 0 .534.215a.73.73 0 0 1 .216.535.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215H6.054l5.773 5.773a.3.3 0 0 0 .221.087.3.3 0 0 0 .221-.087l6.66-6.66a.7.7 0 0 1 .522-.22q.3.003.532.236.217.232.225.527a.7.7 0 0 1-.225.527l-6.654 6.654q-.27.27-.608.401a1.9 1.9 0 0 1-.673.131q-.337 0-.673-.13a1.75 1.75 0 0 1-.608-.402z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-missed-outgoing-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m19 10.021-5.767 5.768q-.272.27-.608.401a1.8 1.8 0 0 1-.673.131q-.336 0-.673-.13a1.75 1.75 0 0 1-.608-.402L4.017 9.135a.72.72 0 0 1-.212-.515.75.75 0 0 1 .212-.54.74.74 0 0 1 .535-.232q.302 0 .535.233l6.644 6.644a.3.3 0 0 0 .221.087q.134 0 .221-.087l5.773-5.773H13.75a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.32.216-.535a.73.73 0 0 1 .534-.215h5.846q.387 0 .645.259a.88.88 0 0 1 .259.645v5.846a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-missed-outgoing.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m19.25 9.942-6.224 6.224a1.5 1.5 0 0 1-.52.345q-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.522-.346L3.702 9.06a.78.78 0 0 1-.237-.552.78.78 0 0 1 .222-.565.82.82 0 0 1 1.151.003l7.08 7.087L18.2 8.75H13.93a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.562q0-.33.231-.56a.77.77 0 0 1 .565-.23h6.116q.324 0 .556.233.231.232.232.555v6.234q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.23-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-missed.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.751 9.942v4.246q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.229-.565V7.954a.76.76 0 0 1 .231-.555.77.77 0 0 1 .565-.232h6.117q.323 0 .555.233.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.555.23H5.8l6.284 6.282 7.11-7.11a.72.72 0 0 1 .557-.227.86.86 0 0 1 .568.25.8.8 0 0 1 .219.562.77.77 0 0 1-.238.55l-7.107 7.107q-.238.24-.52.346-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.521-.345z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-received-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.404 18.5a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V9.75q0-.318.216-.534A.73.73 0 0 1 6.25 9q.32 0 .535.216A.73.73 0 0 1 7 9.75v6.196L17.92 5.027a.72.72 0 0 1 .521-.212.7.7 0 0 1 .532.212q.217.217.217.527a.72.72 0 0 1-.217.527L8.053 17h6.197q.319 0 .534.215a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call-received.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.967 18.833a.76.76 0 0 1-.56-.231.78.78 0 0 1-.227-.565V9.771q0-.325.227-.557a.76.76 0 0 1 .563-.23q.334 0 .564.23.23.232.229.557v6.366L18.18 4.717a.7.7 0 0 1 .545-.236.8.8 0 0 1 .559.24.76.76 0 0 1 .241.554q0 .312-.241.546L7.867 17.25h6.367q.333 0 .564.232a.76.76 0 0 1 .232.558q0 .334-.232.564a.77.77 0 0 1-.564.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/cancel-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 13.054 3.073 3.073q.208.208.522.213a.7.7 0 0 0 .532-.213.72.72 0 0 0 .217-.527.72.72 0 0 0-.217-.527L13.054 12l3.073-3.073a.73.73 0 0 0 .213-.522.7.7 0 0 0-.213-.532.72.72 0 0 0-.527-.217.72.72 0 0 0-.527.217L12 10.946 8.927 7.873a.73.73 0 0 0-.522-.213.7.7 0 0 0-.532.213.72.72 0 0 0-.217.527q0 .31.217.527L10.946 12l-3.073 3.073a.73.73 0 0 0-.213.522.7.7 0 0 0 .213.532q.217.217.527.217a.72.72 0 0 0 .527-.217zm.002 8.446a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/index.tsx",
    "content": "export {default as Call} from './call';\nexport {default as CallEnd} from './call-end';\nexport {default as CallEndFill} from './call-end-fill';\nexport {default as CallFill} from './call-fill';\nexport {default as CallLog} from './call-log';\nexport {default as CallLogFill} from './call-log-fill';\nexport {default as CallMade} from './call-made';\nexport {default as CallMadeFill} from './call-made-fill';\nexport {default as CallMissed} from './call-missed';\nexport {default as CallMissedFill} from './call-missed-fill';\nexport {default as CallMissedOutgoing} from './call-missed-outgoing';\nexport {default as CallMissedOutgoingFill} from './call-missed-outgoing-fill';\nexport {default as CallReceived} from './call-received';\nexport {default as CallReceivedFill} from './call-received-fill';\nexport {default as CancelFill} from './cancel-fill';\nexport {default as PlayArrow} from './play-arrow';\n"
  },
  {
    "path": "examples/SampleApp/src/components/calls/icons/play-arrow.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M8.117 17.313V6.578a.73.73 0 0 1 .24-.57.8.8 0 0 1 .56-.216q.096 0 .206.022a.7.7 0 0 1 .21.08l8.438 5.393q.18.133.273.298t.094.365a.7.7 0 0 1-.094.365.9.9 0 0 1-.273.29l-8.437 5.393a.6.6 0 0 1-.212.087 1 1 0 0 1-.201.023.81.81 0 0 1-.564-.216.74.74 0 0 1-.24-.579m1.583-1.43 6.184-3.933L9.7 8.017z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/helper/GroupListeners.ts",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUIKit} from '@cometchat/chat-uikit-react-native';\n\nexport const listners = {\n  addListener: {\n    groupListener: ({groupListenerId, handleGroupListener}: any) =>\n      CometChat.addGroupListener(\n        groupListenerId,\n        new CometChat.GroupListener({\n          onGroupMemberKicked: (\n            message: any,\n            kickedUser: any,\n            kickedBy: any,\n            kickedFrom: any,\n          ) => {\n            handleGroupListener(kickedFrom);\n          },\n          onGroupMemberBanned: (\n            message: any,\n            bannedUser: any,\n            bannedBy: any,\n            bannedFrom: any,\n          ) => {\n            handleGroupListener(bannedFrom);\n          },\n          onMemberAddedToGroup: (\n            message: any,\n            userAdded: any,\n            userAddedBy: any,\n            userAddedIn: any,\n          ) => {\n            handleGroupListener(userAddedIn);\n          },\n          onGroupMemberLeft: (message: any, leavingUser: any, group: any) => {\n            handleGroupListener(group);\n          },\n          onGroupMemberScopeChanged: (\n            message: any,\n            changedUser: CometChat.User,\n            newScope: any,\n            oldScope: any,\n            changedGroup: CometChat.Group,\n          ) => {\n            console.log('changedGroup: ', message, changedGroup);\n            if (changedUser.getUid() == CometChatUIKit.loggedInUser!.getUid()) {\n              changedGroup.setScope(newScope);\n              handleGroupListener(changedGroup);\n            }\n          },\n        }),\n      ),\n  },\n  removeListner: {\n    removeUserListener: ({userStatusListenerId}: any) =>\n      CometChat.removeUserListener(userStatusListenerId),\n\n    removeGroupListener: ({groupListenerId}: any) =>\n      CometChat.removeGroupListener(groupListenerId),\n  },\n};\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/AddMember.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  NativeModules,\n  Platform,\n  KeyboardAvoidingView,\n  BackHandler,\n} from 'react-native';\nimport {\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  CometChatUsers,\n  CometChatUsersActionsInterface,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport {\n  useRoute,\n  useNavigation,\n  RouteProp,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport { styles } from './AddMemberStyles';\nimport { commonVars } from '@cometchat/chat-uikit-react-native/src/shared/base/vars';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { useConfig } from '../../../config/store';\nconst { CommonUtil } = NativeModules;\n\nconst AddMember: React.FC = () => {\n  const route = useRoute<RouteProp<RootStackParamList, 'AddMember'>>();\n  const navigation = useNavigation();\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const userRef = useRef<CometChatUsersActionsInterface>(null);\n  const [selectedUsers, setSelectedUsers] = useState<CometChat.User[]>([]);\n  const [errorToastVisible, setErrorToastVisible] = useState(false);\n  const [errorToastMessage, setErrorToastMessage] = useState('');\n  const errorTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const [kbOffset, setKbOffset] = React.useState(900);\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n    );\n\n  useEffect(() => {\n    return () => {\n      if (errorTimeoutRef.current) {\n        clearTimeout(errorTimeoutRef.current);\n      }\n    };\n  }, []);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  useLayoutEffect(() => {\n    if (Platform.OS === 'ios') {\n      if (Number.isInteger(commonVars.safeAreaInsets.top)) {\n        setKbOffset(commonVars.safeAreaInsets.top ?? 0);\n        return;\n      }\n\n      CommonUtil.getSafeAreaInsets().then((res: any) => {\n        if (Number.isInteger(res.top)) {\n          commonVars.safeAreaInsets.top = res.top;\n          commonVars.safeAreaInsets.bottom = res.bottom;\n          setKbOffset(res.top);\n        }\n      });\n    }\n  }, []);\n\n  const addMembersToGroup = useCallback(\n    async (users: Array<CometChat.User>) => {\n      try {\n        const membersList = users.map((item: CometChat.User) => {\n          const groupMember = new CometChat.GroupMember(\n            item.getUid(),\n            CometChat.GROUP_MEMBER_SCOPE.PARTICIPANT,\n          );\n          groupMember.setName(item.getName());\n          return groupMember;\n        });\n\n        const guid = group.getGuid();\n        const response = await CometChat.addMembersToGroup(\n          guid,\n          membersList,\n          [],\n        );\n\n        const addedUIDs = Object.entries(response)\n          .filter(([_, status]) => status === 'success')\n          .map(([uid]) => uid);\n\n        const addedMembers = membersList.filter(member =>\n          addedUIDs.includes(member.getUid()),\n        );\n\n        if (addedUIDs.length > 0) {\n          navigation.goBack();\n        } else {\n          setErrorToastMessage('Error, Unable to add members');\n          setErrorToastVisible(true);\n          errorTimeoutRef.current = setTimeout(() => {\n            setErrorToastVisible(false);\n          }, 3000);\n        }\n\n        // If all succeeded, emit individual events for each member\n        if (addedMembers.length > 0) {\n          const groupInfo  = await CometChat.getGroup(group.getGuid());\n          group.setMembersCount(groupInfo.getMembersCount());\n          // Create separate action for each added member\n          addedMembers.forEach(member => {\n            const action: CometChat.Action = new CometChat.Action(\n              guid,\n              CometChatUiKitConstants.MessageTypeConstants.groupMember,\n              CometChat.RECEIVER_TYPE.GROUP,\n              CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n            );\n            action.setConversationId(guid);\n            action.setActionBy(CometChatUIKit.loggedInUser!);\n            action.setActionFor(group);\n            action.setSender(CometChatUIKit.loggedInUser!);\n            // Initialize data to prevent crash when SDK accesses getData().metadata during render\n            action.setData({ metadata: {} });\n\n            // Emit individual event for each member added\n            CometChatUIEventHandler.emitGroupEvent(\n              CometChatGroupsEvents.ccGroupMemberAdded,\n              {\n                addedBy: CometChatUIKit.loggedInUser,\n                message: action,\n                usersAdded: [member],\n                userAddedIn: group,\n              },\n            );\n          });\n        }\n      } catch (error) {\n        console.error('Something went wrong', error);\n        setErrorToastMessage('Error, Unable to add members');\n        setErrorToastVisible(true);\n        errorTimeoutRef.current = setTimeout(() => {\n          setErrorToastVisible(false);\n        }, 2000);\n      }\n    },\n    [group, navigation],\n  );\n\n  const handleUserSelection = useCallback((users: CometChat.User[]) => {\n    setSelectedUsers(users);\n  }, []);\n\n  return (\n    <KeyboardAvoidingView\n      behavior=\"padding\"\n      enabled={Platform.OS === 'ios' ? true : false}\n      keyboardVerticalOffset={kbOffset}\n      style={{ flex: 1, backgroundColor: theme.color.background1 }}\n    >\n      {/* Header */}\n      <View style={{ flex: 1 }}>\n        <View\n          style={[\n            styles.addMemberContainer,\n            { borderBottomColor: theme.color.borderLight },\n          ]}\n        >\n          <TouchableOpacity\n            style={styles.iconContainer}\n            onPress={() => navigation.goBack()}\n          >\n            <Icon\n              icon={\n                <ArrowBack\n                  color={theme.color.iconPrimary}\n                  height={24}\n                  width={24}\n                />\n              }\n            />\n          </TouchableOpacity>\n          <Text\n            style={[\n              theme.typography.heading1.bold,\n              styles.addMemberText,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            {t('ADD_MEMBERS')}\n          </Text>\n        </View>\n\n        {/* Users List */}\n        <CometChatUsers\n          hideHeader={true}\n          ref={userRef}\n          usersRequestBuilder={new CometChat.UsersRequestBuilder()\n            .setLimit(30)\n            .hideBlockedUsers(false)\n            .setRoles([])\n            .friendsOnly(false)\n            .setStatus('')\n            .setTags([])\n            .sortBy('name')\n            .setUIDs([])}\n          selectionMode=\"multiple\"\n          onSelection={handleUserSelection}\n          showBackButton={true}\n          onBack={() => navigation.goBack()}\n          usersStatusVisibility={userAndFriendsPresence}\n        />\n\n        {/* Add Members Button */}\n        <TouchableOpacity\n          onPress={() => addMembersToGroup(selectedUsers)}\n          style={styles.addMembersButton}\n        >\n          <View\n            style={[\n              styles.addMembersButtonContainer,\n              { backgroundColor: theme.color.primaryButtonBackground },\n            ]}\n          >\n            <Text\n              style={[\n                theme.typography.heading4.medium,\n                { color: theme.color.primaryButtonText, alignSelf: 'center' },\n              ]}\n            >\n              {t('ADD_MEMBERS')}\n            </Text>\n          </View>\n        </TouchableOpacity>\n\n        {/* Error Toast */}\n        {errorToastVisible && (\n          <View\n            style={[\n              styles.toastContainer,\n              { backgroundColor: theme.color.error },\n            ]}\n          >\n            <Text style={styles.toastTextStyle}>{errorToastMessage}</Text>\n          </View>\n        )}\n      </View>\n    </KeyboardAvoidingView>\n  );\n};\n\nexport default AddMember;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/AddMemberStyles.tsx",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n    toastTextStyle: {\n      color: '#fff',\n      fontSize: 16,\n    },\n    toastContainer: {\n      position: 'absolute',\n      bottom: 20,\n      left: 20,\n      right: 20,\n      paddingVertical: 10,\n      borderRadius: 5,\n      alignItems: 'center',\n    },\n    addMemberContainer: {\n      paddingTop: 15,\n      paddingLeft: 10,\n      flexDirection: 'row',\n      paddingBottom: 15,\n      borderBottomWidth: 1,\n    },\n    addMemberText: {\n      paddingLeft: 10,\n    },\n    iconContainer: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    addMembersButton: {\n      alignContent: 'center',\n      justifyContent: 'center',\n      paddingVertical: 1,\n      height: 50,\n      width: '100%',\n      alignSelf: 'center',\n    },\n    addMembersButtonContainer: {\n      marginHorizontal: 20,\n      alignSelf: 'center',\n      justifyContent: 'center',\n      borderRadius: 6,\n      height: '75%',\n      width: '95%',\n    },\n  });\n  "
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/BannedMember.tsx",
    "content": "import React, { useCallback, useRef, useState } from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  Modal,\n  TouchableWithoutFeedback,\n  BackHandler,\n} from 'react-native';\nimport {\n  CometChatConfirmDialog,\n  CometChatList,\n  CometChatListActionsInterface,\n} from '@cometchat/chat-uikit-react-native/src/shared';\nimport { Skeleton } from '@cometchat/chat-uikit-react-native/src/CometChatUsers/Skeleton';\nimport {\n  Icon,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { ErrorEmptyView } from '@cometchat/chat-uikit-react-native/src/shared/views/ErrorEmptyView/ErrorEmptyView';\nimport { RouteProp, useFocusEffect } from '@react-navigation/native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  getBannedMemberStyleLight,\n  styles,\n  getBannedMemberStyleDark,\n} from './BannedMemberStyles';\nimport UserEmptyIcon from '../../../assets/icons/UserEmptyIcon';\nimport Close from '../../../assets/icons/Close';\nimport Block from '../../../assets/icons/Block';\n\ntype BannedMembersRouteProp = {\n  route: RouteProp<RootStackParamList, 'BannedMember'>;\n  navigation: StackNavigationProp<RootStackParamList, 'BannedMember'>;\n};\n\nconst BannedMember: React.FC<BannedMembersRouteProp> = ({\n  route,\n  navigation,\n}) => {\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const bannedListRef = useRef<CometChatListActionsInterface>(null);\n\n  const [isModalVisible, setModalVisible] = useState(false);\n  const [selectedUser, setSelectedUser] = useState<CometChat.User | null>(null);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  /**\n   * --- Callbacks / Handlers ---\n   */\n\n  const openUnbanModal = (user: CometChat.User) => {\n    setSelectedUser(user);\n    setModalVisible(true);\n  };\n\n  const closeUnbanModal = () => {\n    setModalVisible(false);\n    setSelectedUser(null);\n  };\n\n  const handleUnbanUser = async () => {\n    if (!group || !selectedUser) return;\n    try {\n      const guid = group.getGuid();\n      const uid = selectedUser.getUid();\n\n      // Unban the user\n      await CometChat.unbanGroupMember(guid, uid);\n\n      // Create and dispatch an Action message for the unban event\n      const actionMessage = new CometChat.Action(\n        guid,\n        CometChatUiKitConstants.MessageTypeConstants.groupMember,\n        CometChat.RECEIVER_TYPE.GROUP,\n        CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n      );\n      actionMessage.setConversationId(guid);\n      actionMessage.setActionFor(group);\n      actionMessage.setActionOn(selectedUser);\n      actionMessage.setActionBy(CometChatUIKit.loggedInUser!);\n      actionMessage.setSender(CometChatUIKit.loggedInUser!);\n      // Initialize data to prevent crash when SDK accesses getData().metadata during render\n      actionMessage.setData({ metadata: {} });\n      actionMessage.setMessage(\n        `${CometChatUIKit.loggedInUser?.getName()} ${t(\n          'UNBANNED',\n        )} ${selectedUser.getName()}`,\n      );\n      CometChatUIEventHandler.emitGroupEvent(\n        CometChatGroupsEvents.ccGroupMemberUnBanned,\n        {\n          unbannedBy: CometChatUIKit.loggedInUser,\n          userUnbanned: selectedUser,\n          group,\n          message: actionMessage,\n        },\n      );\n\n      // Remove from the banned list\n      bannedListRef.current?.removeItemFromList(uid);\n\n      // Close modal\n      closeUnbanModal();\n    } catch (error) {\n      console.error('Error unbanning user:', error);\n    }\n  };\n\n  /**\n   * --- Render Helpers ---\n   */\n\n  const renderEmptyView = useCallback(() => {\n    return (\n      <View style={styles.flexContainer}>\n        <ErrorEmptyView\n          title={t('NO_BANNED_MEMBERS_FOUND')}\n          Icon={\n            <Icon\n              icon={\n                <UserEmptyIcon\n                  color={theme.color.neutral300}\n                  height={100}\n                  width={100}\n                />\n              }\n              size={theme.spacing.spacing.s20}\n              containerStyle={{ marginBottom: theme.spacing.spacing.s5 }}\n            />\n          }\n          containerStyle={styles.emptyViewContainer}\n          titleStyle={[\n            theme.userStyles.emptyStateStyle.titleStyle,\n            { color: theme.color.textPrimary },\n          ]}\n        />\n      </View>\n    );\n  }, [theme]);\n\n  const renderLoadingView = () => <Skeleton />;\n\n  const renderTailView = (user: CometChat.User) => (\n    <TouchableOpacity onPress={() => openUnbanModal(user)}>\n      <Icon\n        icon={<Close height={24} width={24} color={theme.color.iconTertiary} />}\n      />\n    </TouchableOpacity>\n  );\n\n  const renderUnbanModal = () => {\n    if (!selectedUser) return null;\n\n    return (\n      <CometChatConfirmDialog\n        titleText={`${t('UNBAN')} ${selectedUser.getName()}`}\n        icon={<Block color={theme.color.error} height={40} width={40} />}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('UNBAN')}\n        messageText={t('UNBAN_SURE') + ' ' + selectedUser.getName()}\n        isOpen={true}\n        onCancel={closeUnbanModal}\n        onConfirm={handleUnbanUser}\n      />\n    );\n  };\n\n  /**\n   * --- Main JSX ---\n   */\n  return (\n    <>\n      <View\n        style={[\n          styles.flexContainer,\n          { backgroundColor: theme.color.background1 },\n        ]}\n      >\n        <CometChatList\n          title={t('BANNED_MEMBERS')}\n          hideStickyHeader={true}\n          ref={bannedListRef}\n          onBack={() => navigation.goBack()}\n          listItemKey=\"uid\"\n          hideBackButton={false}\n          LoadingView={renderLoadingView}\n          EmptyView={renderEmptyView}\n          TrailingView={renderTailView}\n          listStyle={\n            theme.mode === 'light'\n              ? getBannedMemberStyleLight(theme)\n              : getBannedMemberStyleDark(theme)\n          }\n          requestBuilder={new CometChat.BannedMembersRequestBuilder(\n            group.getGuid(),\n          ).setLimit(30)}\n        />\n      </View>\n      {renderUnbanModal()}\n    </>\n  );\n};\n\nexport default BannedMember;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/BannedMemberStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\nimport {\n  CometChatListStylesInterface,\n  CometChatTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {deepMerge} from '@cometchat/chat-uikit-react-native/src/shared/helper/helperFunctions';\nimport {DeepPartial} from '@cometchat/chat-uikit-react-native/src/shared/helper/types';\nimport {ColorValue, ViewStyle} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  flexContainer: {\n    flex: 1,\n  },\n  emptyViewContainer: {\n    flex: 1,\n    justifyContent: 'center',\n    alignItems: 'center',\n    paddingHorizontal: '10%',\n  },\n\n  /* Modal Styles */\n  modalOverlay: {\n    flex: 1,\n    backgroundColor: 'rgba(0, 0, 0, 0.5)',\n  },\n  modalContainer: {\n    position: 'absolute',\n    top: '30%',\n    left: '2%',\n    right: '2%',\n    borderRadius: 10,\n    padding: 20,\n    elevation: 5,\n  },\n  modalIconContainer: {\n    alignSelf: 'center',\n    marginBottom: 10,\n    width: 64,\n    height: 64,\n    borderRadius: 32,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  modalContentContainer: {\n    alignItems: 'center',\n  },\n  modalTitle: {\n    marginBottom: 15,\n  },\n  modalDesc: {\n    textAlign: 'center',\n    marginBottom: 20,\n  },\n  buttonContainer: {\n    flexDirection: 'row',\n  },\n  cancelButton: {\n    flex: 1,\n    paddingVertical: 10,\n    marginHorizontal: 5,\n    borderRadius: 5,\n    borderWidth: 1,\n    alignItems: 'center',\n  },\n  unbanButton: {\n    flex: 1,\n    paddingVertical: 10,\n    marginHorizontal: 5,\n    borderRadius: 5,\n    alignItems: 'center',\n  },\n});\n\nexport type BannedMemberStyle = CometChatListStylesInterface & {\n  skeletonStyle: {\n    backgroundColor: ColorValue;\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n  };\n  headerContainerStyle: ViewStyle;\n};\n\nexport const getBannedMemberStyleLight = (\n  theme: CometChatTheme,\n): DeepPartial<BannedMemberStyle> => {\n  const {color, spacing, typography} = theme;\n  return {\n    headerContainerStyle: {\n      alignItems: 'flex-start',\n      justifyContent: 'center',\n      width: '100%',\n      borderRadius: 0,\n      paddingHorizontal: 0,\n    },\n    titleSeparatorStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n      flexDirection: 'row',\n      justifyContent: 'space-between',\n      alignItems: 'center',\n      width: '100%',\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      flex: 1,\n    },\n    itemStyle: {\n      containerStyle: {\n        flexDirection: 'row',\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        gap: spacing.spacing.s3,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.body.regular,\n      },\n      statusIndicatorStyle: {},\n      avatarStyle: {\n        containerStyle: {},\n        textStyle: {},\n        imageStyle: {},\n      },\n      headViewContainerStyle: {},\n      titleSubtitleContainerStyle: {\n        alignSelf: 'center',\n      },\n      trailingViewContainerStyle: {\n        alignSelf: 'center',\n      },\n    },\n    confirmSelectionStyle: {},\n    selectionCancelStyle: {},\n    loadingIconTint: color.primary,\n    sectionHeaderTextStyle: {\n      marginHorizontal: spacing.spacing.s5,\n      color: color.primary,\n      ...typography.heading4.medium,\n    },\n    onlineStatusColor: color.success,\n    titleViewStyle: {\n      paddingVertical: spacing.spacing.s3,\n      paddingLeft: spacing.spacing.s3,\n      margin: spacing.spacing.s0,\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      ...typography.heading1.bold,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    searchStyle: {\n      textStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.regular,\n        textAlignVertical: 'center',\n        paddingVertical: 0,\n        height: spacing.spacing.s7,\n      },\n      containerStyle: {\n        backgroundColor: color.background3,\n        paddingVertical: spacing.spacing.s3,\n        marginTop: spacing.spacing.s3,\n        width: '95%',\n        gap: spacing.spacing.s1,\n        alignContent: 'space-around',\n        alignSelf: 'center',\n        flexDirection: 'row',\n        alignItems: 'center',\n      },\n      icon: undefined,\n      iconStyle: {\n        tintColor: color.iconSecondary,\n      },\n      placehodlerTextStyle: {\n        color: color.textTertiary,\n      },\n    },\n    emptyStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: 'center' as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: 'center',\n        display: 'none',\n        alignItems: 'center',\n        padding: spacing.padding.p3,\n      },\n    },\n    errorStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: 'center' as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: 'center',\n        alignItems: 'center',\n        padding: spacing.padding.p3,\n      },\n    },\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: ['#E8E8E8', '#F5F5F5'] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  };\n};\n\nexport const getBannedMemberStyleDark = (\n  theme: CometChatTheme,\n): DeepPartial<BannedMemberStyle> => {\n  const {color, spacing, typography} = theme;\n  return deepMerge(getBannedMemberStyleLight(theme), {\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: ['#383838', '#272727'] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  });\n};\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/Conversations.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport React, { useCallback, useContext, useRef, useState } from 'react';\nimport { TouchableOpacity, View } from 'react-native';\nimport {\n  CometChatAvatar,\n  CometChatConversations,\n  CometChatUIKit,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { AuthContext } from '../../../navigation/AuthContext';\nimport {\n  useFocusEffect,\n  useNavigation,\n  CommonActions,\n} from '@react-navigation/native';\nimport { TooltipMenu } from '../../../utils/TooltipMenu';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport AccountCircle from '../../../assets/icons/AccountCircle';\nimport AddComment from '../../../assets/icons/AddComment';\nimport InfoIcon from '../../../assets/icons/InfoIcon';\nimport Logout from '../../../assets/icons/Logout';\nimport { navigate, navigationRef } from '../../../navigation/NavigationService';\nimport { AppConstants, SCREEN_CONSTANTS } from '../../../utils/AppConstants';\nimport Builder from '../../../assets/icons/Builder';\nimport { useConfig, useConfigStore } from '../../../config/store'; // adjust import if needed\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport Reset from '../../../assets/icons/Reset';\nimport AiIcon from '../../../assets/icons/AiIcon';\n\ntype ChatNavigationProp = StackNavigationProp<\n  RootStackParamList,\n  'Conversation'\n>;\n\nconst Conversations: React.FC<{}> = ({}) => {\n  const theme = useTheme();\n  const { setIsLoggedIn: setLogout } = useContext(AuthContext);\n  const [isLoggingOut, setIsLoggingOut] = useState(false);\n  const tooltipPositon = React.useRef({ pageX: 0, pageY: 0 });\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const selectedConversation = useRef<CometChat.Conversation | null>(null);\n  const navigation = useNavigation<ChatNavigationProp>();\n  const avatarContainerRef = useRef<View>(null);\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser,\n  ).current;\n  const { t } = useCometChatTranslation();\n\n  const [isConfigUpdated, setIsConfigUpdated] = useState(false);\n  const messageDeliveryAndReadReceipts = useConfig(\n        (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n\n  useFocusEffect(\n    useCallback(() => {\n      // Check config updated flag\n      AsyncStorage.getItem('@config_updated').then(val => {\n        setIsConfigUpdated(val === 'true');\n      });\n      return () => {\n        setTooltipVisible(false);\n      };\n    }, []),\n  );\n\n  const handleResetConfig = async () => {\n    useConfigStore.getState().resetConfig();\n    await AsyncStorage.removeItem('@config_updated');\n    setIsConfigUpdated(false);\n  };\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n    );\n\n  const openMessagesFor = (item: CometChat.Conversation) => {\n    // Determine if it's a user or group conversation\n    const isUser = item.getConversationType() === 'user';\n    const isGroup = item.getConversationType() === 'group';\n\n    // Navigate to Messages with appropriate params\n    navigation.navigate('Messages', {\n      user: isUser ? (item.getConversationWith() as CometChat.User) : undefined,\n      group: isGroup\n        ? (item.getConversationWith() as CometChat.Group)\n        : undefined,\n    });\n  };\n\n  const _conversationsConfig = {\n    onItemPress: openMessagesFor,\n    onError: (err: any) => {\n      console.log('ERROR IN CONVO: ', err);\n    },\n  };\n\n  const handleAvatarPress = () => {\n    try {\n      if (avatarContainerRef.current) {\n        avatarContainerRef.current.measureInWindow((x, y, height) => {\n          // Set tooltip position 10px below the avatar\n          tooltipPositon.current = { pageX: x, pageY: y + height };\n        });\n        selectedConversation.current = null;\n        setTooltipVisible(true);\n      }\n    } catch (error) {\n      console.error('Error while handling avatar press:', error);\n    }\n  };\n\n  const handleLogout = async () => {\n    if (isLoggingOut) return;\n    setIsLoggingOut(true);\n\n    // Step 1: Logout from CometChat\n    try {\n      await CometChat.logout();\n    } catch (error) {\n      console.error('CometChat logout failed:', error);\n      setIsLoggingOut(false);\n      return; // Exit if CometChat logout fails\n    }\n\n    // If all operations succeed, navigate to the LoginScreen\n    setIsLoggingOut(false);\n    setLogout(false);\n    navigationRef.dispatch(\n      CommonActions.reset({\n        index: 0,\n        routes: [{ name: 'SampleUser' }],\n      }),\n    );\n  };\n\n  const NewConversation = () => {\n    return (\n      <View ref={avatarContainerRef}>\n        <TouchableOpacity\n          onPress={() => {\n            handleAvatarPress();\n          }}\n        >\n          <CometChatAvatar\n            style={{\n              containerStyle: {\n                height: 40,\n                width: 40,\n                justifyContent: 'center',\n                alignItems: 'center',\n                overflow: 'hidden',\n              },\n              textStyle: {\n                fontSize: 22,\n                lineHeight: 28,\n                textAlign: 'center',\n              },\n            }}\n            image={\n              loggedInUser?.getAvatar()\n                ? { uri: loggedInUser?.getAvatar() }\n                : undefined\n            }\n            name={loggedInUser?.getName() ?? ''}\n          />\n        </TouchableOpacity>\n      </View>\n    );\n  };\n\n  return (\n    <View style={{ flex: 1 }}>\n      <View style={{ flex: 1 }}>\n        <CometChatConversations\n          {..._conversationsConfig}\n          AppBarOptions={NewConversation}\n          selectionMode=\"none\"\n          showSearchBar={true}\n          onSearchBarClicked={() => {\n            navigate(SCREEN_CONSTANTS.SEARCH_MESSAGES);\n          }}\n          usersStatusVisibility={userAndFriendsPresence}\n          receiptsVisibility={messageDeliveryAndReadReceipts}\n        />\n      </View>\n\n      <View\n        style={{\n          position: 'absolute',\n          top: tooltipPositon.current.pageY,\n          left: tooltipPositon.current.pageX,\n          zIndex: 9999,\n        }}\n      >\n        <TooltipMenu\n          visible={tooltipVisible}\n          onClose={() => {\n            setTooltipVisible(false);\n          }}\n          onDismiss={() => {\n            setTooltipVisible(false);\n          }}\n          event={{\n            nativeEvent: tooltipPositon.current,\n          }}\n          menuItems={[\n            {\n              text: t('CREATE_CONVERSATION'),\n              onPress: () => {\n                navigation.navigate('CreateConversation');\n              },\n              icon: (\n                <AddComment\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}\n                ></AddComment>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: t('AI_ASSISTANTS'),\n              onPress: () => {\n                navigation.navigate(SCREEN_CONSTANTS.AI_AGENTS);\n              },\n              icon: (\n                <AiIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}\n                ></AiIcon>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: loggedInUser?.getName() || 'User',\n              onPress: () => {\n                setTooltipVisible(false);\n              },\n              icon: (\n                <AccountCircle\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}\n                ></AccountCircle>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: t('LOGOUT'),\n              onPress: () => {\n                handleLogout();\n              },\n              icon: (\n                <Logout\n                  height={24}\n                  width={24}\n                  color={theme.color.error}\n                ></Logout>\n              ),\n              textColor: theme.color.error,\n              iconColor: theme.color.error,\n            },\n            {\n              text: AppConstants.versionNumber,\n              onPress: () => {},\n              icon: (\n                <InfoIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}\n                ></InfoIcon>\n              ),\n            },\n            isConfigUpdated\n              ? {\n                text: 'Reset to Default',\n                onPress: handleResetConfig,\n                icon: (\n                  <Reset\n                    height={24}\n                    width={24}\n                    color={theme.color.textPrimary}\n                  />\n                ),\n                textColor: theme.color.textPrimary,\n                iconColor: theme.color.textPrimary,\n              }\n              : {\n                text: 'Builder Live Preview',\n                onPress: () => {\n                  navigation.navigate(SCREEN_CONSTANTS.QR_SCREEN);\n                },\n                icon: (\n                  <Builder\n                    height={24}\n                    width={24}\n                    color={theme.color.textPrimary}\n                  />\n                ),\n                textColor: theme.color.textPrimary,\n                iconColor: theme.color.textPrimary,\n              },\n          ]}\n        />\n      </View>\n    </View>\n  );\n};\n\nexport default Conversations;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/CreateConversation.tsx",
    "content": "import React, { useState } from 'react';\nimport { View, Text, TouchableOpacity, StyleSheet } from 'react-native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RouteProp } from '@react-navigation/native';\nimport {\n  useTheme,\n  CometChatUsers,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport Groups from '../../groups/Groups';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\n\n// Define prop types for the component using React Navigation's types\ntype Props = {\n  route: RouteProp<RootStackParamList, 'CreateConversation'>;\n  navigation: StackNavigationProp<RootStackParamList, 'CreateConversation'>;\n};\n\nconst CreateConversation: React.FC<Props> = ({ route, navigation }) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const {\n    background1,\n    background3,\n    iconPrimary,\n    textPrimary,\n    textSecondary,\n    primary,\n  } = theme.color;\n  const { heading1 } = theme.typography;\n  const [selectedTab, setSelectedTab] = useState<'Users' | 'Groups'>('Users');\n\n  return (\n    <View style={[styles.container, { backgroundColor: background1 }]}>\n      {/* Header */}\n      <View style={styles.header}>\n        <TouchableOpacity style={styles.rowCenter} onPress={navigation.goBack}>\n          <Icon\n            icon={<ArrowBack color={iconPrimary} height={24} width={24} />}\n          />\n        </TouchableOpacity>\n        <Text\n          style={[heading1.bold, styles.headerText, { color: textPrimary }]}\n        >\n          {t('NEW_CHAT')}\n        </Text>\n      </View>\n\n      {/* Tab Bar */}\n      <View style={[styles.tabContainer, { backgroundColor: background3 }]}>\n        {['Users', 'Groups'].map(tab => (\n          <TouchableOpacity\n            key={tab}\n            style={[\n              styles.tabButton,\n              selectedTab === tab && styles.activeTab,\n              selectedTab === tab && { backgroundColor: background1 },\n            ]}\n            onPress={() => setSelectedTab(tab as 'Users' | 'Groups')}\n          >\n            <Text\n              style={[\n                styles.tabText,\n                { color: selectedTab === tab ? primary : textSecondary },\n              ]}\n            >\n              {t(tab.toUpperCase())}\n            </Text>\n          </TouchableOpacity>\n        ))}\n      </View>\n\n      {/* Content */}\n      <View style={styles.content}>\n        {selectedTab === 'Users' ? (\n          <CometChatUsers\n            usersRequestBuilder={new CometChat.UsersRequestBuilder()\n              .setLimit(30)\n              .hideBlockedUsers(false)\n              .setRoles([])\n              .friendsOnly(false)\n              .setStatus('')\n              .setTags([])\n              .sortBy('name')\n              .setUIDs([])}\n            hideHeader\n            onItemPress={(user: CometChat.User) =>\n              navigation.navigate('Messages', { user })\n            }\n          />\n        ) : (\n          <Groups hideHeader />\n        )}\n      </View>\n    </View>\n  );\n};\n\nexport default CreateConversation;\n\n// Style definitions for the component\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  header: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  rowCenter: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  headerText: {\n    paddingLeft: 5,\n  },\n  tabContainer: {\n    marginTop: 20,\n    flexDirection: 'row',\n    marginHorizontal: 20,\n    borderRadius: 30,\n    padding: 5,\n  },\n  tabButton: {\n    flex: 1,\n    paddingVertical: 10,\n    alignItems: 'center',\n    borderRadius: 30,\n  },\n  activeTab: {\n    borderRadius: 30,\n  },\n  tabText: {\n    fontSize: 16,\n    fontWeight: 'bold',\n  },\n  content: {\n    flex: 1,\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/GroupInfo.tsx",
    "content": "import React, { useEffect, useRef, useState } from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  useWindowDimensions,\n  BackHandler,\n} from 'react-native';\nimport { RouteProp, useFocusEffect } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  CometChatAvatar,\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatConfirmDialog,\n  useTheme,\n  CometChatConversationEvents,\n  CometChatUIEvents,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatUIKit,\n  CometChatUiKitConstants,\n} from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { listners } from '../helper/GroupListeners';\nimport { styles } from './GroupInfoStyles';\nimport { leaveGroup } from '../../../utils/helper';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport Group from '../../../assets/icons/Group';\nimport PersonAdd from '../../../assets/icons/PersonAdd';\nimport PersonOff from '../../../assets/icons/PersonOff';\nimport Block from '../../../assets/icons/Block';\nimport Delete from '../../../assets/icons/Delete';\nimport { useConfig } from '../../../config/store';\n\ntype GroupInfoProps = {\n  route: RouteProp<RootStackParamList, 'GroupInfo'>;\n  navigation: StackNavigationProp<RootStackParamList, 'GroupInfo'>;\n};\n\nconst GroupInfo: React.FC<GroupInfoProps> = ({route, navigation}) => {\n  const addMembersToGroups = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.addMembersToGroups\n  );\n  const joinLeaveGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.joinLeaveGroup\n  );\n  const deleteGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.deleteGroup\n  );\n  const viewGroupMembers = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.viewGroupMembers\n  );\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const groupListenerId = useRef('groupListener' + new Date().getTime());\n\n  const [data, setData] = useState({ groupDetails: group });\n  const [userScope, setUserScope] = useState(\n    group?.getOwner() === CometChatUIKit.loggedInUser?.getUid()\n      ? CometChatUiKitConstants.GroupMemberScope.owner\n      : group?.getScope(),\n  );\n\n  // Separate states for each type of modal\n  const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false);\n  const [isOwnerLeaveModalOpen, setIsOwnerLeaveModalOpen] = useState(false);\n  const [isDeleteExitModalOpen, setIsDeleteExitModalOpen] = useState(false);\n  const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);\n\n  const { width } = useWindowDimensions();\n  const isSmallDevice = width < 360;\n\n  useEffect(() => {\n    // Update group details in state whenever group changes\n    const handleGroupListener = (updatedGroup: CometChat.Group) => {\n      if (updatedGroup.getGuid() === route.params.group.getGuid()) {\n        setData({ groupDetails: updatedGroup });\n        setUserScope(\n          updatedGroup?.getOwner() === CometChatUIKit.loggedInUser?.getUid()\n            ? CometChatUiKitConstants.GroupMemberScope.owner\n            : (updatedGroup?.getScope() ?? userScope),\n        );\n      }\n    };\n\n    const handleGroupMemberKicked = ({ kickedFrom }: any) => {\n      handleGroupListener(CommonUtils.clone(kickedFrom));\n    };\n    const handleGroupMemberBanned = ({ kickedFrom }: any) => {\n      handleGroupListener(CommonUtils.clone(kickedFrom));\n    };\n    const handleGroupMemberAdded = ({ userAddedIn }: any) => {\n      handleGroupListener(CommonUtils.clone(userAddedIn));\n    };\n    const handleOwnershipChanged = ({ group }: any) => {\n      handleGroupListener(group);\n    };\n\n    // Add group listeners\n    listners.addListener.groupListener({\n      groupListenerId: groupListenerId.current,\n      handleGroupListener,\n    });\n\n    CometChatUIEventHandler.addGroupListener(groupListenerId.current, {\n      ccGroupMemberKicked: (item: any) => handleGroupMemberKicked(item),\n      ccGroupMemberBanned: (item: any) => handleGroupMemberBanned(item),\n      ccGroupMemberAdded: (item: any) => handleGroupMemberAdded(item),\n      ccOwnershipChanged: (item: any) => handleOwnershipChanged(item),\n    });\n\n    return () => {\n      // Cleanup\n      listners.removeListner.removeGroupListener({\n        groupListenerId: groupListenerId.current,\n      });\n      CometChatUIEventHandler.removeGroupListener(groupListenerId.current);\n      CometChat.removeGroupListener(groupListenerId.current);\n    };\n  }, [group, userScope]);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  const getLabel = (key: string) => {\n    const label = t(key);\n    // Split into two words if device is small\n    if (isSmallDevice && label.split(' ').length === 2) {\n      return label.split(' ').join('\\n');\n    }\n    return label;\n  };\n\n  /**\n   * Handlers for each modal's confirm action\n   */\n\n  // 1) Normal \"Leave Group\" confirm\n  const handleLeaveConfirm = () => {\n    setIsLeaveModalOpen(false);\n    if (data.groupDetails) {\n      leaveGroup(data.groupDetails, navigation, 2);\n    }\n  };\n\n  // 2) \"Owner => Transfer Ownership\" confirm\n  const handleOwnerLeaveConfirm = () => {\n    if (!data.groupDetails) return;\n\n    setIsOwnerLeaveModalOpen(false);\n    navigation.navigate('TransferOwnershipSection', {\n      group: data.groupDetails,\n    });\n  };\n\n  // 3) \"Delete and Exit\" confirm\n  const handleDeleteExitConfirm = () => {\n    setIsDeleteExitModalOpen(false);\n    if (!data.groupDetails) return;\n    // Delete group\n    CometChat.deleteGroup(data.groupDetails.getGuid())\n      .then(() => {\n        navigation.pop(2);\n      })\n      .catch(error => {\n        console.log('Group deletion failed:', error);\n      });\n    // Emit group deleted event\n    CometChatUIEventHandler.emitGroupEvent(\n      CometChatGroupsEvents.ccGroupDeleted,\n      {\n        group: data.groupDetails,\n      },\n    );\n  };\n\n  /** DELETE CONVERSATION LOGIC **/\n  const handleDeleteConversationConfirm = () => {\n    setDeleteModalOpen(false); // close the dialog\n    if (group) {\n      CometChat.getConversation(group.getGuid(), 'group')\n        .then(conversation => {\n          CometChat.deleteConversation(group.getGuid(), 'group')\n            .then(deletedConversation => {\n              console.log(deletedConversation);\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccConversationDeleted,\n                { conversation: conversation },\n              );\n              navigation.pop(2);\n            })\n            .catch(error => {\n              console.log('Error while deleting conversation:', error);\n            });\n        })\n        .catch(error => {\n          console.log('Error while deleting conversation:', error);\n        });\n    }\n  };\n\n  return (\n    <View\n      style={[styles.flexOne, { backgroundColor: theme.color.background1 }]}\n    >\n      {/* Header */}\n      <View style={styles.headerContainer}>\n        <TouchableOpacity\n          style={styles.iconContainer}\n          onPress={() => navigation.goBack()}\n        >\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.pL5,\n            { color: theme.color.textPrimary },\n          ]}\n        >\n          {t('GROUP_INFO')}\n        </Text>\n      </View>\n\n      {/* Main Group Info Container */}\n      <View\n        style={[\n          styles.groupInfoSection,\n          { borderColor: theme.color.borderLight },\n        ]}\n      >\n        <View style={styles.infoTitleContainer}>\n          <CometChatAvatar\n            style={{\n              containerStyle: styles.avatarContainer,\n              textStyle: styles.avatarText,\n              imageStyle: styles.avatarImage,\n            }}\n            image={\n              data.groupDetails?.getIcon()\n                ? { uri: data.groupDetails?.getIcon() }\n                : undefined\n            }\n            name={data.groupDetails?.getName() ?? ''}\n          />\n          <View style={styles.ellipseTail}>\n            <Text\n              style={[\n                theme.typography.heading3.medium,\n                styles.titleName,\n                { color: theme.color.textPrimary },\n              ]}\n              numberOfLines={1}\n              ellipsizeMode=\"tail\"\n            >\n              {data.groupDetails?.getName()}\n            </Text>\n          </View>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.boxLabel,\n              { color: theme.color.textSecondary },\n            ]}\n          >\n            {data.groupDetails?.getMembersCount() +\n              ' ' +\n              t(\n                data.groupDetails?.getMembersCount() === 1\n                  ? 'MEMBER'\n                  : 'MEMBERS',\n              )}\n          </Text>\n        </View>\n\n        {/* Action Boxes: Add Members / View Members / Banned Members */}\n        <View style={styles.boxContainerRow}>\n          {/* Add Members */}\n          {addMembersToGroups &&\n            [CometChatUiKitConstants.GroupMemberScope.owner,\n            CometChatUiKitConstants.GroupMemberScope.admin\n            ].includes(userScope) && (\n              <TouchableOpacity\n                onPress={() => {\n                  navigation.navigate('AddMember', { group });\n                }}\n                style={[\n                  styles.buttonContainer,\n                  { borderColor: theme.color.borderDefault },\n                ]}\n              >\n                <Icon\n                  icon={\n                    <PersonAdd color={theme.color.primary} height={24} width={24} />\n                  }\n                  containerStyle={styles.buttonIcon}\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.boxLabel,\n                    { color: theme.color.textSecondary },\n                  ]}\n                >\n                  {getLabel('ADD_MEMBERS')}\n                </Text>\n              </TouchableOpacity>\n            )}\n\n          {/* View Members */}\n          {viewGroupMembers && (\n          <TouchableOpacity\n            onPress={() => {\n              navigation.navigate('ViewMembers', { group });\n            }}\n            style={[\n              styles.buttonContainer,\n              { borderColor: theme.color.borderDefault },\n            ]}\n          >\n            <Icon\n              icon={\n                <Group color={theme.color.primary} height={24} width={24} />\n              }\n              containerStyle={styles.buttonIcon}\n            />\n            <Text\n              style={[\n                theme.typography.caption1.regular,\n                styles.boxLabel,\n                { color: theme.color.textSecondary },\n              ]}\n            >\n              {getLabel('VIEW_MEMBERS')}\n            </Text>\n          </TouchableOpacity>\n          )}\n          {/* Banned Members */}\n          {(userScope === CometChatUiKitConstants.GroupMemberScope.owner ||\n            userScope === CometChatUiKitConstants.GroupMemberScope.admin ||\n            userScope ===\n              CometChatUiKitConstants.GroupMemberScope.moderator) && (\n            <TouchableOpacity\n              onPress={() => {\n                navigation.navigate('BannedMember', { group });\n              }}\n              style={[\n                styles.buttonContainer,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            >\n              <Icon\n                icon={\n                  <PersonOff\n                    color={theme.color.primary}\n                    height={24}\n                    width={24}\n                  />\n                }\n                containerStyle={styles.buttonIcon}\n              />\n              <Text\n                style={[\n                  theme.typography.caption1.regular,\n                  styles.boxLabel,\n                  { color: theme.color.textSecondary },\n                ]}\n              >\n                {getLabel('BANNED_MEMBERS')}\n              </Text>\n            </TouchableOpacity>\n          )}\n        </View>\n      </View>\n\n      {/* Actions */}\n      <View style={styles.actionContainer}>\n        <View style={styles.actionButtons}>\n          <TouchableOpacity\n            onPress={() => setDeleteModalOpen(true)}\n            style={styles.iconContainer}\n          >\n            <Icon\n              icon={<Delete color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.mL5,\n                { color: theme.color.error },\n              ]}\n            >\n              {t('DELETE_CHAT_TEXT')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n        {/* If user is owner but group has multiple members => must TRANSFER ownership.\n           Otherwise => normal leave.  */}\n        {joinLeaveGroup &&\n          (data.groupDetails.getMembersCount() > 1 ||\n            userScope !== CometChatUiKitConstants.GroupMemberScope.owner) && (\n            <View style={styles.actionButtons}>\n              <TouchableOpacity\n                onPress={() => {\n                  if (userScope === CometChatUiKitConstants.GroupMemberScope.owner) {\n                    setIsOwnerLeaveModalOpen(true);\n                  } else {\n                    setIsLeaveModalOpen(true);\n                  }\n                }}\n                style={styles.iconContainer}\n              >\n                <Icon icon={<Block color={theme.color.error} height={24} width={24} />} />\n                <Text\n                  style={[\n                    theme.typography.heading4.regular,\n                    styles.mL5,\n                    { color: theme.color.error },\n                  ]}\n                >\n                  {t('LEAVE')}\n                </Text>\n              </TouchableOpacity>\n            </View>\n          )}\n\n        {/* Delete and Exit (Group owner only) */}\n        {deleteGroup && \n        [\n          CometChatUiKitConstants.GroupMemberScope.owner,\n          CometChatUiKitConstants.GroupMemberScope.admin,\n        ].includes(userScope) && (\n          <View style={styles.actionButtons}>\n            <TouchableOpacity\n              onPress={() => setIsDeleteExitModalOpen(true)}\n              style={styles.iconContainer}\n            >\n              <Icon\n                icon={\n                  <Delete color={theme.color.error} height={24} width={24} />\n                }\n              />\n              <Text\n                style={[\n                  theme.typography.heading4.regular,\n                  styles.mL5,\n                  { color: theme.color.error },\n                ]}\n              >\n                {t('DELETE_AND_EXIT')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        )}\n      </View>\n\n      {/* ============== LEAVE GROUP (Regular) Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isLeaveModalOpen}\n        onCancel={() => setIsLeaveModalOpen(false)}\n        onConfirm={handleLeaveConfirm}\n        titleText={t('LEAVE_GROUP_TEXT')}\n        messageText={t('LEAVE_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('LEAVE')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* ============== TRANSFER OWNERSHIP Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isOwnerLeaveModalOpen}\n        onCancel={() => setIsOwnerLeaveModalOpen(false)}\n        onConfirm={handleOwnerLeaveConfirm}\n        titleText={t('TRANSFER_OWNERSHIP')}\n        messageText={t('TRANSFER_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('TRANSFER')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* ============== DELETE AND EXIT Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteExitModalOpen}\n        onCancel={() => setIsDeleteExitModalOpen(false)}\n        onConfirm={handleDeleteExitConfirm}\n        titleText={`${t('DELETE_AND_EXIT')}?`}\n        messageText={t('DELETE_AND_EXIT_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE_AND_EXIT')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* =============== DELETE CHAT MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteModalOpen}\n        onCancel={() => setDeleteModalOpen(false)}\n        onConfirm={handleDeleteConversationConfirm}\n        onDismiss={() => console.log('Delete Modal dismissed')}\n        titleText={t('DELETE_CHAT')}\n        messageText={t('SURE_TO_DELETE_CHAT')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n    </View>\n  );\n};\n\nexport default GroupInfo;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/GroupInfoStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  headerContainer: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  ellipseTail: {paddingHorizontal: 10, width: '80%'},\n  iconContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  pL5: {\n    paddingLeft: 5,\n  },\n  groupInfoSection: {\n    marginTop: 12,\n    borderTopWidth: 1,\n    borderBottomWidth: 1,\n    paddingVertical: 10,\n  },\n  infoTitleContainer: {\n    alignSelf: 'center',\n    alignItems: 'center',\n    marginTop: 20,\n  },\n  avatarContainer: {\n    height: 120,\n    width: 120,\n  },\n  avatarText: {\n    fontSize: 28,\n    lineHeight: 55,\n  },\n  avatarImage: {\n    height: '100%',\n    width: '100%',\n  },\n  titleName: {\n    marginTop: 10,\n    // width:'20%',\n    alignSelf: 'center',\n  },\n  boxLabel: {\n    marginTop: 5,\n    textAlign: 'center',\n    alignSelf: 'center',\n  },\n  boxContainerRow: {\n    flexDirection: 'row',\n    paddingHorizontal: 20,\n    justifyContent: 'space-between',\n    marginVertical: 20,\n  },\n  buttonContainer: {\n    flex: 1,\n    paddingVertical: 10,\n    borderWidth: 1,\n    borderRadius: 8,\n    justifyContent: 'center',\n    alignItems: 'center',\n    marginHorizontal: 5,\n  },\n  buttonIcon: {\n    marginBottom: 5,\n  },\n  actionContainer: {\n    paddingTop: 10,\n    gap: 4,\n  },\n  actionButtons: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 8,\n    paddingLeft: 20,\n    width: '100%',\n  },\n  mL5: {\n    marginLeft: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/Messages.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  TouchableOpacity,\n  View,\n  StyleSheet,\n  Text,\n  BackHandler,\n  Platform,\n  Modal,\n  Animated,\n  Dimensions,\n  AppState,\n  AppStateStatus,\n} from 'react-native';\nimport {\n  CometChatUIKit,\n  CometChatMessageHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  CometChatMessageComposer,\n  useTheme,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  ChatConfigurator,\n  useCometChatTranslation,\n  Icon,\n  CometChatAIAssistantChatHistory,\n  CometChatAIAssistantTools,\n  CometChatThemeProvider,\n  stopStreamingForRunId,\n} from '@cometchat/chat-uikit-react-native';\nimport { StackScreenProps } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport InfoIcon from '../../../assets/icons/InfoIcon';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport Info from '../../../assets/icons/Info';\nimport {useActiveChat} from '../../../utils/ActiveChatContext';\nimport { useConfig } from '../../../config/store';\nimport { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\n\nconst { width } = Dimensions.get('window');\n\ntype Props = StackScreenProps<RootStackParamList, 'Messages'>;\n\nconst Messages: React.FC<Props> = ({ route, navigation }) => {\n  const {\n    user,\n    group,\n    fromMention = false,\n    fromMessagePrivately = false,\n    parentMessageId: routeParentMessageId,\n    messageId,\n    searchKeyword,\n    navigatedFromSearch\n  } = route.params;\n  const typingIndicator = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.typingIndicator\n  );\n  const threadConversationAndReplies = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.threadConversationAndReplies\n  );\n  const photosSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing\n  );\n  const videoSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing\n  );\n  const audioSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing\n  );\n  const fileSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing\n  );\n  const editMessage = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.editMessage\n  );\n  const deleteMessage = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.deleteMessage\n  );\n  const messageDeliveryAndReadReceipts = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n  const userAndFriendsPresence = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n  );\n  const mentions = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.mentions\n  );\n  const reactions = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.reactions\n  );\n  const messageTranslation = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.messageTranslation\n  );\n  const polls = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.polls\n  );\n  const collaborativeWhiteboard = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeWhiteboard\n  );\n  const collaborativeDocument = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeDocument\n  );\n  const voiceNotes = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes\n  );\n  const stickers = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.stickers\n  );\n  const userInfo = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.userInfo\n  );\n  const groupInfo = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.groupInfo\n  );\n  const sendPrivateMessageToGroupMembers = useConfig(\n    (state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers\n  );\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n  const compactMessageComposer = useConfig(\n    (state) => state.settings.layout.compactMessageComposer\n  );\n\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const themeRef = useRef(theme);\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n  const userListenerId = 'app_messages' + new Date().getTime();\n  // Stable listener id for the lifetime of this component (prevents multiple listeners)\n  const openmessageListenerIdRef = useRef('message_' + new Date().getTime());\n  const lastOpenChatRef = useRef<{ uid: string; time: number } | null>(null);\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(user);\n  const [messageListKey, setMessageListKey] = useState(0);\n  const [messageComposerKey, setMessageComposerKey] = useState(0);\n  const [showHistoryModal, setShowHistoryModal] = useState(false);\n\n  // Manage parentMessageId in parent component\n  const [parentMessageId, setParentMessageId] = useState<string | undefined>(routeParentMessageId);\n\n  const { setActiveChat } = useActiveChat();\n  const insets = useSafeAreaInsets();\n\n  // Add ref to track streaming state\n  const messageComposerRef = useRef<any>(null);\n\n  /** Animation state for drawer */\n  const slideAnim = useRef(new Animated.Value(width)).current;\n\n  useEffect(() => {\n    if (showHistoryModal) {\n      Animated.timing(slideAnim, {\n        toValue: 0,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    } else {\n      Animated.timing(slideAnim, {\n        toValue: width,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    }\n  }, [showHistoryModal, slideAnim]);\n\n  /** Agentic user check */\n  const isAgenticUser = useCallback((): boolean => {\n    if (localUser) {\n      return localUser.getRole?.() === '@agentic';\n    }\n    return false;\n  }, [localUser]);\n  const agentic = isAgenticUser();\n\n  // ============================================\n  // Group Kicked/Banned Detection (real-time)\n  // ============================================\n  const isNoLongerMember = useGroupMemberStatus(group);\n\n  // Stop streaming when app goes to background for agentic users\n  useEffect(() => {\n    if (!agentic) return;\n\n    const handleAppStateChange = (nextAppState: AppStateStatus) => {\n      if (nextAppState === 'background' || nextAppState === 'inactive') {\n        stopStreamingForRunId();\n        if (messageComposerRef.current?.resetStreaming) {\n          messageComposerRef.current.resetStreaming();\n        }\n      }\n    };\n\n    const subscription = AppState.addEventListener('change', handleAppStateChange);\n    return () => subscription.remove();\n  }, [agentic]);\n\n  useEffect(() => {\n    // if it’s a user chat\n    if (user) {\n      setActiveChat({ type: 'user', id: user.getUid() });\n    } else if (group) {\n      setActiveChat({ type: 'group', id: group.getGuid() });\n    }\n\n    // Cleanup on unmount => setActiveChat(null)\n    return () => {\n      setActiveChat(null);\n      // Reset streaming state when leaving chat\n      if (messageComposerRef.current?.resetStreaming) {\n        messageComposerRef.current.resetStreaming();\n      }\n    };\n  }, [user, group, setActiveChat]);\n\n  useEffect(() => {\n    const backAction = () => {\n      if (fromMention || fromMessagePrivately) {\n        navigation.goBack();\n      } else {\n        navigation.popToTop();\n      }\n      return true;\n    };\n    // Add event listener for hardware back press\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n    return () => backHandler.remove();\n  }, [navigation, fromMention, fromMessagePrivately]);\n\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (item: { user: CometChat.User }) =>\n        handleccUserBlocked(item),\n      ccUserUnBlocked: (item: { user: CometChat.User }) =>\n        handleccUserUnBlocked(item),\n    });\n    const statusListenerId = 'user_status_messages_' + new Date().getTime();\n    if (localUser) {\n\n      CometChat.addUserListener(\n        statusListenerId,\n        new CometChat.UserListener({\n          onUserOnline: (onlineUser: CometChat.User) => {\n            if (onlineUser.getUid() === localUser.getUid()) {\n              console.log('🚀 ~ onUserOnline ~ onlineUser:', onlineUser);\n              setLocalUser(onlineUser);\n            }\n          },\n          onUserOffline: (offlineUser: CometChat.User) => {\n            console.log('🚀 ~ onUserOffline ~ offlineUser:', offlineUser);\n            if (offlineUser.getUid() === localUser.getUid()) {\n              setLocalUser(offlineUser);\n            }\n          },\n        }),\n      );\n    }\n\n    // Only attach the openChat listener when we are in a group context.\n    // This prevents stacking duplicate private chat screens because the group\n    // screen remains mounted underneath the user chat.\n    const currentListenerId = openmessageListenerIdRef.current;\n    if (group) {\n      CometChatUIEventHandler.addUIListener(currentListenerId, {\n        openChat: ({ user: chatUser }) => {\n          if (!chatUser) return;\n\n          try {\n            const uid = chatUser.getUid();\n\n            // 1. Debounce rapid duplicate events (within 800ms for the same UID)\n            const now = Date.now();\n            if (\n              lastOpenChatRef.current &&\n              lastOpenChatRef.current.uid === uid &&\n              now - lastOpenChatRef.current.time < 800\n            ) {\n              return; // ignore duplicate\n            }\n\n            const state = navigation.getState();\n            const routes = state?.routes || [];\n            const topRoute = routes[routes.length - 1];\n\n            // If the top route already represents this user chat, skip.\n            if (\n              topRoute?.name === 'Messages' &&\n              (topRoute as any)?.params?.user?.getUid &&\n              (topRoute as any).params.user.getUid() === uid\n            ) {\n              return;\n            }\n\n            // If any existing route (beneath) already has this user chat, pop back to it instead of pushing another.\n            const existingIndex = routes.findIndex(\n              r =>\n                r.name === 'Messages' &&\n                (r as any)?.params?.user?.getUid &&\n                (r as any).params.user.getUid() === uid,\n            );\n            if (existingIndex !== -1) {\n              const popCount = routes.length - existingIndex - 1;\n              if (popCount > 0) {\n                navigation.pop(popCount);\n              }\n              return; // we're now at the existing user chat\n            }\n\n            lastOpenChatRef.current = { uid, time: now };\n            navigation.push('Messages', { user: chatUser, fromMessagePrivately: true });\n          } catch (e) {\n            console.warn('openChat navigation prevented due to error', e);\n          }\n        },\n      });\n    }\n\n    // Close sticker panel when screen loses focus\n    const blurSub = navigation.addListener('blur', () => {\n      CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n        alignment: 'composerBottom',\n        child: () => null,\n        panelId: 'sticker',\n      });\n    });\n    const focusSub = navigation.addListener('focus', () => {\n      // Force re-mount composer so auxiliary options (StickerButton) reset correctly\n      setMessageComposerKey(prev => prev + 1);\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n      CometChat.removeUserListener(statusListenerId);\n      if (group) {\n        CometChatUIEventHandler.removeUIListener(currentListenerId);\n      }\n      blurSub();\n\n      focusSub();\n    };\n    // Listener re-attached only when group context changes\n  }, [navigation, group, userListenerId]);\n\n  const handleccUserBlocked = ({ user: blockedUser }: { user: CometChat.User }) => {\n    setLocalUser(CommonUtils.clone(blockedUser));\n  };\n\n  const handleccUserUnBlocked = ({ user: unblockedUser }: { user: CometChat.User }) => {\n    setLocalUser(CommonUtils.clone(unblockedUser));\n  };\n\n  /** Reset messages */\n  const handleNewChatClick = useCallback(() => {\n    if (messageComposerRef.current?.resetStreaming) {\n      messageComposerRef.current.resetStreaming();\n    }\n    setParentMessageId(undefined);\n    setMessageListKey(prev => prev + 1);\n    setMessageComposerKey(prev => prev + 1);\n    setShowHistoryModal(false);\n    navigation.replace('Messages', {\n      user,\n      group,\n    });\n  }, [navigation, user, group]);\n\n  /** Open chat history modal */\n  const handleChatHistoryClick = useCallback(() => {\n    setShowHistoryModal(true);\n  }, []);\n\n  /** Handle history message click */\n  const handleHistoryMessageClick = useCallback(\n    (message: CometChat.BaseMessage) => {\n      if (messageComposerRef.current && messageComposerRef.current.stopStreaming) {\n        messageComposerRef.current.stopStreaming();\n      }\n      setShowHistoryModal(false);\n      setParentMessageId(message.getId().toString());\n      setMessageListKey(prev => prev + 1);\n      setMessageComposerKey(prev => prev + 1);\n    },\n    [],\n  );\n\n  /** Handle history error */\n  const handleChatHistoryError = useCallback(\n    (_error: CometChat.CometChatException) => {},\n    [],\n  );\n\n  const unblock = async (userToUnblock: CometChat.User) => {\n    let uid = userToUnblock.getUid();\n    try {\n      const response = await CometChat.unblockUsers([uid]);\n      const unBlockedUser = await CometChat.getUser(uid);\n      if (response) {\n        setLocalUser(unBlockedUser);\n\n        // Optionally emit an event or let the server call do the job\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: unBlockedUser,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user:', error);\n    }\n  };\n\n  // Options for the header menu\n  const options = useMemo(() => {\n    return () => {\n      // For agentic users, don't show any options menu\n      if (agentic) {\n        return [];\n      }\n\n      const menuOptions = [];\n\n      // Add info option first\n      if (group && loggedInUser) {\n        menuOptions.push({\n          text: 'Group Info',\n          onPress: () => {\n            navigation.navigate('GroupInfo', { group });\n          },\n          icon: <Icon name=\"info\" width={20} height={20} color={theme.color.iconSecondary} />,\n        });\n      } else if (localUser && !localUser.getBlockedByMe()) {\n        menuOptions.push({\n          text: 'User Info',\n          onPress: () => {\n            navigation.navigate('UserInfo', { user: localUser });\n          },\n          icon: <Icon name=\"info\" width={20} height={20} color={theme.color.iconSecondary} />,\n        });\n      }\n\n      // Then add search option\n      menuOptions.push({\n        text: 'Search',\n        onPress: () => {\n          if (group) {\n            navigation.navigate('SearchMessages', { group });\n          } else if (localUser) {\n            navigation.navigate('SearchMessages', { user: localUser });\n          }\n        },\n        icon: <Icon name=\"search\" width={20} height={20} color={theme.color.iconSecondary} />,\n      });\n\n      return menuOptions;\n    };\n  }, [navigation, group, localUser, theme, agentic, loggedInUser]);\n\n\n  const getMentionsTap = useCallback(() => {\n    const mentionsFormatter =\n      ChatConfigurator.getDataSource().getMentionsFormatter(\n        loggedInUser,\n        theme,\n      );\n    if (user) mentionsFormatter.setUser(user);\n    if (group) mentionsFormatter.setGroup(group);\n\n    mentionsFormatter.setOnMentionClick(\n      (_message: CometChat.BaseMessage, uid: string) => {\n        if (uid !== loggedInUser.getUid()) {\n          // Get the user by UID and navigate to Messages\n          CometChat.getUser(uid)\n            .then((mentionedUser: CometChat.User) => {\n              navigation.push('Messages', {\n                user: mentionedUser,\n                fromMention: true,\n              });\n            })\n            .catch((error: any) => {\n              console.error('Error fetching mentioned user:', error);\n            });\n        }\n      },\n    );\n    return mentionsFormatter;\n  }, [user, group, loggedInUser, navigation, theme]);\n\n  /** Theme override for agentic outgoing bubble */\n  const providerTheme = useMemo(() => {\n    const defaultOutgoingBg =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.containerStyle?.backgroundColor;\n    const defaultTextColor =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.textBubbleStyles?.textStyle?.color;\n\n    const outgoingOverride = {\n      messageComposerStyles: {\n        containerStyle: {\n          backgroundColor: agentic\n            ? theme.color.background3\n            : theme?.messageComposerStyles?.containerStyle?.backgroundColor,\n        },\n      },\n      messageListStyles: {\n        outgoingMessageBubbleStyles: {\n          containerStyle: {\n            backgroundColor: agentic\n              ? theme.color.background4\n              : defaultOutgoingBg,\n          },\n          textBubbleStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textPrimary\n                : defaultTextColor,\n            },\n          },\n          dateStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textSecondary\n                : defaultTextColor,\n            },\n          },\n        },\n      },\n    };\n\n    return {\n      light: outgoingOverride,\n      dark: outgoingOverride,\n      mode: \"auto\" as \"auto\",\n    };\n  }, [agentic, theme]);\n\n  return (\n    <CometChatThemeProvider theme={providerTheme}>\n      <View style={styles.flexOne}>\n        <CometChatMessageHeader\n          user={localUser}\n          group={group}\n          onBack={() => {\n            if (fromMention || fromMessagePrivately) {\n              navigation.goBack();\n            } else {\n              navigation.popToTop();\n            }\n          }}\n          showBackButton={true}\n          usersStatusVisibility={userAndFriendsPresence}\n          hideVoiceCallButton={\n            (user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)\n          }\n          hideVideoCallButton={\n            (user && !oneOnOneVideoCalling) || (group && !groupVideoConference)\n          }\n          hideChatHistoryButton={false}\n          hideNewChatButton={false}\n          onChatHistoryButtonClick={handleChatHistoryClick}\n          onNewChatButtonClick={handleNewChatClick}\n          options={options}\n        />\n        <View style={styles.flexOne}>\n          <CometChatMessageList\n            key={messageListKey}\n            textFormatters={[getMentionsTap()]}\n            user={user}\n            group={group}\n            parentMessageId={parentMessageId}\n            // callback signature expects (messageObject, messageBubbleView)\n            onThreadRepliesPress={(messageObject, _messageBubbleView) => {\n              CometChatUIEventHandler.emitUIEvent?.(\n                CometChatUIEvents.hidePanel,\n                {\n                  alignment: 'composerBottom',\n                  child: () => null,\n                },\n              );\n              navigation.navigate('ThreadView', { message: messageObject, user, group });\n            }}\n            hideReplyInThreadOption={!threadConversationAndReplies}\n            hideEditMessageOption={!editMessage}\n            hideDeleteMessageOption={!deleteMessage}\n            receiptsVisibility={messageDeliveryAndReadReceipts}\n            hideTranslateMessageOption={!messageTranslation}\n            hideReactionOption={!reactions}\n            hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}\n            aiAssistantTools={new CometChatAIAssistantTools({\n              getCurrentWeather: (args: any) => console.log('Weather args', args),\n            })}\n            streamingSpeed={10}\n            goToMessageId={messageId}\n            searchKeyword={searchKeyword}\n            navigatedFromSearch={navigatedFromSearch}\n            showMarkAsUnreadOption={true}\n            startFromUnreadMessages={true}\n          />\n        </View>\n\n        {/* Chat History Drawer */}\n        {agentic && (\n          <Modal visible={showHistoryModal} transparent animationType=\"none\" onRequestClose={() => setShowHistoryModal(false)}>\n            <View style={drawerStyles.backdrop}>\n              <Animated.View\n                style={[\n                  drawerStyles.drawer,\n                  {\n                    backgroundColor: theme.color.background1,\n                    paddingTop: Platform.OS === 'ios' ? insets.top : 0,\n                  },\n                  { transform: [{ translateX: slideAnim }] },\n                ]}\n              >\n                <CometChatAIAssistantChatHistory\n                  user={localUser}\n                  group={group}\n                  onClose={() => setShowHistoryModal(false)}\n                  onMessageClicked={handleHistoryMessageClick}\n                  onError={handleChatHistoryError}\n                  onNewChatButtonClick={handleNewChatClick}\n                />\n              </Animated.View>\n            </View>\n          </Modal>\n        )}\n\n        {isNoLongerMember ? (\n          <View\n            style={{\n              paddingVertical: 12,\n              paddingHorizontal: 16,\n              backgroundColor: theme.color.background1 as string,\n            }}\n          >\n            <Text\n              style={[\n                theme.typography.body.regular,\n                {\n                  color: theme.color.textPrimary as string,\n                  textAlign: 'center',\n                },\n              ]}\n            >\n              {t('GROUP_NO_LONGER_MEMBER')}\n            </Text>\n          </View>\n        ) : localUser?.getBlockedByMe() ? (\n          <View\n            style={[\n              styles.blockedContainer,\n              { backgroundColor: theme.color.background3 },\n            ]}\n          >\n            <Text\n              style={[\n                theme.typography.button.regular,\n                {\n                  color: theme.color.textSecondary,\n                  textAlign: 'center',\n                  paddingBottom: 10,\n                },\n              ]}\n            >\n              {t('BLOCKED_USER_DESC')}\n            </Text>\n            <TouchableOpacity\n              onPress={() => unblock(localUser)}\n              style={[styles.button, { borderColor: theme.color.borderDefault }]}\n            >\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.buttontext,\n                  {\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n              >\n                {t('UNBLOCK')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        ) : (\n          compactMessageComposer ? (\n          <CometChatCompactMessageComposer\n            key={messageComposerKey}\n            ref={messageComposerRef}\n            parentMessageId={parentMessageId}\n            user={localUser}\n            group={group}\n            keyboardAvoidingViewProps={{\n              ...(Platform.OS === 'android'\n                ? {}\n                : {\n                  behavior: 'padding',\n                }),\n            }}\n            disableTypingEvents={!typingIndicator}\n            hideImageAttachmentOption={!photosSharing}\n            hideVideoAttachmentOption={!videoSharing}\n            hideAudioAttachmentOption={!audioSharing}\n            hideFileAttachmentOption={!fileSharing}\n            hideCameraOption={!photosSharing}\n            disableMentions={!mentions}\n            hideStickersButton={!stickers}\n            hideCollaborativeDocumentOption={!collaborativeDocument}\n            hideCollaborativeWhiteboardOption={!collaborativeWhiteboard}\n            hidePollsAttachmentOption={!polls}\n            hideVoiceRecordingButton={!voiceNotes}\n          />\n          ) : (\n          <CometChatMessageComposer\n            key={messageComposerKey}\n            ref={messageComposerRef}\n            parentMessageId={parentMessageId}\n            user={localUser}\n            group={group}\n            keyboardAvoidingViewProps={{\n              ...(Platform.OS === 'android'\n                ? {}\n                : {\n                  behavior: 'padding',\n                }),\n            }}\n            disableTypingEvents={!typingIndicator}\n            hideImageAttachmentOption={!photosSharing}\n            hideVideoAttachmentOption={!videoSharing}\n            hideAudioAttachmentOption={!audioSharing}\n            hideFileAttachmentOption={!fileSharing}\n            hideCameraOption={!photosSharing}\n            disableMentions={!mentions}\n            hideStickersButton={!stickers}\n            hideCollaborativeDocumentOption={!collaborativeDocument}\n            hideCollaborativeWhiteboardOption={!collaborativeWhiteboard}\n            hidePollsAttachmentOption={!polls}\n            hideVoiceRecordingButton={!voiceNotes}\n          />\n          )\n        )}\n      </View>\n    </CometChatThemeProvider>\n  );\n};\n\nconst styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n\n});\n\nconst drawerStyles = StyleSheet.create({\n  backdrop: {\n    justifyContent: 'flex-start',\n    alignItems: 'flex-end',\n  },\n  drawer: {\n    width: '100%',\n    height: '100%',\n    overflow: 'hidden',\n  },\n});\n\nexport default Messages;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/OngoingCallScreen.tsx",
    "content": "import React, { useMemo, useRef } from 'react';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { navigate, navigationRef } from '../../../navigation/NavigationService';\nimport { CometChatCalls } from '@cometchat/calls-sdk-react-native';\nimport { CometChatOngoingCall } from '@cometchat/chat-uikit-react-native';\nimport { SCREEN_CONSTANTS } from '../../../utils/AppConstants';\nimport type { RouteProp } from '@react-navigation/native';\nimport { CallType, RootStackParamList } from '../../../navigation/types';\n\ntype Props = {\n  navigation: any;\n  route: RouteProp<RootStackParamList, 'OngoingCallScreen'>;\n};\n\nconst OngoingCallScreen = ({ navigation, route }: Props) => {\n  const params = route.params;\n\n  // 1) Normalize sessionId\n  const sessionID: string | undefined = useMemo(() => {\n    if ('sessionId' in params) return params.sessionId;\n    const c = (params as { call: any }).call;\n    return c?.sessionId ?? c?.getSessionId?.();\n  }, [params]);\n\n  // 2) Normalize/derive callType\n  const callType: CallType | undefined = useMemo(() => {\n    if ('callType' in params) return params.callType;\n\n    const c = (params as { call: any }).call;\n    if (!c) return undefined;\n\n    // Prefer public getter if available\n    if (typeof c?.getType === 'function') {\n      const t = c.getType();\n      if (t === CometChat.CALL_TYPE.AUDIO) return 'audio';\n      if (t === CometChat.CALL_TYPE.VIDEO) return 'video';\n    }\n\n    // Fallbacks for raw payloads\n    if (c?.callType === 'audio') return 'audio';\n    if (c?.data?.type === CometChat.CALL_TYPE.AUDIO) return 'audio';\n    if (c?.data?.type === CometChat.CALL_TYPE.VIDEO) return 'video';\n\n    return undefined;\n  }, [params]);\n\n  const isAudioOnly = callType === 'audio';\n\n  const callListener = useRef<any>(\n    new CometChatCalls.OngoingCallListener({\n      onCallEnded: () => {\n        CometChat.clearActiveCall();\n        CometChatCalls.endSession();\n        navigate('BottomTabNavigator', undefined as any);\n        navigationRef.reset({\n          index: 0,\n          routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n        });\n      },\n      onCallEndButtonPressed: () => {\n        if (sessionID) CometChat.endCall(sessionID);\n        navigate('BottomTabNavigator', undefined as any);\n        navigationRef.reset({\n          index: 0,\n          routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n        });\n      },\n    }),\n  );\n\n  const callSettings = useMemo(() => {\n    return new CometChatCalls.CallSettingsBuilder()\n      .enableDefaultLayout(true)\n      .setCallEventListener(callListener.current)\n      .setIsAudioOnlyCall(!!isAudioOnly);\n  }, [isAudioOnly]);\n\n  if (!sessionID) {\n    // Belt & suspenders: try to recover from active call if param missing\n    const active: any = CometChat.getActiveCall?.();\n    const recovered =\n      typeof active?.getSessionId === 'function'\n        ? active.getSessionId()\n        : undefined;\n    if (!recovered) return null; // or render a fallback/loading\n  }\n\n  return (\n    <CometChatOngoingCall\n      sessionID={sessionID!}\n      callSettingsBuilder={callSettings}\n    />\n  );\n};\n\nexport default OngoingCallScreen;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/SearchMessages.tsx",
    "content": "import React, { useCallback, useEffect, useRef } from 'react';\nimport {\n  View,\n  StyleSheet,\n  BackHandler,\n  Platform,\n  StatusBar,\n} from 'react-native';\nimport {\n  CometChatSearch,\n  useTheme,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { StackScreenProps } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { navigate } from '../../../navigation/NavigationService';\nimport { SCREEN_CONSTANTS } from '../../../utils/AppConstants';\n\ntype Props = StackScreenProps<RootStackParamList, 'SearchMessages'>;\n\nconst SearchMessages: React.FC<Props> = ({ route, navigation }) => {\n  const { user, group } = route.params || {};\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const insets = useSafeAreaInsets();\n\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n\n  // Update refs when navigation/route changes\n  useEffect(() => {\n    navigationRef.current = navigation;\n    routeRef.current = route;\n  }, [navigation, route]);\n\n  // Handle back button on Android\n  useEffect(() => {\n    const backAction = () => {\n      navigation.goBack();\n      return true;\n    };\n\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n\n    return () => backHandler.remove();\n  }, [navigation]);\n\n  const handleBack = useCallback(() => {\n    navigation.goBack();\n  }, [navigation]);\n\n  const handleConversationClicked = useCallback((conversation: CometChat.Conversation, searchKeyword?: string) => {\n    // Navigate to messages screen with the selected conversation\n    const conversationWith = conversation.getConversationWith();\n\n    if (conversationWith instanceof CometChat.User) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        user: conversationWith,\n        searchKeyword,\n      });\n    } else if (conversationWith instanceof CometChat.Group) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        group: conversationWith,\n        searchKeyword,\n      });\n    }\n  }, []);\n\n  const handleMessageClicked = useCallback(async (message: CometChat.BaseMessage, searchKeyword?: string) => {\n    // Navigate to messages screen and highlight the specific message\n    const messageReceiver = message.getReceiver();\n    const parentMessageId = message.getParentMessageId();\n\n    let targetUser: CometChat.User | undefined;\n    let targetGroup: CometChat.Group | undefined;\n\n    if (messageReceiver instanceof CometChat.User) {\n      // For user messages, determine if it's a direct message to/from the user\n      const sender = message.getSender();\n      const loggedInUser = await CometChat.getLoggedinUser();\n\n      if (sender.getUid() === loggedInUser?.getUid()) {\n        // Message sent by logged-in user, target is receiver\n        targetUser = messageReceiver;\n      } else {\n        // Message received by logged-in user, target is sender\n        targetUser = sender;\n      }\n    } else if (messageReceiver instanceof CometChat.Group) {\n      targetGroup = messageReceiver;\n    }\n\n    if (parentMessageId) {\n      try {\n        const parentMessage = await CometChat.getMessageDetails(parentMessageId);\n        if (parentMessage) {\n          navigate(SCREEN_CONSTANTS.THREAD_VIEW, {\n            message: parentMessage,\n            user: targetUser,\n            group: targetGroup,\n            highlightMessageId: String(message.getId()),\n          });\n          return;\n        }\n      } catch (e) {\n        console.error(\"Failed to fetch parent message\", e);\n      }\n    }\n\n    if (targetUser) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        user: targetUser,\n        messageId: String(message.getId()),\n        searchKeyword,\n        navigatedFromSearch: true,\n      });\n    } else if (targetGroup) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        group: targetGroup,\n        messageId: String(message.getId()),\n        searchKeyword,\n        navigatedFromSearch: true,\n      });\n    }\n  }, []);\n\n\n  // Determine placeholder text\n  let searchPlaceholder = \"Search\";\n  if (user && user.getName()) {\n    searchPlaceholder = `Search in ${user.getName()}`;\n  } else if (group && group.getName()) {\n    searchPlaceholder = `Search in ${group.getName()}`;\n  }\n\n  return (\n    <View style={[styles.container, Platform.OS === 'android' && { paddingTop: insets.top }]}>\n      {Platform.OS === 'ios' && (\n        <StatusBar\n          barStyle={\n            theme.mode === 'light' ? 'dark-content' : 'light-content'\n          }\n          backgroundColor={theme.color.background1}\n        />\n      )}\n\n      <CometChatSearch\n        onBack={handleBack}\n        hideBackButton={false}\n        onConversationClicked={handleConversationClicked}\n        onMessageClicked={handleMessageClicked}\n        uid={user?.getUid()}\n        guid={group?.getGuid()}\n        searchPlaceholder={searchPlaceholder}\n        messagesRequestBuilder={\n          new CometChat.MessagesRequestBuilder().setLimit(30)\n        }\n        conversationsRequestBuilder={\n          new CometChat.ConversationsRequestBuilder().setLimit(30)\n        }\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\nexport default SearchMessages;"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/ThreadView.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  StyleSheet,\n  BackHandler,\n  Platform,\n} from 'react-native';\nimport {\n  RouteProp,\n  useRoute,\n  useNavigation,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport {\n  CometChatThreadHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  CometChatMessageComposer,\n  useCometChatTranslation,\n  CometChatUIKit,\n  ChatConfigurator,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { useTheme } from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport { useConfig } from '../../../config/store';\nimport { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';\n\n\ntype ThreadViewRouteProp = RouteProp<RootStackParamList, 'ThreadView'>;\ntype ThreadViewNavProp = StackNavigationProp<RootStackParamList>;\n\nconst ThreadView = () => {\n  const { params } = useRoute<ThreadViewRouteProp>();\n  const navigation = useNavigation<ThreadViewNavProp>(); // <-- added navigation\n  const { goBack } = navigation;\n  const theme = useTheme();\n  const { message, user, group } = params || {};\n  const { t } = useCometChatTranslation();\n  const messageDeliveryAndReadReceipts = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n  const hideReactionOption = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.reactions\n  );\n  const photosSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing\n  );\n  const videoSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing\n  );\n  const audioSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing\n  );\n  const fileSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing\n  );\n  const mentions = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.mentions\n  );\n  const voiceNotes = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes\n  );\n  const stickers = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.stickers\n  );\n  const sendPrivateMessageToGroupMembers = useConfig(\n      (state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers\n  );\n  const compactMessageComposer = useConfig(\n      (state) => state.settings.layout.compactMessageComposer\n  );\n\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(\n    params?.user,\n  );\n\n  // ============================================\n  // Group Kicked/Banned Detection (real-time)\n  // ============================================\n  const isNoLongerMember = useGroupMemberStatus(group);\n\n  // keep listener ids unique\n  const userListenerId = 'thread_user_' + new Date().getTime();\n\n  // Fetch latest user on mount (if user present)\n  useEffect(() => {\n    let mounted = true;\n    const init = async () => {\n      try {\n        if (params?.user) {\n          const uid = params.user.getUid();\n          const fresh = await CometChat.getUser(uid);\n          if (mounted) setLocalUser(CommonUtils.clone(fresh));\n        }\n      } catch (err) {\n        console.error('Error fetching user in ThreadView:', err);\n      }\n    };\n    init();\n    return () => {\n      mounted = false;\n    };\n  }, [params?.user]);\n\n  // add UI listeners for block/unblock to update localUser\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (payload: { user: CometChat.User }) => {\n        setLocalUser(CommonUtils.clone(payload.user));\n      },\n      ccUserUnBlocked: (payload: { user: CometChat.User }) => {\n        setLocalUser(CommonUtils.clone(payload.user));\n      },\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n    };\n  }, []);\n\n  useEffect(() => {\n    CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n      alignment: 'composerBottom',\n      child: () => null,\n      panelId: 'sticker',\n    });\n  }, []);\n\n// B) Ensure back also asks to close (already using goBack())\nconst handleBack = useCallback(() => {\n  CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n    alignment: 'composerBottom',\n    child: () => null,\n    panelId: 'sticker',\n  });\n  navigation.goBack();\n  return true;\n}, [navigation]);\n\n  useFocusEffect(\n    useCallback(() => {\n      // Android hardware back -> just pop\n      const sub = BackHandler.addEventListener('hardwareBackPress', handleBack);\n\n      // iOS gesture/back button -> let default POP happen, but run side-effects\n      const unsub = navigation.addListener('beforeRemove', e => {\n        const type = e?.data?.action?.type;\n        if (type === 'GO_BACK' || type === 'POP') {\n          // IMPORTANT: do NOT e.preventDefault()\n          CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n            alignment: 'composerBottom',\n            child: () => null,\n            panelId: 'sticker',\n          });\n        }\n      });\n\n      return () => {\n        sub.remove();\n        unsub();\n      };\n    }, [navigation, handleBack]),\n  );\n\n  // Header back\n  <TouchableOpacity onPress={handleBack}>…</TouchableOpacity>;\n\n  // Keep gesture enabled\n  useLayoutEffect(() => {\n    navigation.setOptions?.({ gestureEnabled: true } as any);\n  }, [navigation]);\n  \n  const unblock = async (userToUnblock: CometChat.User) => {\n    try {\n      const uid = userToUnblock.getUid();\n      const response = await CometChat.unblockUsers([uid]);\n      if (response) {\n        const fresh = await CometChat.getUser(uid);\n        setLocalUser(CommonUtils.clone(fresh));\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: fresh,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user from ThreadView:', error);\n    }\n  };\n\n  const getMentionsTap = useCallback(() => {\n    const mentionsFormatter =\n      ChatConfigurator.getDataSource().getMentionsFormatter(\n        loggedInUser,\n        theme,\n      );\n    if (user) mentionsFormatter.setUser(user);\n    if (group) mentionsFormatter.setGroup(group);\n\n    mentionsFormatter.setOnMentionClick(\n      (_message: CometChat.BaseMessage, uid: string) => {\n        if (uid !== loggedInUser.getUid()) {\n          CometChat.getUser(uid)\n            .then((mentionedUser: CometChat.User) => {\n              navigation.push('Messages', {\n                user: mentionedUser,\n                fromMention: true,\n              });\n            })\n            .catch((error: any) => {\n              console.error('Error fetching mentioned user:', error);\n            });\n        }\n      },\n    );\n    return mentionsFormatter;\n  }, [user, group, loggedInUser, navigation, theme]);\n\n  const threadHeaderMentionsFormatter = useMemo(\n    () => getMentionsTap(),\n    [getMentionsTap],\n  );\n\n  return (\n    <View style={{ backgroundColor: theme.color.background1, flex: 1 }}>\n      {/* Custom Header */}\n      <View style={styles.headerStyle}>\n        <TouchableOpacity style={styles.iconStyle} onPress={handleBack}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <View style={styles.textStyle}>\n          <Text\n            style={[\n              theme.typography.heading1.bold,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            {t('THREAD')}\n          </Text>\n          <Text\n            style={[\n              theme.typography.caption1.regular,\n              {\n                color: theme.color.textSecondary,\n                maxWidth: '90%',\n              },\n            ]}\n            numberOfLines={1}\n            ellipsizeMode=\"tail\"\n          >\n            {user ? user?.getName() : group?.getName()}\n          </Text>\n        </View>\n      </View>\n\n      {/* Thread Header */}\n      <CometChatThreadHeader\n        parentMessage={message}\n        receiptsVisibility={messageDeliveryAndReadReceipts}\n        textFormatters={[threadHeaderMentionsFormatter]}\n      />\n\n      {/* Threaded Message List */}\n      <View style={{ flex: 1 }}>\n        <CometChatMessageList\n          user={user}\n          group={group}\n          parentMessageId={message.getId().toString()}\n          textFormatters={[getMentionsTap()]}\n          receiptsVisibility={messageDeliveryAndReadReceipts}\n          hideReactionOption={!hideReactionOption}\n          hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}\n          goToMessageId={params?.highlightMessageId}\n        />\n      </View>\n\n      {/* Message Composer for Thread */}\n      {isNoLongerMember ? (\n        <View\n          style={{\n            paddingVertical: 12,\n            paddingHorizontal: 16,\n            backgroundColor: theme.color.background1 as string,\n          }}\n        >\n          <Text\n            style={[\n              theme.typography.body.regular,\n              {\n                color: theme.color.textPrimary as string,\n                textAlign: 'center',\n              },\n            ]}\n          >\n            {t('GROUP_NO_LONGER_MEMBER')}\n          </Text>\n        </View>\n      ) : localUser?.getBlockedByMe() ? (\n        <View\n          style={[\n            styles.blockedContainer,\n            { backgroundColor: theme.color.background3 },\n          ]}\n        >\n          <Text\n            style={[\n              theme.typography.button.regular,\n              {\n                color: theme.color.textSecondary,\n                textAlign: 'center',\n                paddingBottom: 10,\n              },\n            ]}\n          >\n            {t('BLOCKED_USER_DESC')}\n          </Text>\n          <TouchableOpacity\n            onPress={() => unblock(localUser)}\n            style={[styles.button, { borderColor: theme.color.borderDefault }]}\n          >\n            <Text\n              style={[\n                theme.typography.button.medium,\n                styles.buttontext,\n                {\n                  color: theme.color.textPrimary,\n                },\n              ]}\n            >\n              {t('UNBLOCK')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      ) : (\n        compactMessageComposer ? (\n        <CometChatCompactMessageComposer\n          user={localUser}\n          group={group}\n          parentMessageId={message.getId()}\n          onError={(error: any) => console.error('Composer Error:', error)}\n          keyboardAvoidingViewProps={\n            Platform.OS === 'android' ? {} : { behavior: 'padding' }\n          }\n          hideImageAttachmentOption={!photosSharing}\n          hideVideoAttachmentOption={!videoSharing}\n          hideAudioAttachmentOption={!audioSharing}\n          hideFileAttachmentOption={!fileSharing}\n          hideCameraOption={!photosSharing}\n          disableMentions={!mentions}\n          hideVoiceRecordingButton={!voiceNotes}\n          hideStickersButton={!stickers}\n        />\n        ) : (\n        <CometChatMessageComposer\n          user={localUser}\n          group={group}\n          parentMessageId={message.getId()}\n          onError={(error: any) => console.error('Composer Error:', error)}\n          keyboardAvoidingViewProps={\n            Platform.OS === 'android' ? {} : { behavior: 'padding' }\n          }\n          hideImageAttachmentOption={!photosSharing}\n          hideVideoAttachmentOption={!videoSharing}\n          hideAudioAttachmentOption={!audioSharing}\n          hideFileAttachmentOption={!fileSharing}\n          hideCameraOption={!photosSharing}\n          disableMentions={!mentions}\n          hideVoiceRecordingButton={!voiceNotes}\n          hideStickersButton={!stickers}\n        />\n        )\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  headerStyle: {\n    paddingVertical: 10,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  iconStyle: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  textStyle: {\n    paddingLeft: 10,\n    alignItems: 'flex-start',\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n});\n\nexport default ThreadView;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/TransferOwnership.tsx",
    "content": "import React, {useState} from 'react';\nimport {View, Text, TouchableOpacity} from 'react-native';\nimport {RouteProp} from '@react-navigation/native';\nimport {\n  CometChatGroupMembers,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../../navigation/types';\nimport {styles} from './TransferOwnershipStyles';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport {CommonUtils} from '../../../utils/CommonUtils';\n\ntype TransferOwnershipScreenProps = {\n  route: RouteProp<RootStackParamList, 'TransferOwnershipSection'>;\n  navigation: StackNavigationProp<\n    RootStackParamList,\n    'TransferOwnershipSection'\n  >;\n};\n\nconst TransferOwnership: React.FC<TransferOwnershipScreenProps> = ({\n  route,\n  navigation,\n}) => {\n  const {group} = route.params;\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [selectedOwnershipMember, setSelectedOwnershipMember] =\n    useState<CometChat.User | null>(null);\n\n  const handleBack = () => {\n    navigation.goBack();\n  };\n\n  // Function to leave the group after ownership transfer\n  const leaveGroup = (group: CometChat.Group) => {\n    CometChat.leaveGroup(group.getGuid())\n      .then(() => {\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          leftGroup: CommonUtils.clone(group),\n        });\n        navigation.pop(3);\n      })\n      .catch(error => {\n        console.log('Group leaving failed with exception:', error);\n      });\n  };\n\n  const handleTransferOwnership = async () => {\n    if (!selectedOwnershipMember || !group) return;\n    try {\n      await CometChat.transferGroupOwnership(\n        group.getGuid(),\n        selectedOwnershipMember.getUid(),\n      );\n      leaveGroup(group);\n    } catch (error) {\n      console.error('Ownership transfer failed:', error);\n    }\n  };\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {/* Header */}\n      <View style={styles.headerSection}>\n        <TouchableOpacity\n          style={styles.backButtonContainer}\n          onPress={handleBack}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.leftPaddingSmall,\n            {color: theme.color.textPrimary},\n          ]}>\n          {t('TRANSFER_OWNERSHIP')}\n        </Text>\n      </View>\n\n      {/* Group Members List */}\n      {group && (\n        <CometChatGroupMembers\n          group={group}\n          excludeOwner={true}\n          onBack={handleBack}\n          hideHeader={true}\n          selectionMode=\"single\"\n          onSelection={members => {\n            setSelectedOwnershipMember(\n              members && members.length > 0 ? members[0] : null,\n            );\n          }}\n        />\n      )}\n\n      {/* Transfer Ownership Button */}\n      <TouchableOpacity\n        onPress={handleTransferOwnership}\n        style={styles.transferButtonWrapper}>\n        <View\n          style={[\n            styles.transferButtonContent,\n            {backgroundColor: theme.color.primaryButtonBackground},\n          ]}>\n          <Text\n            style={[\n              theme.typography.heading4.medium,\n              styles.centerAligned,\n              {color: theme.color.primaryButtonText},\n            ]}>\n            {t('TRANSFER_OWNERSHIP')}\n          </Text>\n        </View>\n      </TouchableOpacity>\n    </View>\n  );\n};\n\nexport default TransferOwnership;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/TransferOwnershipStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  headerSection: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  backButtonContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  leftPaddingSmall: {\n    paddingLeft: 5,\n  },\n  transferButtonWrapper: {\n    alignContent: 'center',\n    justifyContent: 'center',\n    paddingVertical: 1,\n    height: '7%',\n    width: '100%',\n    alignSelf: 'center',\n  },\n  transferButtonContent: {\n    marginHorizontal: 20,\n    alignSelf: 'center',\n    justifyContent: 'center',\n    borderRadius: 6,\n    height: '75%',\n    width: '95%',\n  },\n  centerAligned: {\n    alignSelf: 'center',\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/UserInfo.tsx",
    "content": "import React, {useState, useEffect, useRef, FC} from 'react';\nimport {View, Text, TouchableOpacity, BackHandler} from 'react-native';\nimport {\n  CometChatAvatar,\n  useTheme,\n  CometChatUIEventHandler,\n  CallUIEvents,\n  CometChatConversationEvents,\n  CometChatConfirmDialog,\n  CometChatOutgoingCall,\n  useCometChatTranslation,\n  getLastSeenTime,\n} from '@cometchat/chat-uikit-react-native';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {permissionUtil} from '@cometchat/chat-uikit-react-native/src/shared/utils/PermissionUtil';\nimport {CallTypeConstants, UserStatusConstants} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport { blockUser, unblock } from '../../../utils/helper';\nimport {styles} from './UserInfoStyles';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport Block from '../../../assets/icons/Block';\nimport Delete from '../../../assets/icons/Delete';\nimport Videocam from '../../../assets/icons/VideoCam';\nimport Call from '../../../assets/icons/Call';\nimport {StackNavigationProp, StackScreenProps} from '@react-navigation/stack';\nimport {\n  RootStackParamList,\n} from '../../../navigation/types';\nimport {useFocusEffect} from '@react-navigation/native';\nimport { useConfig } from '../../../config/store';\n\n\ntype ScreenProps = StackScreenProps<RootStackParamList, 'UserInfo'>;\ntype NavigationProps = StackNavigationProp<RootStackParamList, 'UserInfo'>;\ntype Props = ScreenProps & {navigation: NavigationProps};\n\nconst UserInfo: FC<Props> = ({route, navigation}) => {\n  const {user} = route.params;\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [userObj, setUserObj] = useState<CometChat.User>(user);\n  /** STATES **/\n  const [disableButton, setDisableButton] = useState<boolean>(false);\n  const [blocked, setBlocked] = useState<boolean>(false);\n\n  // separate modal states\n  const [isBlockModalOpen, setBlockModalOpen] = useState(false);\n  const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);\n\n  const [userStatus, setUserStatus] = useState(userObj.getStatus());\n  const listenerId = useRef<string>('CallListener_' + Date.now());\n  const userStatusListenerId = 'user_status_' + new Date().getTime();\n\n  const [callObj, setCallObj] = useState<CometChat.Call>();\n\n  const callType = useRef<string | undefined>(undefined);\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n\n  useEffect(() => {\n    setUserStatus(userObj.getStatus());\n    setBlocked(userObj.getBlockedByMe());\n\n    CometChat.addCallListener(\n      listenerId.current,\n      new CometChat.CallListener({\n        onIncomingCallReceived: () => {\n          setDisableButton(true);\n        },\n        onOutgoingCallAccepted: () => {\n          console.log('call accepted');\n        },\n        onOutgoingCallRejected: () => {\n          setDisableButton(false);\n          setCallObj(undefined);\n        },\n        onIncomingCallCancelled: () => {\n          setDisableButton(false);\n        },\n      }),\n    );\n    CometChatUIEventHandler.addCallListener(listenerId.current, {\n      ccCallRejected: () => {\n        setDisableButton(false);\n        setCallObj(undefined);\n      },\n      ccCallEnded: () => {\n        setDisableButton(false);\n        setCallObj(undefined);\n      },\n    });\n\n    CometChat.addUserListener(\n      userStatusListenerId,\n      new CometChat.UserListener({\n        onUserOnline: (onlineUser: CometChat.User) => {\n          if (onlineUser.getUid() === userObj.getUid()) {\n            setUserObj(onlineUser);\n            setUserStatus(onlineUser.getStatus());\n          }\n        },\n        onUserOffline: (offlineUser: CometChat.User) => {\n          if (offlineUser.getUid() === userObj.getUid()) {\n            setUserObj(offlineUser);\n            setUserStatus(offlineUser.getStatus());\n          }\n        },\n      }),\n    );\n\n    return () => {\n      CometChat.removeCallListener(listenerId.current);\n      CometChatUIEventHandler.removeCallListener(listenerId.current);\n      CometChat.removeUserListener(userStatusListenerId);\n    };\n  }, [userObj]);\n\n  const translations = {\n    lastSeen: 'Last seen',\n    minutesAgo: (minutes: number) =>\n      `${minutes} minute${minutes === 1 ? '' : 's'} ago`,\n    hoursAgo: (hours: number) => `${hours} hour${hours === 1 ? '' : 's'} ago`,\n  };\n\n  const makeVoiceCall = async (): Promise<void> => {\n    if (disableButton) return;\n    if (!(await permissionUtil.startResourceBasedTask(['mic']))) return;\n    callType.current = CallTypeConstants.audio;\n    makeCall(CallTypeConstants.audio);\n  };\n\n  const makeVideoCall = async (): Promise<void> => {\n    if (disableButton) return;\n    if (!(await permissionUtil.startResourceBasedTask(['mic', 'camera'])))\n      return;\n    callType.current = CallTypeConstants.video;\n    makeCall(CallTypeConstants.video);\n  };\n\n  const makeCall = (type: string): void => {\n    if (type === CallTypeConstants.audio || type === CallTypeConstants.video) {\n      const receiverID = userObj.getUid();\n      const callTypeValue = type;\n      const receiverType = CometChat.RECEIVER_TYPE.USER;\n      if (!receiverID || !receiverType) return;\n\n      const call = new CometChat.Call(\n        receiverID,\n        callTypeValue,\n        receiverType,\n        CometChat.CATEGORY_CALL,\n      );\n\n      CometChat.initiateCall(call).then(\n        initiatedCall => {\n          setCallObj(initiatedCall);\n          setDisableButton(true);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, {\n            call: initiatedCall,\n          });\n        },\n        error => {\n          console.log('Call initialization failed with exception:', error);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, {\n            call,\n          });\n        },\n      );\n    }\n  };\n\n  /** BLOCK/UNBLOCK LOGIC **/\n  const handleBlockUnblockConfirm = () => {\n    setBlockModalOpen(false); // close the dialog\n    if (blocked) {\n      // user is already blocked by me -> now unblocking\n      unblock(userObj.getUid(), userObj, setBlocked, setUserObj);\n    } else {\n      // user is not blocked -> blocking user\n      blockUser(userObj.getUid(), userObj, setBlocked);\n    }\n  };\n\n  /** DELETE CONVERSATION LOGIC **/\n  const handleDeleteConversationConfirm = () => {\n    setDeleteModalOpen(false); // close the dialog\n    if (userObj) {\n      CometChat.getConversation(userObj.getUid(), 'user')\n        .then(conversation => {\n          CometChat.deleteConversation(userObj.getUid(), 'user')\n            .then(deletedConversation => {\n              console.log(deletedConversation);\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccConversationDeleted,\n                {conversation: conversation},\n              );\n              navigation.pop(2);\n            })\n            .catch(error => {\n              console.log('Error while deleting conversation:', error);\n            });\n        })\n        .catch(error => {\n          console.log('Error while deleting conversation:', error);\n        });\n    }\n  };\n\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {/* Header */}\n      <View style={styles.headerContainer}>\n        <TouchableOpacity\n          style={styles.backButtonContainer}\n          onPress={() => navigation.goBack()}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.smallPaddingLeft,\n            {color: theme.color.textPrimary},\n          ]}>\n          {t('USER_INFO')}\n        </Text>\n      </View>\n\n      {/* User Information Section */}\n      <View\n        style={[styles.profileCard, {borderColor: theme.color.borderLight}]}>\n        <View style={styles.profileInfo}>\n          <CometChatAvatar\n            style={{\n              containerStyle: styles.avatarContainer,\n              textStyle: styles.avatarText,\n              imageStyle: styles.avatarImage,\n            }}\n            image={\n              userObj?.getAvatar() ? {uri: userObj.getAvatar()} : undefined\n            }\n            name={userObj?.getName() ?? ''}\n          />\n          <Text\n            style={[\n              theme.typography.heading3.medium,\n              styles.mt10Centered,\n              {color: theme.color.textPrimary},\n            ]}>\n            {userObj?.getName()}\n          </Text>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.mt5Centered,\n              {color: theme.color.textSecondary},\n            ]}>\n            {userObj &&\n              !(userObj.getBlockedByMe() || userObj.getHasBlockedMe()) &&\n              (userStatus === UserStatusConstants.online\n                ? t('ONLINE')\n                : getLastSeenTime(userObj.getLastActiveAt()))}\n          </Text>\n        </View>\n\n        {/* Action Boxes */}\n        {(oneOnOneVoiceCalling || oneOnOneVideoCalling) && (\n          <View style={styles.actionsRow}>\n            {oneOnOneVoiceCalling && (\n              <TouchableOpacity\n                style={[\n                  styles.callActionButton,\n                  {borderColor: theme.color.borderDefault},\n                ]}\n                onPress={makeVoiceCall}>\n                <Icon\n                  icon={<Call color={theme.color.primary} height={24} width={24} />}\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.mt5Centered,\n                    { color: theme.color.textSecondary },\n                  ]}>\n                  {t('AUDIO_CALL')}\n                </Text>\n              </TouchableOpacity>\n            )}\n\n            {oneOnOneVideoCalling && (\n              <TouchableOpacity\n                style={[\n                  styles.callActionButton,\n                  {borderColor: theme.color.borderDefault},\n                ]}\n                onPress={makeVideoCall}>\n                <Icon\n                  icon={\n                    <Videocam color={theme.color.primary} height={24} width={24} />\n                  }\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.mt5Centered,\n                    { color: theme.color.textSecondary },\n                  ]}>\n                  {t('VIDEO_CALL')}\n                </Text>\n              </TouchableOpacity>\n            )}\n          </View>\n        )}\n      </View>\n\n      {/* Block/Unblock and Delete Chat Buttons */}\n      <View style={styles.optionsContainer}>\n        <View style={styles.optionRow}>\n          <TouchableOpacity\n            onPress={() => setBlockModalOpen(true)}\n            style={styles.backButtonContainer}>\n            <Icon\n              icon={<Block color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.ml5,\n                {color: theme.color.error},\n              ]}>\n              {blocked ? t('UNBLOCK') : t('BLOCK')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n\n        {/* DELETE CHAT */}\n        <View style={styles.optionRow}>\n          <TouchableOpacity\n            onPress={() => setDeleteModalOpen(true)}\n            style={styles.backButtonContainer}>\n            <Icon\n              icon={<Delete color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.ml5,\n                { color: theme.color.error },\n              ]}>\n              {t('DELETE_CHAT_TEXT')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      </View>\n\n      {/* =============== BLOCK/UNBLOCK MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isBlockModalOpen}\n        onCancel={() => setBlockModalOpen(false)}\n        onConfirm={handleBlockUnblockConfirm}\n        onDismiss={() => console.log('Block/Unblock Modal dismissed')}\n        titleText={\n          blocked ? t('UNBLOCK_CONTACT') : t('BLOCK_USER')\n        }\n        messageText={\n          blocked ? t('UNBLOCK_SURE') : t('BLOCK_SURE')\n        }\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={blocked ? t('UNBLOCK') : t('BLOCK')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* =============== DELETE CHAT MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteModalOpen}\n        onCancel={() => setDeleteModalOpen(false)}\n        onConfirm={handleDeleteConversationConfirm}\n        onDismiss={() => console.log('Delete Modal dismissed')}\n        titleText={t('DELETE_CHAT')}\n        messageText={t('SURE_TO_DELETE_CHAT')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n\n      {callObj && (\n        <CometChatOutgoingCall\n          call={callObj}\n          onEndCallButtonPressed={call => {\n            CometChat.rejectCall(\n              call?.getSessionId(),\n              CometChat.CALL_STATUS.CANCELLED,\n            ).then(\n              rejectedCall => {\n                console.log('🚀 ~ rejectedCall:', rejectedCall);\n                CometChatUIEventHandler.emitCallEvent(\n                  CallUIEvents.ccCallRejected,\n                  {\n                    call: rejectedCall,\n                  },\n                );\n                setCallObj(undefined);\n              },\n              err => {\n                console.log('🚀 ~ err:', err);\n                setCallObj(undefined);\n                // onError && onError(err);\n              },\n            );\n          }}\n        />\n      )}\n    </View>\n  );\n};\n\nexport default UserInfo;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/UserInfoStyles.tsx",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n    headerContainer: {\n      paddingTop: 15,\n      paddingLeft: 10,\n      flexDirection: 'row',\n    },\n    backButtonContainer: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    smallPaddingLeft: {\n      paddingLeft: 5,\n    },\n    profileCard: {\n      marginTop: 12,\n      borderTopWidth: 1,\n      borderBottomWidth: 1,\n      paddingVertical: 10,\n    },\n    profileInfo: {\n      alignSelf: 'center',\n      alignItems: 'center',\n      marginTop: 20,\n    },\n    avatarContainer: {\n      height: 120,\n      width: 120,\n    },\n    avatarText: {\n      fontSize: 28,\n      lineHeight: 55,\n    },\n    avatarImage: {\n      height: '100%',\n      width: '100%',\n    },\n    mt10Centered: {\n      marginTop: 10,\n      alignSelf: 'center',\n    },\n    mt5Centered: {\n      marginTop: 5,\n      textAlign: 'center',\n      alignSelf: 'center',\n    },\n    actionsRow: {\n      flexDirection: 'row',\n      paddingHorizontal: 20,\n      justifyContent: 'space-between',\n      marginVertical: 20,\n    },\n    callActionButton: {\n      flex: 1,\n      paddingVertical: 10,\n      borderWidth: 1,\n      borderRadius: 8,\n      justifyContent: 'center',\n      alignItems: 'center',\n      marginHorizontal: 5,\n    },\n    optionsContainer: {\n      paddingTop: 10,\n      gap: 4,\n    },\n    optionRow: {\n      flexDirection: 'row',\n      alignItems: 'center',\n      paddingVertical: 8,\n      paddingLeft: 20,\n      width: '100%',\n    },\n    ml5: {\n      marginLeft: 5,\n    },\n  });\n  "
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/ViewMembers.tsx",
    "content": "import React from 'react';\nimport { BackHandler, View } from 'react-native';\nimport {\n  CometChatGroupMembers,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  useRoute,\n  useNavigation,\n  RouteProp,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { useConfig } from '../../../config/store';\n\nconst ViewMembers: React.FC = () => {\n  const route = useRoute<RouteProp<RootStackParamList, 'ViewMembers'>>();\n  const navigation = useNavigation();\n  const { group } = route.params;\n  const theme = useTheme();\n  const kickUsers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.kickUsers\n  );\n  const banUsers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.banUsers\n  );\n  const promoteDemoteMembers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.promoteDemoteMembers\n  );\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n   );\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  return (\n    <View style={[{ flex: 1, backgroundColor: theme.color.background1 }]}>\n      <CometChatGroupMembers\n        group={group}\n        onBack={() => {\n          navigation.goBack();\n        }}\n        selectionMode=\"none\"\n        showBackButton={true}\n        hideKickMemberOption={!kickUsers}\n        hideBanMemberOption={!banUsers}\n        hideScopeChangeOption={!promoteDemoteMembers}\n        usersStatusVisibility={userAndFriendsPresence}\n      />\n    </View>\n  );\n};\n\nexport default ViewMembers;\n"
  },
  {
    "path": "examples/SampleApp/src/components/conversations/screens/qr_screen.tsx",
    "content": "import React, { useEffect, useState, useRef } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  Alert,\n  TouchableOpacity,\n  Image,\n} from 'react-native';\nimport { Camera, useCameraDevice, useCodeScanner } from 'react-native-vision-camera';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { useNavigation } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { Animated, Easing } from 'react-native';\nimport Flash from '../../../assets/icons/Flash';\nimport sync from '../../../assets/icons/cometchat_sync_img.png';\n\ninterface ConfigData {\n  data: {\n    builderId: string;\n    settings: any;\n    name: string;\n    type: string;\n    createdAt: number;\n    updatedAt: number;\n    expiresAt: number;\n  };\n}\n\nconst CONFIG_STORAGE_KEY = '@app_config';\nconst MINIMUM_LOADING_TIME = 5000;\n\nconst QRScreen: React.FC = () => {\n  const device = useCameraDevice('back');\n  const [hasPermission, setHasPermission] = useState(false);\n  const [scannedCode, setScannedCode] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const [lineAnim] = useState(new Animated.Value(0));\n  const [isFlashOn, setIsFlashOn] = useState(false);\n  const [loadingProgress] = useState(new Animated.Value(0));\n  \n  const apiResponseReceived = useRef(false);\n  const loadingStartTime = useRef<number>(0);\n  \n  type ChatNavigationProp = StackNavigationProp<\n    RootStackParamList,\n    'Conversation'\n  >;\n  const navigation = useNavigation<ChatNavigationProp>();\n  \n  const codeScanner = useCodeScanner({\n    codeTypes: ['qr', 'ean-13'],\n    onCodeScanned: (codes) => {\n      if (codes.length > 0 && !isLoading && !scannedCode) {\n        const qrData = codes[0].value ?? '';\n        setScannedCode(qrData);\n        fetchDataFromAPI(qrData);\n      }\n    },\n  });\n\n  useEffect(() => {\n    Animated.loop(\n      Animated.sequence([\n        Animated.timing(lineAnim, {\n          toValue: 1,\n          duration: 2000,\n          easing: Easing.linear,\n          useNativeDriver: true,\n        }),\n        Animated.timing(lineAnim, {\n          toValue: 0,\n          duration: 2000,\n          easing: Easing.linear,\n          useNativeDriver: true,\n        }),\n      ])\n    ).start();\n  }, [lineAnim]);\n\n  const toggleFlash = () => {\n    setIsFlashOn(prev => !prev);\n  };\n\n  useEffect(() => {\n    const requestPermissions = async () => {\n      let cameraStatus = await Camera.getCameraPermissionStatus();\n\n      if (cameraStatus !== 'granted') {\n        cameraStatus = await Camera.requestCameraPermission();\n      }\n\n      if (cameraStatus === 'granted') {\n        setHasPermission(true);\n      } else {\n        Alert.alert(\n          'Camera permission denied',\n          'Please enable camera permission in your settings to scan QR codes'\n        );\n      }\n    };\n\n    requestPermissions();\n  }, []);\n\n  const saveConfigToStorage = async (configData: ConfigData) => {\n    try {\n      // Save to AsyncStorage\n      await AsyncStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(configData));\n      try {\n        const { useConfigStore } = await import('../../../config/store');\n        // Use the data property which contains the actual config structure\n        useConfigStore.setState({ config: configData.data });        \n        await AsyncStorage.setItem('@config_updated', 'true');\n      } catch (importError) {\n        console.log('Config store not available or failed to update:', importError);\n      }\n\n      return true;\n    } catch (error) {\n      console.error('Error saving config to AsyncStorage:', error);\n      throw error;\n    }\n  };\n\n  const finishLoading = () => {\n    setIsLoading(false);\n    navigation.goBack();\n  };\n\n  const fetchDataFromAPI = async (qrCodeData: string) => {\n    try {\n      setIsLoading(true);\n      apiResponseReceived.current = false;\n      loadingStartTime.current = Date.now();\n      \n      // Start progress bar animation (5 seconds to match minimum loading time)\n      loadingProgress.setValue(0);\n      Animated.timing(loadingProgress, {\n        toValue: 1,\n        duration: MINIMUM_LOADING_TIME,\n        easing: Easing.ease,\n        useNativeDriver: false,\n      }).start();\n      \n      // QR code contains the builder ID, construct the API URL\n      const builderId = qrCodeData.trim();\n      const apiUrl = `https://apivcb.cometchat.io/v1/builders/${builderId}`;\n      \n      const response = await fetch(apiUrl, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      });\n\n      if (!response.ok) {\n        throw new Error(`API request failed with status ${response.status}`);\n      }\n\n      const data: ConfigData = await response.json();\n      await saveConfigToStorage(data);\n      \n      apiResponseReceived.current = true;\n      \n      const elapsedTime = Date.now() - loadingStartTime.current;\n      const remainingTime = Math.max(0, MINIMUM_LOADING_TIME - elapsedTime);\n      \n      setTimeout(() => {\n        finishLoading();\n      }, remainingTime);\n      \n      return data;\n    } catch (error) {\n      console.error('Error fetching data from API:', error);\n      Alert.alert(\n        'Error',\n        `Failed to update configuration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n        [\n          {\n            text: 'OK',\n            onPress: () => {\n              setIsLoading(false);\n              setScannedCode(null);\n              apiResponseReceived.current = false;\n            }\n          }\n        ]\n      );\n    }\n  };\n\n\n\n  if (!device) {\n    return (\n      <View style={styles.center}>\n        <Text style={styles.centerText}>Loading camera...</Text>\n      </View>\n    );\n  }\n\n  if (!hasPermission) {\n    return (\n      <View style={styles.center}>\n        <Text style={styles.centerText}>No camera permission</Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Camera\n        style={StyleSheet.absoluteFill}\n        device={device}\n        isActive={!isLoading}\n        codeScanner={codeScanner}\n        torch={isFlashOn ? 'on' : 'off'}\n      />\n      \n      {/* Top Overlay */}\n      <View style={styles.topOverlay}>\n        {/* Header */}\n        <View style={styles.header}>\n          <TouchableOpacity \n            style={styles.closeButton} \n            onPress={() => navigation.goBack()}\n          >\n            <Text style={styles.closeIcon}>✕</Text>\n          </TouchableOpacity>\n          \n         <TouchableOpacity style={styles.flashButton} onPress={toggleFlash}>\n            <Flash color={'#FFFFFF'} width={24} height={24} />\n        </TouchableOpacity>\n\n        </View>\n\n        {/* Title Section */}\n        <View style={styles.titleSection}>\n          <Text style={styles.title}>Scan to Preview Your Chat UI</Text>\n          <Text style={styles.subtitle}>\n            Instantly load and test your configuration{'\\n'}from the Visual builder.\n          </Text>\n        </View>\n      </View>\n\n      {/* Simple full overlay approach */}\n      <View style={styles.overlayTop} />\n      <View style={styles.overlayLeft} />\n      <View style={styles.overlayRight} />\n      <View style={styles.overlayBottom} />\n\n      {/* Bottom Overlay */}\n      <View style={styles.bottomOverlay}>\n        {/* Instructions Section */}\n        <View style={styles.instructionsSection}>\n          <Text style={styles.instructionsTitle}>How to Use:</Text>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>1.</Text>\n            <Text style={styles.stepText}>Go to the Visual builder & generate QR code.</Text>\n          </View>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>2.</Text>\n            <Text style={styles.stepText}>Point your camera at the QR code to scan.</Text>\n          </View>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>3.</Text>\n            <Text style={styles.stepText}>Preview your design live on this device instantly.</Text>\n          </View>\n\n          {/* Note Section */}\n          <View style={styles.noteSection}>\n            <Text style={styles.noteTitle}>Note:</Text>\n            <Text style={styles.noteText}>\n              Make sure to save changes on the builder before scanning again to view updates.\n            </Text>\n          </View>\n        </View>\n      </View>\n\n      {/* Camera Container Border */}\n      \n      <View style={styles.cameraBorder}>\n        <Animated.View\n          style={[\n            styles.animatedLine,\n            {\n              transform: [\n                {\n                  translateY: lineAnim.interpolate({\n                    inputRange: [0, 1],\n                    outputRange: [0, 370], \n                  }),\n                },\n              ],\n            },\n          ]}\n        />\n      </View>\n\n      {/* Loading Overlay */}\n      {isLoading && (\n        <View style={styles.loadingOverlay}>\n          <View style={styles.loadingContent}>\n            <Image \n              source={sync} \n              style={styles.syncImage}\n              resizeMode=\"contain\"\n            />\n            <Text style={styles.loadingText}>Syncing with Visual Builder...</Text>\n            <View style={styles.progressBarContainer}>\n              <Animated.View \n                style={[\n                  styles.progressBarFill,\n                  {\n                    width: loadingProgress.interpolate({\n                      inputRange: [0, 1],\n                      outputRange: ['0%', '100%'],\n                    }),\n                  },\n                ]}\n              />\n            </View>\n          </View>\n        </View>\n      )}\n\n      {/* Scanned Code Display */}\n      {scannedCode && !isLoading && (\n        <View style={styles.resultBox}>\n          <Text style={styles.resultLabel}>Scanned:</Text>\n          <Text style={styles.resultText} numberOfLines={2}>\n            {scannedCode}\n          </Text>\n        </View>\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: { \n    flex: 1, \n    backgroundColor: 'black' ,\n  },\n  center: { \n    flex: 1, \n    alignItems: 'center', \n    justifyContent: 'center', \n    backgroundColor: 'black'\n  },\n  centerText: {\n    color: 'white',\n    fontSize: 16,\n  },\n  topOverlay: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    height: 180,\n    zIndex: 3,\n  },\n  overlayTop: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    height: 180,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayLeft: {\n    position: 'absolute',\n    top: 180,\n    left: 0,\n    width: 20,\n    height: 370,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayRight: {\n    position: 'absolute',\n    top: 180,\n    right: 0,\n    width: 20,\n    height: 370,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayBottom: {\n    position: 'absolute',\n    top: 550,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  animatedLine: {\n    position: 'absolute',\n    left: 0,\n    right: 0,\n    height: 3,\n    backgroundColor: '#6366f1',\n    borderRadius: 2,\n  },\n  bottomOverlay: {\n    position: 'absolute',\n    bottom: 0,\n    left: 0,\n    right: 0,\n    top: 560,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 3,\n  },\n  cameraBorder: {\n    position: 'absolute',\n    top: 180,\n    left: 20,\n    right: 20,\n    height: 370,\n    zIndex: 4,\n  },\n  header: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    paddingTop: 20,\n    paddingBottom: 20,\n  },\n  closeButton: {\n    width: 40,\n    height: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  closeIcon: {\n    color: '#FFFFFF',\n    fontSize: 24,\n    fontWeight: 'bold',\n  },\n  flashButton: {\n    width: 40,\n    height: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  flashIcon: {\n    fontSize: 24,\n  },\n  titleSection: {\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    marginBottom: 30,\n  },\n  title: {\n    color: '#FFFFFF',\n    fontSize: 24,\n    fontWeight: 'bold',\n    textAlign: 'center',\n    marginBottom: 12,\n  },\n  subtitle: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    textAlign: 'center',\n    lineHeight: 22,\n  },\n  instructionsSection: {\n    flex: 1,\n    paddingHorizontal: 20,\n    paddingBottom: 40,\n  },\n  instructionsTitle: {\n    color: '#FFFFFF',\n    fontSize: 18,\n    fontWeight: 'bold',\n    marginBottom: 16,\n  },\n  stepContainer: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n    marginBottom: 12,\n  },\n  stepNumber: {\n    color: '#FFFFFF',\n    fontSize: 16,\n    fontWeight: '600',\n    marginRight: 8,\n    minWidth: 20,\n  },\n  stepText: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    lineHeight: 24,\n    flex: 1,\n  },\n  noteSection: {\n    backgroundColor: '#0B7BEA33',\n    borderRadius: 8,\n    padding: 16,\n    marginTop: 20,\n    borderLeftWidth: 4,\n  },\n  noteTitle: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    fontWeight: 'bold',\n    marginBottom: 8,\n  },\n  noteText: {\n    color: '#CCCCCC',\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  resultBox: {\n    position: 'absolute',\n    bottom: 130,\n    alignSelf: 'center',\n    padding: 12,\n    backgroundColor: 'rgba(0,0,0,0.8)',\n    borderRadius: 8,\n    maxWidth: '80%',\n    borderWidth: 1,\n    borderColor: '#6366f1',\n    zIndex: 2,\n  },\n  resultLabel: {\n    color: '#6366f1',\n    fontSize: 12,\n    fontWeight: 'bold',\n    marginBottom: 4,\n  },\n  resultText: { \n    color: 'white', \n    fontSize: 14 \n  },\n  loadingOverlay: {\n    ...StyleSheet.absoluteFillObject,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    justifyContent: 'center',\n    alignItems: 'center',\n    zIndex: 999,\n  },\n  loadingContent: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    width: '100%',\n  },\n  syncImage: {\n    width: 300,\n    height: 455,\n    marginBottom: 24,\n  },\n  loadingText: {\n    color: '#000000',\n    fontSize: 18,\n    fontWeight: '600',\n    marginBottom: 24,\n  },\n  progressBarContainer: {\n    width: 280,\n    height: 8,\n    backgroundColor: '#E5E7EB',\n    borderRadius: 4,\n    overflow: 'hidden',\n  },\n  progressBarFill: {\n    height: '100%',\n    backgroundColor: '#6366f1',\n    borderRadius: 4,\n  },\n});\n\nexport default QRScreen;"
  },
  {
    "path": "examples/SampleApp/src/components/groups/GroupHelper.tsx",
    "content": "import React, {useEffect, useState} from 'react';\nimport {\n  Dimensions,\n  View,\n  Text,\n  TouchableOpacity,\n  TextInput,\n  TouchableWithoutFeedback,\n} from 'react-native';\nimport {\n  CometChatAvatar,\n  CometChatBottomSheet,\n  CometChatUIKitHelper,\n  Icon,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {styles} from './styles';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport GroupAdd from '../../assets/icons/GroupAdd';\nimport Group from '../../assets/icons/Group';\n\n/**\n * Subcomponent for the default AppBar Options\n * (e.g., the \"Add Group\" button/icon on the header).\n */\nexport const GroupScreenAppBarOptions: React.FC<{\n  onPress: () => void;\n}> = ({onPress}) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n\n  return (\n    <View style={{paddingRight: 10}}>\n      <TouchableOpacity onPress={onPress}>\n        <Icon\n          icon={<GroupAdd color={theme.color.primary} height={28} width={28} />}\n          size={28}\n        />\n      </TouchableOpacity>\n    </View>\n  );\n};\n\n/**\n * Bottom sheet for creating a new group.\n */\ninterface CreateGroupBottomSheetProps {\n  visible: boolean;\n  onClose: () => void;\n  onGroupCreated: (group: CometChat.Group) => void;\n}\n\nexport const CreateGroupBottomSheet: React.FC<CreateGroupBottomSheetProps> = ({\n  visible,\n  onClose,\n  onGroupCreated,\n}) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n\n  // Group state\n  const [groupName, setGroupName] = useState('');\n  const [groupPassword, setGroupPassword] = useState('');\n  const [selectedOption, setSelectedOption] = useState('Public');\n  const [showPasswordField, setShowPasswordField] = useState(false);\n  const [showError, setShowError] = useState('');\n  const groupTypes = [\n    { display: t('PUBLIC'), value: 'Public' },\n    { display: t('PRIVATE'), value: 'Private' },\n    { display: t('PASSWORD'), value: 'Password' }\n  ];\n\n  const resetFields = () => {\n    setGroupName('');\n    setGroupPassword('');\n    setSelectedOption('Public');\n    setShowPasswordField(false);\n    setShowError('');\n  };\n\n  const handleDismiss = () => {\n    resetFields(); // clear everything the user typed/selected\n    onClose(); // let the parent know the sheet closed\n  };\n\n  useEffect(() => {\n    if (!showError) return;\n    const timer = setTimeout(() => setShowError(''), 3000);\n    return () => clearTimeout(timer);\n  }, [showError]);\n\n  const handleOptionPress = (option: { display: string, value: string }) => {\n    setSelectedOption(option.value);\n    setShowPasswordField(option.value === 'Password');\n  };\n\n  const handleCreateGroup = async () => {\n    if (!groupName.trim()) {\n      setShowError('Group name cannot be empty');\n      return;\n    }\n    const GUID = 'group_' + Date.now();\n    let groupType = CometChat.GROUP_TYPE.PUBLIC;\n    let password = '';\n\n    switch (selectedOption) {\n      case 'Private':\n        groupType = CometChat.GROUP_TYPE.PRIVATE;\n        break;\n      case 'Password':\n        groupType = CometChat.GROUP_TYPE.PASSWORD;\n        break;\n      default:\n        groupType = CometChat.GROUP_TYPE.PUBLIC;\n    }\n\n    if (groupType === CometChat.GROUP_TYPE.PASSWORD) {\n      if (!groupPassword.trim()) {\n        setShowError('Password is mandatory for password-protected groups');\n        return;\n      }\n      password = groupPassword;\n    }\n\n    const newGroup = new CometChat.Group(GUID, groupName, groupType, password);\n\n    try {\n      const createdGroup = await CometChat.createGroup(newGroup);\n      CometChatUIKitHelper.onGroupCreated(createdGroup);\n      onGroupCreated(createdGroup);\n\n      // Reset fields after creation\n      resetFields();\n      onClose();\n    } catch (error) {\n      console.log('Group creation failed with exception:', error);\n    }\n  };\n\n  return (\n    <CometChatBottomSheet\n      isOpen={visible}\n      onClose={handleDismiss}\n      doNotOccupyEntireHeight={true}\n      scrollEnabled={true}\n      style={{maxHeight: Dimensions.get('window').height * 0.8}}>\n      <View style={styles.bottomSheetContainer}>\n        {/* Header / Icon */}\n        <View style={{alignItems: 'center'}}>\n          <View\n            style={[\n              styles.avatarIconContainer,\n              {backgroundColor: theme.color.background3},\n            ]}>\n            <Icon\n              icon={\n                <Group color={theme.color.primary} height={44} width={44} />\n              }\n              size={44}\n            />\n          </View>\n          <Text\n            style={[\n              {color: theme.color.textPrimary, marginTop: 15},\n              theme.typography.heading2.regular,\n            ]}>\n            {t('NEW__GROUP')}\n          </Text>\n        </View>\n\n        {/* Group Type Tabs */}\n        <View style={{marginVertical: 20}}>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              {marginBottom: 10, color: theme.color.textPrimary},\n            ]}>\n            {t('TYPE')}\n          </Text>\n          <View\n            style={[\n              styles.optionTabsContainer,\n              {\n                borderColor: theme.color.borderLight,\n                backgroundColor: theme.color.background3,\n              },\n            ]}>\n            {groupTypes.map(option => {\n              const isSelected = selectedOption === option.value;\n              return (\n                <TouchableOpacity\n                  key={option.value}\n                  onPress={() => handleOptionPress(option)}\n                  style={{\n                    flex: 1,\n                    alignItems: 'center',\n                    paddingVertical: 8,\n                    borderRadius: 12,\n                    backgroundColor: isSelected\n                      ? theme.color.background1\n                      : 'transparent',\n                  }}>\n                  <Text\n                    style={[\n                      theme.typography.body.medium,\n                      {\n                        color: isSelected\n                          ? theme.color.textHighlight\n                          : theme.color.textSecondary,\n                        fontWeight: isSelected ? 'bold' : 'normal',\n                      },\n                    ]}>\n                    {option.display}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n\n        {/* Group Name Input */}\n        <View style={styles.marginBottom20}>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              {marginBottom: 10, color: theme.color.textPrimary},\n            ]}>\n            {t('NAME')}\n          </Text>\n          <View\n            style={[\n              styles.passwordInputContainer,\n              {\n                borderColor: theme.color.borderLight,\n                backgroundColor: theme.color.background3,\n              },\n            ]}>\n            <TextInput\n              value={groupName}\n              onChangeText={setGroupName}\n              style={[\n                theme.typography.body.regular,\n                {\n                  flex: 1,\n                  color: theme.color.textPrimary,\n                },\n              ]}\n              placeholder={t(\"ENTER_GROUP_NAME\")}\n              placeholderTextColor={theme.color.textTertiary}\n            />\n          </View>\n        </View>\n\n        {/* Group Password (if needed) */}\n        {showPasswordField && (\n          <View style={styles.marginBottom20}>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginBottom: 10, color: theme.color.textPrimary},\n              ]}>\n              {t('PASSWORD')}\n            </Text>\n            <View\n              style={[\n                styles.passwordInputContainer,\n                {\n                  borderColor: theme.color.borderLight,\n                  backgroundColor: theme.color.background3,\n                },\n              ]}>\n              <TextInput\n                value={groupPassword}\n                onChangeText={setGroupPassword}\n                style={[\n                  theme.typography.body.regular,\n                  {flex: 1, color: theme.color.textPrimary},\n                ]}\n                placeholder={t(\"ENTER_PASSWORD\")}\n                placeholderTextColor={theme.color.textTertiary}\n                secureTextEntry\n              />\n            </View>\n          </View>\n        )}\n\n        {showError !== '' && (\n          <View style={styles.toastContainer}>\n            <Text style={[styles.toastMessage, {color: theme.color.error}]}>\n              {showError}\n            </Text>\n          </View>\n        )}\n\n        {/* Create Group Button */}\n        <TouchableWithoutFeedback onPress={handleCreateGroup}>\n          <View\n            style={[\n              styles.createButton,\n              {backgroundColor: theme.color.primaryButtonBackground},\n            ]}>\n            <Text\n              style={[\n                theme.typography.button.medium,\n                {color: theme.color.primaryButtonText},\n              ]}>\n              {t('CREATE_GROUP')}\n            </Text>\n          </View>\n        </TouchableWithoutFeedback>\n      </View>\n    </CometChatBottomSheet>\n  );\n};\n\n/**\n * Bottom sheet for joining a password-protected group.\n */\ninterface JoinGroupBottomSheetProps {\n  visible: boolean;\n  groupToJoin: CometChat.Group | null;\n  onClose: () => void;\n  onJoinSuccess: (joinedGroup: CometChat.Group) => void;\n}\n\nexport const JoinGroupBottomSheet: React.FC<JoinGroupBottomSheetProps> = ({\n  visible,\n  groupToJoin,\n  onClose,\n  onJoinSuccess,\n}) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n  const [enteredPassword, setEnteredPassword] = useState('');\n  const [isPasswordErrorVisible, setIsPasswordErrorVisible] = useState(false);\n\n  useEffect(() => {\n    if (!isPasswordErrorVisible) return;\n    const timer = setTimeout(() => setIsPasswordErrorVisible(false), 3000);\n    return () => clearTimeout(timer);\n  }, [isPasswordErrorVisible]);\n\n  const joinPasswordGroup = async () => {\n    if (!groupToJoin || !enteredPassword.trim()) return;\n\n    try {\n      const joinedGroup = await CometChat.joinGroup(\n        groupToJoin.getGuid(),\n        groupToJoin.getType() as CometChat.GroupType,\n        enteredPassword,\n      );\n      onJoinSuccess(joinedGroup);\n      handleClose();\n    } catch (error) {\n      setIsPasswordErrorVisible(true);\n      console.log('Error joining password group:', error);\n    }\n  };\n\n  const handleClose = () => {\n    setEnteredPassword('');\n    setIsPasswordErrorVisible(false);\n    onClose();\n  };\n\n  return (\n    <CometChatBottomSheet\n      isOpen={visible}\n      onClose={handleClose}\n      scrollEnabled={true}\n      doNotOccupyEntireHeight={true}>\n      {groupToJoin && (\n        <View style={styles.joiningGroup}>\n          {/* Header */}\n          <Text\n            style={[\n              theme.typography.heading3.bold,\n              {\n                marginBottom: 20,\n                textAlign: 'center',\n                color: theme.color.textPrimary,\n              },\n            ]}>\n            {t('JOIN_GROUP')}\n          </Text>\n\n          {/* Group Info */}\n          <View style={{alignItems: 'center', marginBottom: 20}}>\n            <CometChatAvatar\n              name={groupToJoin.getName()}\n              image={{uri: groupToJoin.getIcon()}}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                {marginTop: 10, color: theme.color.textPrimary},\n              ]}>\n              {groupToJoin.getName()}\n            </Text>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginTop: 5, color: theme.color.textSecondary},\n              ]}>\n              {groupToJoin.getMembersCount()} {t('MEMBERS')}\n            </Text>\n          </View>\n\n          {/* Password Input */}\n          <View style={styles.marginBottom20}>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginBottom: 10, color: theme.color.textPrimary},\n              ]}>\n              {t('ENTER_PASSWORD')}\n            </Text>\n            <View\n              style={[\n                styles.passwordInputContainer,\n                {\n                  borderColor: theme.color.borderLight,\n                  backgroundColor: theme.color.background3,\n                },\n              ]}>\n              <TextInput\n                value={enteredPassword}\n                onChangeText={setEnteredPassword}\n                style={[\n                  theme.typography.body.regular,\n                  {flex: 1, color: theme.color.textPrimary},\n                ]}\n                placeholder={t('ENTER_PASSWORD')}\n                placeholderTextColor={theme.color.textTertiary}\n                secureTextEntry\n              />\n            </View>\n          </View>\n\n          {/* Incorrect Password Toast */}\n          {isPasswordErrorVisible && (\n            <View style={styles.toastContainer}>\n              <Text style={[styles.toastMessage, {color: theme.color.error}]}>\n                {t('PASSWORD_INCORRECT_GROUP')}\n              </Text>\n            </View>\n          )}\n\n          {/* Join Button */}\n          <TouchableWithoutFeedback onPress={joinPasswordGroup}>\n            <View\n              style={[\n                styles.joinGroupButton,\n                {backgroundColor: theme.color.primaryButtonBackground},\n              ]}>\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  {color: theme.color.primaryButtonText},\n                ]}>\n                {t('JOIN_GROUP')}\n              </Text>\n            </View>\n          </TouchableWithoutFeedback>\n        </View>\n      )}\n    </CometChatBottomSheet>\n  );\n};\n"
  },
  {
    "path": "examples/SampleApp/src/components/groups/Groups.tsx",
    "content": "import React, { useCallback, useEffect, useState } from 'react';\nimport { View } from 'react-native';\nimport { useFocusEffect, useNavigation } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  CometChatGroups,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { RootStackParamList } from '../../navigation/types';\nimport { styles } from './styles';\n\nimport {\n  GroupScreenAppBarOptions,\n  CreateGroupBottomSheet,\n  JoinGroupBottomSheet,\n} from './GroupHelper';\nimport {SCREEN_CONSTANTS} from '../../utils/AppConstants';\nimport { useConfig } from '../../config/store';\n\ntype GroupNavigationProp = StackNavigationProp<RootStackParamList, 'Groups'>;\n\ninterface GroupsProps {\n  hideHeader?: boolean;\n}\n\nconst Groups: React.FC<GroupsProps> = ({hideHeader = false}) => {\n  const createGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.createGroup\n  );\n  const theme = useTheme();\n  const navigation = useNavigation<GroupNavigationProp>();\n  const [pendingChat, setPendingChat] = useState<CometChat.Group | null>(null);\n\n  // State to handle showing/hiding bottom sheets\n  const [isCreateGroupSheetVisible, setCreateGroupSheetVisible] =\n    useState(false);\n  const [isJoinGroupSheetVisible, setJoinGroupSheetVisible] = useState(false);\n\n  // State for the group that user wants to join\n  const [groupToJoin, setGroupToJoin] = useState<CometChat.Group | null>(null);\n\n  // Condition to hide the entire screen if needed\n  const [shouldHide, setShouldHide] = useState(false);\n\n  useEffect(() => {\n    if (!isCreateGroupSheetVisible && !isJoinGroupSheetVisible && pendingChat) {\n      const timer = setTimeout(() => {\n        navigation.navigate(SCREEN_CONSTANTS.MESSAGES, { group: pendingChat });\n        setPendingChat(null);\n      }, 300);\n      return () => clearTimeout(timer);\n    }\n  }, [isCreateGroupSheetVisible, isJoinGroupSheetVisible, pendingChat]);\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, [navigation]),\n  );\n\n  /**\n   * Navigates to the Messages screen after group creation or join.\n   */\n  const handleNavigateToMessages = (group: CometChat.Group) => {\n    // close any open sheet first\n    setCreateGroupSheetVisible(false);\n    setJoinGroupSheetVisible(false);\n\n    // save the group – navigation will happen in a useEffect below\n    setPendingChat(group);\n  };\n\n  /**\n   * Handle group item press:\n   * - If joined, open chat\n   * - If public, join automatically\n   * - If password, show the join modal\n   */\n  const handleGroupItemPress = (group: CometChat.Group) => {\n    if (group.getHasJoined()) {\n      handleNavigateToMessages(group);\n      return;\n    }\n\n    if (group.getType() === CometChat.GROUP_TYPE.PUBLIC) {\n      joinPublicGroup(group);\n    } else if (group.getType() === CometChat.GROUP_TYPE.PASSWORD) {\n      setGroupToJoin(group);\n      setJoinGroupSheetVisible(true);\n    }\n    // For private group, you'd have a different flow.\n  };\n\n  const joinPublicGroup = async (group: CometChat.Group) => {\n    try {\n      const joinedGroup = await CometChat.joinGroup(\n        group.getGuid(),\n        group.getType() as CometChat.GroupType,\n        '',\n      );\n\n      handleNavigateToMessages(joinedGroup);\n      CometChatUIEventHandler.emitGroupEvent(\n        CometChatUIEvents.ccGroupMemberJoined,\n        {\n          joinedUser: CometChatUIKit.loggedInUser,\n          joinedGroup: joinedGroup,\n        },\n      );\n    } catch (error) {\n      console.log('Error joining public group:', error);\n    }\n  };\n\n  if (shouldHide) return null;\n\n  return (\n    <View\n      style={[\n        styles.safeAreaContainer,\n        { backgroundColor: theme.color.background1 },\n      ]}\n    >\n      {/* CometChatGroups list component */}\n      <CometChatGroups\n        AppBarOptions={\n          createGroup\n            ? () => (\n              <GroupScreenAppBarOptions\n                onPress={() => setCreateGroupSheetVisible(true)}\n              />\n            )\n            : undefined\n        }\n        onItemPress={handleGroupItemPress}\n        hideHeader={hideHeader}\n      />\n\n      {/* Create Group Bottom Sheet */}\n      <CreateGroupBottomSheet\n        visible={isCreateGroupSheetVisible}\n        onClose={() => setCreateGroupSheetVisible(false)}\n        onGroupCreated={handleNavigateToMessages}\n      />\n\n      {/* Join Group Bottom Sheet */}\n      <JoinGroupBottomSheet\n        visible={isJoinGroupSheetVisible}\n        groupToJoin={groupToJoin}\n        onClose={() => {\n          setJoinGroupSheetVisible(false);\n          setGroupToJoin(null);\n        }}\n        onJoinSuccess={handleNavigateToMessages}\n      />\n    </View>\n  );\n};\n\nexport default Groups;\n"
  },
  {
    "path": "examples/SampleApp/src/components/groups/styles.ts",
    "content": "import {Platform, StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  safeAreaContainer: {\n    flex: 1,\n  },\n  bottomSheetContainer: {\n    paddingHorizontal: 20,\n    paddingBottom: Platform.OS==='android'  ? 10: 30,\n    flex: 1,\n  },\n  marginBottom20: {\n    marginBottom: 20,\n  },\n  toastContainer: {\n    alignItems: 'flex-start',\n  },\n  toastMessage: {\n    paddingVertical: 10,\n  },\n  passwordInputContainer: {\n    borderWidth: 1,\n    flexDirection: 'row',\n    borderRadius: 12,\n    paddingLeft: 10,\n    paddingTop: Platform.select({android: 0, ios: 8}),\n    paddingBottom: Platform.select({android: 0, ios: 12}),\n  },\n  avatarIconContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    width: 80,\n    height: 80,\n    borderRadius: 50,\n  },\n  optionTabsContainer: {\n    borderWidth: 1,\n    flexDirection: 'row',\n    borderRadius: 12,\n    padding: Platform.OS === 'ios' ? 5 : 2,\n    justifyContent: 'space-between',\n    alignItems: 'center',\n  },\n  createButton: {\n    borderRadius: 8,\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingHorizontal: 20,\n    paddingVertical: 12,\n  },\n  joinGroupButton: {\n    borderRadius: 8,\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingHorizontal: 20,\n    paddingVertical: 12,\n  },\n  joiningGroup: {\n    paddingHorizontal: 20,\n    flex: 1,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/components/login/AppCredentials.tsx",
    "content": "import React, { useEffect, useMemo, useState } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TextInput,\n  TouchableOpacity,\n  Image,\n  useColorScheme,\n  Platform,\n  StatusBar,\n  Dimensions,\n  ScrollView,\n  KeyboardAvoidingView,\n  BackHandler,\n  Keyboard,\n} from 'react-native';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport {\n  CometChatUIKit,\n  UIKitSettings,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { navigate, navigationRef } from '../../navigation/NavigationService';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useFocusEffect } from '@react-navigation/native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { useHeaderHeight } from '@react-navigation/elements';\n\nconst AppCredentials: React.FC = () => {\n  const [storedAppId, setStoredAppId] = useState<string>('');\n  const [storedAuthKey, setStoredAuthKey] = useState<string>('');\n  const [storedRegion, setStoredRegion] = useState<string>('US');\n\n  // These are the *editable* states bound to the TextInput fields\n  const [appId, setAppId] = useState<string>('');\n  const [authKey, setAuthKey] = useState<string>('');\n  const [selectedRegion, setSelectedRegion] = useState<string>('US');\n\n  // Toast state for showing error messages\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const { width } = Dimensions.get('window');\n  const mode = useColorScheme();\n\n  const insets = useSafeAreaInsets();\n  const headerHeight = useHeaderHeight(); // returns 0 if no header\n  const statusBarHeight =\n    Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;\n\n  const [keyboardBehavior, setKeyboardBehavior] = useState<\n    'padding' | 'height' | undefined\n  >(Platform.OS === 'ios' ? 'padding' : 'height');\n\n  useEffect(() => {\n    const showListener = Keyboard.addListener('keyboardDidShow', () => {\n      setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');\n    });\n\n    const hideListener = Keyboard.addListener('keyboardDidHide', () => {\n      setKeyboardBehavior(undefined); // Remove behavior when keyboard hides\n    });\n\n    return () => {\n      showListener.remove();\n      hideListener.remove();\n    };\n  }, []);\n  // For iOS use insets.top (not status bar) + header height\n  const keyboardVerticalOffset = useMemo(() => {\n  return Platform.OS === 'ios'\n    ? (insets.top || 0) + (headerHeight || 0)\n    : (statusBarHeight || 0) + (headerHeight || 0);\n}, [insets.top, headerHeight, statusBarHeight]);\n\n  // Compute if form is valid (all fields provided)\n  const isFormValid =\n    appId.trim().length > 0 &&\n    authKey.trim().length > 0 &&\n    selectedRegion.trim().length > 0;\n\n  // Load existing credentials if any\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        navigationRef.goBack();\n        return true; // Prevent default behavior\n      };\n      const backHandler = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      async function loadCredentials() {\n        try {\n          const credentialsStr = await AsyncStorage.getItem('appCredentials');\n          if (credentialsStr) {\n            const credentials = JSON.parse(credentialsStr);\n            // Update 'stored' states\n            setStoredAppId(credentials.appId || '');\n            setStoredAuthKey(credentials.authKey || '');\n            setStoredRegion(credentials.region || 'US');\n\n            // Also set the fields so user sees them pre-populated\n            setAppId(credentials.appId || '');\n            setAuthKey(credentials.authKey || '');\n            setSelectedRegion(credentials.region || 'US');\n          }\n        } catch (error) {\n          console.log('Error loading stored credentials:', error);\n        }\n      }\n\n      loadCredentials();\n\n      return () => backHandler.remove();\n    }, []),\n  );\n\n  const showToast = (message: string) => {\n    setToastMessage(message);\n    setTimeout(() => {\n      setToastMessage(null);\n    }, 2500);\n  };\n\n  const handleContinue = async (): Promise<void> => {\n    // Validate the inputs.\n    // If the user has modified a field and cleared it (i.e. the input becomes empty),\n    // show a toast message.\n    if (!appId.trim()) {\n      showToast('Please enter App ID');\n      return;\n    }\n    if (!authKey.trim()) {\n      showToast('Please enter Auth Key');\n      return;\n    }\n    if (!selectedRegion.trim()) {\n      showToast('Please select a region');\n      return;\n    }\n\n    // Since all fields are non-empty, use their current values.\n    const newAppId = appId.trim();\n    const newAuthKey = authKey.trim();\n    const newRegion = selectedRegion.trim();\n\n    try {\n      const credentials = {\n        region: newRegion,\n        appId: newAppId,\n        authKey: newAuthKey,\n      };\n      console.log('Saving credentials:', credentials);\n      await AsyncStorage.setItem('appCredentials', JSON.stringify(credentials));\n\n      // Re-initialize with updated credentials\n      await CometChatUIKit.init({\n        appId: newAppId,\n        authKey: newAuthKey,\n        region: newRegion,\n        subscriptionType: CometChat.AppSettings\n          .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n      });\n    } catch (error) {\n      console.error('Failed to save credentials', error);\n    }\n\n    // Navigate to the next screen.\n    navigate('BottomTabNavigator');\n    navigationRef.reset({\n      index: 0,\n      routes: [{ name: SCREEN_CONSTANTS.SAMPLE_USER }],\n    });\n  };\n\n  return (\n    <View\n      style={[styles.container, { backgroundColor: theme.color.background2 }]}\n    >\n      <KeyboardAvoidingView\n        style={{ flex: 1 }}\n        behavior={keyboardBehavior} // Use dynamic behavior\n        keyboardVerticalOffset={keyboardVerticalOffset}\n      >\n        <View style={styles.contentContainer}>\n          <ScrollView\n            contentContainerStyle={styles.scrollContent}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {/* Header/Logo */}\n            <View style={styles.logoContainer}>\n              <Image\n                source={\n                  mode === 'dark'\n                    ? require('../../assets/icons/Dark.png')\n                    : require('../../assets/icons/Light.png')\n                }\n                style={{\n                  width: width * 0.25,\n                  height: width * 0.25,\n                  resizeMode: 'contain',\n                }}\n              />\n            </View>\n\n            {/* Title */}\n            <Text\n              style={[\n                theme.typography.heading2.bold,\n                {\n                  color: theme.color.textPrimary,\n                  marginBottom: 20,\n                  alignSelf: 'center',\n                },\n              ]}\n            >\n              {t('APP_CREDENTIALS')}\n            </Text>\n\n            {/* Region Selector */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, marginBottom: 10 },\n                ]}\n              >\n                {t('REGION')}\n              </Text>\n              <View style={styles.regionRow}>\n                {/* US */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'US'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'US'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('US')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/US.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      US\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* EU */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('EU')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/EU.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      EU\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* IN */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('IN')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/India.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      IN\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n              </View>\n            </View>\n\n            {/* App ID */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, paddingBottom: 5 },\n                ]}\n              >\n                APP ID\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={appId}\n                onChangeText={setAppId}\n                placeholder=\"Enter the App ID\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n\n            {/* Auth Key */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, paddingBottom: 5 },\n                ]}\n              >\n                Auth Key\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={authKey}\n                onChangeText={setAuthKey}\n                placeholder=\"Enter the Auth Key\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n          </ScrollView>\n\n          {/* Continue Button */}\n          <View style={styles.buttonWrapper}>\n            <TouchableOpacity\n              style={[\n                styles.continueButton,\n                {\n                  backgroundColor: theme.color.primaryButtonBackground,\n                  opacity: isFormValid ? 1 : 0.6,\n                },\n              ]}\n              onPress={handleContinue}\n            >\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  { textAlign: 'center', color: theme.color.staticWhite },\n                ]}\n              >\n                {t('CONTINUE')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n      {/* Toast Message */}\n      {toastMessage && (\n        <View style={styles.toastContainer}>\n          <Text style={styles.toastText}>{toastMessage}</Text>\n        </View>\n      )}\n    </View>\n  );\n};\n\nexport default AppCredentials;\n\nconst styles = StyleSheet.create({\n  logoContainer: {\n    alignItems: 'center',\n    marginTop: Platform.OS === 'android' ? 30 : 50,\n    marginBottom: 20,\n  },\n  inputContainer: {\n    width: '100%',\n    marginTop: 20,\n  },\n  regionRow: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n  },\n  flagContainer: {\n    width: '32%',\n    borderWidth: 2,\n    borderColor: 'transparent',\n    borderRadius: 12,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  flagInnerContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 10,\n    gap: 5,\n  },\n  flagImage: {\n    width: 30,\n    height: 30,\n    resizeMode: 'contain',\n  },\n  input: {\n    paddingHorizontal: 12,\n    paddingVertical: 10,\n    borderRadius: 8,\n    borderWidth: 1,\n  },\n  continueButton: {\n    borderRadius: 8,\n    paddingVertical: 12,\n    width: '100%',\n  },\n  toastContainer: {\n    position: 'absolute',\n    bottom: '8%',\n    left: 20,\n    right: 20,\n    backgroundColor: '#C73C3E',\n    padding: 6,\n    borderRadius: 8,\n    alignItems: 'center',\n  },\n  toastText: {\n    color: '#fff',\n    fontSize: 14,\n  },\n  container: {\n    flex: 1,\n  },\n  contentContainer: {\n    flex: 1,\n    paddingHorizontal: 16,\n  },\n  scrollContent: {\n    flexGrow: 1,\n    paddingBottom: 80,\n  },\n  buttonWrapper: {\n    justifyContent: 'center',\n    alignItems: 'center',\n    paddingVertical: 10,\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/components/login/SampleUser.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TouchableOpacity,\n  TextInput,\n  Image,\n  Dimensions,\n  useColorScheme,\n  Pressable,\n  ImageSourcePropType,\n  KeyboardAvoidingView,\n  Platform,\n  ScrollView,\n  ActivityIndicator,\n  StatusBar,\n  Keyboard,\n} from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatAvatar,\n  CometChatUIKit,\n  Icon,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport Check from '../../assets/icons/CheckFill';\nimport { sampleData } from '../../utils/helper';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { navigate, navigationRef } from '../../navigation/NavigationService';\nimport Skeleton from './Skeleton';\nimport {\n  SafeAreaView,\n  useSafeAreaInsets,\n} from 'react-native-safe-area-context';\nimport { useHeaderHeight } from '@react-navigation/elements';\n\ntype GridItem = CometChat.User | { dummy: true };\n\nconst LoginScreen: React.FC = () => {\n  const [users, setUsers] = useState<CometChat.User[]>([]);\n  const [selectedUser, setSelectedUser] = useState<string | null>(null);\n  const [userUID, setUserUID] = useState<string>('');\n  const [isLoading, setIsLoading] = useState<boolean>(false);\n  const [loadingUsers, setLoadingUsers] = useState<boolean>(true);\n\n  const theme = useTheme();\n  const mode = useColorScheme();\n  const { width } = Dimensions.get('window');\n  const statusBarHeight =\n    Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;\n\n  const insets = useSafeAreaInsets();\n  const headerHeight = useHeaderHeight(); // returns 0 if no header\n  const keyboardVerticalOffset =\n    Platform.OS === 'ios'\n      ? (insets.top + 8 || 0) + (headerHeight || 0)\n      : (statusBarHeight + 6 || 0) + (headerHeight || 0);\n\n  const [keyboardBehavior, setKeyboardBehavior] = useState<\n    'padding' | 'height' | undefined\n  >(Platform.OS === 'ios' ? 'padding' : 'height');\n\n  useEffect(() => {\n    const showListener = Keyboard.addListener('keyboardDidShow', () => {\n      setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');\n    });\n\n    const hideListener = Keyboard.addListener('keyboardDidHide', () => {\n      setKeyboardBehavior(undefined); // Remove behavior when keyboard hides\n    });\n\n    return () => {\n      showListener.remove();\n      hideListener.remove();\n    };\n  }, []);\n\n  useEffect(() => {\n    (async function loadUsers(): Promise<void> {\n      try {\n        setLoadingUsers(true);\n        const fetchedUsers = await fetchUsers();\n        setUsers(fetchedUsers);\n      } catch (error) {\n        console.error(error);\n      } finally {\n        setLoadingUsers(false);\n      }\n    })();\n  }, []);\n\n  const handleSelectUser = (user: CometChat.User): void => {\n    setSelectedUser(user.getUid());\n    setUserUID('');\n  };\n\n  const handleContinue = async () => {\n    if ((!selectedUser && !userUID.trim()) || isLoading) return;\n    setIsLoading(true);\n    const uid: string = userUID.trim() || selectedUser!;\n    try {\n      await CometChatUIKit.login({ uid });\n      navigate('BottomTabNavigator');\n      navigationRef.reset({\n        index: 0,\n        routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n      });\n    } catch (error: any) {\n      console.log('Login failed with exception:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  /**\n   * Fetch users from a remote sample JSON file.\n   * Falls back to local sample data if there's an error.\n   */\n  async function fetchUsers(): Promise<CometChat.User[]> {\n    try {\n      const response = await fetch(\n        'https://assets.cometchat.io/sampleapp/sampledata.json',\n      );\n\n      if (response.ok) {\n        const data = await response.json();\n        const fetchedUsers = data.users || [];\n        return fetchedUsers.map((user: any) => new CometChat.User(user));\n      } else {\n        throw new Error('Failed to load users');\n      }\n    } catch (error) {\n      console.error('Exception while fetching users:', error);\n      return await getDefaultUsers();\n    }\n  }\n\n  /**\n   * Get users from local sample data (used in case the remote fetch fails).\n   */\n  async function getDefaultUsers(): Promise<CometChat.User[]> {\n    const localUsers = sampleData.users || [];\n    return localUsers.map((user: any) => new CometChat.User(user));\n  }\n\n  /**\n   * Returns the appropriate image source object for the avatar.\n   */\n  const getAvatarSource = (\n    avatar: string | ImageSourcePropType,\n  ): ImageSourcePropType => {\n    if (typeof avatar === 'string') {\n      if (avatar.startsWith('http://') || avatar.startsWith('https://')) {\n        return { uri: avatar };\n      }\n    }\n    return avatar as ImageSourcePropType;\n  };\n\n  // Show skeleton if the API is still loading or if no users are available.\n  const showSkeleton = loadingUsers || users.length === 0;\n\n  // Compute grid data only if users are available.\n  let gridData: GridItem[] = [];\n  if (users.length > 0) {\n    gridData = [...users];\n    const numColumns = 3;\n    const numberOfElementsLastRow = users.length % numColumns;\n    if (numberOfElementsLastRow !== 0) {\n      for (let i = 0; i < numColumns - numberOfElementsLastRow; i++) {\n        gridData.push({ dummy: true });\n      }\n    }\n  }\n\n  const Loading = () => {\n    return (\n      <View\n        style={{\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        <ActivityIndicator\n          size=\"small\"\n          color={theme.color.staticWhite}\n          style={{ alignSelf: 'center', justifyContent: 'center' }}\n        />\n      </View>\n    );\n  };\n\n  return (\n    <SafeAreaView\n      style={[styles.container, { backgroundColor: theme.color.background2 }]}\n      edges={['top']}\n    >\n      <KeyboardAvoidingView\n        style={styles.keyboardAvoidingContainer}\n        behavior={keyboardBehavior} // Use dynamic behavior\n        keyboardVerticalOffset={keyboardVerticalOffset}\n      >\n        <ScrollView\n          style={{ flex: 1 }}\n          contentContainerStyle={styles.scrollContainer}\n          keyboardShouldPersistTaps=\"handled\"\n          showsVerticalScrollIndicator={false}\n        >\n          {/* App Logo */}\n          <View style={styles.logoContainer}>\n            <Image\n              source={\n                mode === 'dark'\n                  ? require('../../assets/icons/Dark.png')\n                  : require('../../assets/icons/Light.png')\n              }\n              style={{\n                width: width * 0.25,\n                height: width * 0.25,\n                resizeMode: 'contain',\n              }}\n            />\n          </View>\n\n          {/* Title */}\n          <Text\n            style={[\n              theme.typography.heading2.bold,\n              styles.logInTitle,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Log In\n          </Text>\n\n          {/* Subtitle */}\n          <Text\n            style={[\n              theme.typography.body.medium,\n              styles.subtitle,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Choose a Sample User\n          </Text>\n\n          {/* Sample Users Grid */}\n          <View style={styles.userGridWrapper}>\n            {showSkeleton ? (\n              <Skeleton />\n            ) : (\n              <View style={styles.usersContainer}>\n                {gridData.map((item, index) => {\n                  // Render a blank view for dummy items\n                  if ('dummy' in item && item.dummy) {\n                    return (\n                      <View key={`dummy-${index}`} style={styles.userCard} />\n                    );\n                  }\n\n                  // Otherwise, render a user\n                  const user = item as CometChat.User;\n                  const isSelected = selectedUser === user.getUid();\n                  const firstName = user.getName();\n\n                  return (\n                    <Pressable\n                      key={user.getUid()}\n                      style={[\n                        styles.userCard,\n                        {\n                          borderWidth: isSelected ? 1.5 : 1,\n                          borderColor: isSelected\n                            ? theme.color.borderHighlight\n                            : theme.color.borderLight,\n                          backgroundColor: isSelected\n                            ? theme.color.extendedPrimary50\n                            : theme.color.background1,\n                        },\n                      ]}\n                      onPress={() => handleSelectUser(user)}\n                    >\n                      {/* Show the check icon ONLY if selected */}\n                      {isSelected && (\n                        <View style={styles.checkIconContainer}>\n                          <Icon\n                            icon={\n                              <Check\n                                color={theme.color.staticWhite}\n                                height={18}\n                                width={18}\n                              />\n                            }\n                          />\n                        </View>\n                      )}\n                      <CometChatAvatar\n                        name={user.getName()}\n                        image={getAvatarSource(user.getAvatar())}\n                      />\n                      {/* Display only the first name */}\n                      <Text\n                        style={[\n                          theme.typography.body.medium,\n                          styles.firstNameText,\n                          { color: theme.color.textPrimary },\n                        ]}\n                      >\n                        {firstName}\n                      </Text>\n                      <Text\n                        style={[\n                          theme.typography.caption1.regular,\n                          styles.uidText,\n                          { color: theme.color.textSecondary },\n                        ]}\n                      >\n                        {user.getUid()}\n                      </Text>\n                    </Pressable>\n                  );\n                })}\n              </View>\n            )}\n          </View>\n\n          {/* Horizontal divider with \"Or\" in the middle */}\n          <View style={styles.dividerRow}>\n            <View\n              style={[\n                styles.divider,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                { color: theme.color.textTertiary },\n              ]}\n            >\n              Or\n            </Text>\n            <View\n              style={[\n                styles.divider,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            />\n          </View>\n\n          {/* UID Input */}\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.uidLabel,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Enter Your UID\n          </Text>\n          <TextInput\n            placeholder=\" Enter UID\"\n            placeholderTextColor={theme.color.textTertiary}\n            style={[\n              theme.typography.body.regular,\n              styles.uidInput,\n              {\n                borderColor: theme.color.borderLight,\n                color: theme.color.textPrimary,\n              },\n            ]}\n            value={userUID}\n            onChangeText={(text: string) => {\n              setUserUID(text);\n              setSelectedUser(null);\n            }}\n          />\n        </ScrollView>\n\n        {/* Bottom container with \"Continue\" button and \"Change App Credentials\" */}\n        <View style={styles.bottomContainer}>\n          <TouchableOpacity\n            style={[\n              styles.continueButton,\n              {\n                backgroundColor: theme.color.primaryButtonBackground,\n              },\n            ]}\n            onPress={handleContinue}\n            disabled={(!selectedUser && !userUID.trim()) || isLoading}\n          >\n            {isLoading ? (\n              <Loading />\n            ) : (\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.continueButtonText,\n                  { color: theme.color.staticWhite },\n                ]}\n              >\n                Continue\n              </Text>\n            )}\n          </TouchableOpacity>\n\n          <View style={styles.changeCredentialsWrapper}>\n            <Text\n              style={[\n                theme.typography.body.regular,\n                { color: theme.color.textSecondary },\n              ]}\n            >\n              Change{' '}\n            </Text>\n            <TouchableOpacity\n              style={styles.changeCredentialsContainer}\n              onPress={() => {\n                navigationRef.navigate(SCREEN_CONSTANTS.APP_CRED);\n              }}\n            >\n              <Text\n                style={[\n                  theme.typography.body.regular,\n                  { color: theme.color.primary },\n                ]}\n              >\n                App Credentials\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </SafeAreaView>\n  );\n};\n\nexport default LoginScreen;\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  keyboardAvoidingContainer: {\n    flex: 1,\n  },\n  scrollContainer: {\n    paddingHorizontal: 16,\n    paddingTop: 16,\n  },\n  logoContainer: {\n    alignItems: 'center',\n    marginBottom: 16,\n  },\n  logInTitle: {\n    marginBottom: 16,\n    alignSelf: 'center',\n  },\n  subtitle: {\n    marginBottom: 6,\n  },\n  usersContainer: {\n    flexDirection: 'row',\n    flexWrap: 'wrap',\n    justifyContent: 'space-around',\n  },\n  userCard: {\n    position: 'relative',\n    width: '30%',\n    borderRadius: 8,\n    paddingVertical: 16,\n    paddingHorizontal: 8,\n    marginBottom: 12,\n    alignItems: 'center',\n    overflow: 'hidden',\n  },\n  checkIconContainer: {\n    position: 'absolute',\n    top: 0,\n    right: 0,\n    borderBottomLeftRadius: 10,\n    borderTopRightRadius: 7,\n    width: '27%',\n    height: '22%',\n    backgroundColor: '#7367F0',\n    alignItems: 'center',\n    justifyContent: 'center',\n    zIndex: 2,\n  },\n  firstNameText: {\n    marginTop: 8,\n    textAlign: 'center',\n  },\n  uidText: {\n    marginTop: 4,\n    textAlign: 'center',\n  },\n  dividerRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginVertical: 16,\n    gap: 10,\n  },\n  divider: {\n    flex: 1,\n    height: 1,\n    borderWidth: 0.5,\n  },\n  uidLabel: {\n    paddingBottom: 5,\n  },\n  uidInput: {\n    borderWidth: 1,\n    borderRadius: 8,\n    padding: 10,\n    marginBottom: 24,\n  },\n  bottomContainer: {\n    paddingHorizontal: 16,\n    backgroundColor: 'transparent',\n  },\n  continueButton: {\n    paddingVertical: 12,\n    borderRadius: 6,\n    marginBottom: 12,\n  },\n  continueButtonText: {\n    alignSelf: 'center',\n  },\n  changeCredentialsWrapper: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'center',\n    marginBottom: 10,\n  },\n  changeCredentialsContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  userGridWrapper: {\n    minHeight: 240,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/components/login/Skeleton.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Dimensions, Easing, StyleSheet, useColorScheme, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\n// import { useThemeInternal } from \"../../../theme/hook\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonBox = ({ index, boxSize, gradientColors }: any) => {\n  // Determine if the box is the last in its row\n  const isLastInRow = (index + 1) % 3 === 0;\n\n  return (\n    <Svg\n      height={boxSize}\n      width={boxSize}\n      viewBox=\"0 0 100 100\"\n      fill=\"none\"\n      style={[\n        styles.skeletonBox,\n        {\n          width: boxSize,\n          height: boxSize,\n          marginRight: isLastInRow ? 0 : 8,\n        },\n      ]}\n    >\n      <Defs>\n        <LinearGradient\n          id={`paint0_linear_${index}`}\n          x1={10}\n          y1={50}\n          x2={90}\n          y2={50}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <Stop stopColor={gradientColors[0]} />\n          <Stop offset={1} stopColor={gradientColors[1]} />\n        </LinearGradient>\n      </Defs>\n      <Rect\n        x={10}\n        y={10}\n        width={80}\n        height={80}\n        rx={15}\n        ry={15}\n        fill={`url(#paint0_linear_${index})`}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const animatedValue = useRef(new Animated.Value(0)).current;\n  const [isLoading, setIsLoading] = useState(true);\n  const  mode  = useColorScheme();\n\n  // Define static colors\n  const color = {\n    staticBlack: \"#000000\",\n    staticWhite: \"#FFFFFF\",\n  };\n\n  // Define skeletonStyle based on the theme mode\n  const skeletonStyle =\n    mode === \"light\"\n      ? {\n          linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n          shimmerBackgroundColor: color.staticBlack,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        }\n      : {\n          linearGradientColors: [\"#383838\", \"#272727\"],\n          shimmerBackgroundColor: color.staticWhite,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        };\n\n  const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } =\n    skeletonStyle;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n\n    // Simulate a loading time of 3 seconds\n    const loadData = setTimeout(() => {\n      setIsLoading(false);\n    }, 3000);\n\n    return () => clearTimeout(loadData);\n  }, [animatedValue, speed]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth, screenWidth],\n  });\n\n  if (!isLoading) {\n    return null;\n  }\n\n  const boxSize = (screenWidth - 22) / 3;\n  const shimmerWidth = screenWidth;\n  const shimmerHeight = boxSize + 16;\n\n  return (\n    <View style={styles.container}>\n      <View style={styles.grid}>\n        {new Array(6).fill(0).map((_, index) => (\n          <SkeletonBox\n            key={index}\n            index={index}\n            boxSize={boxSize}\n            gradientColors={linearGradientColors}\n          />\n        ))}\n      </View>\n      <Animated.View\n        style={[\n          styles.animatedShimmer,\n          {\n            width: shimmerWidth,\n            height: shimmerHeight,\n            transform: [{ translateX: shimmerTranslateX }],\n            backgroundColor: shimmerBackgroundColor,\n            opacity: shimmerOpacity,\n          },\n        ]}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    position: \"relative\",\n    borderRadius: 16,\n    overflow: \"hidden\",\n  },\n  grid: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n  },\n  skeletonBox: {\n    marginBottom: 10,\n    marginHorizontal: -10,\n  },\n  animatedShimmer: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n  },\n});\n\nexport default Skeleton;\n"
  },
  {
    "path": "examples/SampleApp/src/components/users/Users.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatUsers, useTheme } from '@cometchat/chat-uikit-react-native';\nimport React, { useCallback } from 'react';\nimport { View } from 'react-native';\nimport { useFocusEffect, useNavigation } from '@react-navigation/native';\nimport { RootStackParamList } from '../../navigation/types';\nimport { StackNavigationProp } from '@react-navigation/stack';\n\ntype UserNavigationProp = StackNavigationProp<RootStackParamList, 'Users'>;\n\nconst Users: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<UserNavigationProp>();\n  const [shouldHide, setShouldHide] = React.useState(false);\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  return shouldHide ? null : (\n    <View style={{ flex: 1, backgroundColor: theme.color.background1 }}>\n      <CometChatUsers\n        onItemPress={(user: CometChat.User) => {\n          navigation.navigate('Messages', {\n            user: user,\n          });\n        }}\n        usersRequestBuilder={new CometChat.UsersRequestBuilder()\n          .setLimit(30)\n          .hideBlockedUsers(false)\n          // .setRoles(['@agentic'])\n          .friendsOnly(false)\n          .setStatus('')\n          .setTags([])\n          .sortBy('name')\n          .setUIDs([])}\n      />\n    </View>\n  );\n};\n\nexport default Users;\n"
  },
  {
    "path": "examples/SampleApp/src/config/config.json",
    "content": "{\n  \"builderId\": \"8c0c1c80-b08e-4960-80d4-686396119711\",\n  \"settings\": {\n    \"chatFeatures\": {\n      \"coreMessagingExperience\": {\n        \"typingIndicator\": true,\n        \"threadConversationAndReplies\": true,\n        \"photosSharing\": true,\n        \"videoSharing\": true,\n        \"audioSharing\": true,\n        \"fileSharing\": true,\n        \"editMessage\": true,\n        \"deleteMessage\": true,\n        \"messageDeliveryAndReadReceipts\": true,\n        \"userAndFriendsPresence\": true\n      },\n      \"deeperUserEngagement\": {\n        \"mentions\": true,\n        \"reactions\": true,\n        \"messageTranslation\": true,\n        \"polls\": true,\n        \"collaborativeWhiteboard\": true,\n        \"collaborativeDocument\": true,\n        \"voiceNotes\": true,\n        \"emojis\": true,\n        \"stickers\": true,\n        \"userInfo\": true,\n        \"groupInfo\": true\n      },\n      \"aiUserCopilot\": {\n        \"conversationStarter\": true,\n        \"conversationSummary\": true,\n        \"smartReply\": true\n      },\n      \"groupManagement\": {\n        \"createGroup\": true,\n        \"addMembersToGroups\": true,\n        \"joinLeaveGroup\": true,\n        \"deleteGroup\": true,\n        \"viewGroupMembers\": true\n      },\n      \"moderatorControls\": {\n        \"kickUsers\": true,\n        \"banUsers\": true,\n        \"promoteDemoteMembers\": true\n      },\n      \"privateMessagingWithinGroups\": {\n        \"sendPrivateMessageToGroupMembers\": true\n      }\n    },\n    \"callFeatures\": {\n      \"voiceAndVideoCalling\": {\n        \"oneOnOneVoiceCalling\": true,\n        \"oneOnOneVideoCalling\": true,\n        \"groupVideoConference\": true,\n        \"groupVoiceConference\": true\n      }\n    },\n    \"layout\": {\n      \"withSideBar\": true,\n      \"tabs\": [\n        \"chats\",\n        \"calls\",\n        \"users\",\n        \"groups\"\n      ],\n      \"chatType\": \"both\",\n      \"compactMessageComposer\": true\n    },\n    \"style\": {\n          \"theme\": \"system\",\n          \"color\": {\n              \"brandColor\": \"#6852D6\",\n              \"primaryTextLight\": \"#141414\",\n              \"primaryTextDark\": \"#FFFFFF\",\n              \"secondaryTextLight\": \"#727272\",\n              \"secondaryTextDark\": \"#989898\"\n          },\n          \"typography\": {\n              \"font\": \"roboto\",\n              \"size\": \"default\"\n          }\n    },\n    \"noCode\": {\n      \"docked\": false,\n      \"styles\": {\n        \"buttonBackGround\": \"#6952d6\",\n        \"buttonShape\": \"rounded\",\n        \"openIcon\": \"https://nocode-js.cometchat.io/v1/resources/docked_open_icon.svg\",\n        \"closeIcon\": \"https://nocode-js.cometchat.io/v1/resources/docked_close_icon.svg\",\n        \"customJs\": \"\",\n        \"customCss\": \"\"\n      }\n    }\n  },\n  \"name\": \"ttttt\",\n  \"type\": \"low-code\",\n  \"createdAt\": 1749032525,\n  \"updatedAt\": 1749032525,\n  \"expiresAt\": 1812108125\n}"
  },
  {
    "path": "examples/SampleApp/src/config/store.ts",
    "content": "import { create } from 'zustand';\nimport config from './config.json';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\n// TypeScript interfaces for the config structure\ninterface ColorConfig {\n  brandColor: string;\n  primaryTextLight: string;\n  primaryTextDark: string;\n  secondaryTextLight: string;\n  secondaryTextDark: string;\n}\n\ninterface TypographyConfig {\n  font: string;\n  size: string;\n}\n\ninterface StyleConfig {\n  theme: string;\n  color: ColorConfig;\n  typography: TypographyConfig;\n}\n\ninterface NoCodeStyles {\n  buttonBackGround: string;\n  buttonShape: string;\n  openIcon: string;\n  closeIcon: string;\n  customJs: string;\n  customCss: string;\n}\n\ninterface NoCodeConfig {\n  docked: boolean;\n  styles: NoCodeStyles;\n}\n\ninterface LayoutConfig {\n  withSideBar: boolean;\n  tabs: string[];\n  chatType: string;\n  compactMessageComposer: boolean;\n}\n\ninterface CoreMessagingConfig {\n  typingIndicator: boolean;\n  threadConversationAndReplies: boolean;\n  photosSharing: boolean;\n  videoSharing: boolean;\n  audioSharing: boolean;\n  fileSharing: boolean;\n  editMessage: boolean;\n  deleteMessage: boolean;\n  messageDeliveryAndReadReceipts: boolean;\n  userAndFriendsPresence: boolean;\n}\n\ninterface DeeperEngagementConfig {\n  mentions: boolean;\n  reactions: boolean;\n  messageTranslation: boolean;\n  polls: boolean;\n  collaborativeWhiteboard: boolean;\n  collaborativeDocument: boolean;\n  voiceNotes: boolean;\n  emojis: boolean;\n  stickers: boolean;\n  userInfo: boolean;\n  groupInfo: boolean;\n}\n\ninterface AiUserCopilotConfig {\n  conversationStarter: boolean;\n  conversationSummary: boolean;\n  smartReply: boolean;\n}\n\ninterface GroupManagementConfig {\n  createGroup: boolean;\n  addMembersToGroups: boolean;\n  joinLeaveGroup: boolean;\n  deleteGroup: boolean;\n  viewGroupMembers: boolean;\n}\n\ninterface ModeratorControlsConfig {\n  kickUsers: boolean;\n  banUsers: boolean;\n  promoteDemoteMembers: boolean;\n}\n\ninterface PrivateMessagingConfig {\n  sendPrivateMessageToGroupMembers: boolean;\n}\n\ninterface ChatFeaturesConfig {\n  coreMessagingExperience: CoreMessagingConfig;\n  deeperUserEngagement: DeeperEngagementConfig;\n  aiUserCopilot: AiUserCopilotConfig;\n  groupManagement: GroupManagementConfig;\n  moderatorControls: ModeratorControlsConfig;\n  privateMessagingWithinGroups: PrivateMessagingConfig;\n}\n\ninterface VoiceVideoCallingConfig {\n  oneOnOneVoiceCalling: boolean;\n  oneOnOneVideoCalling: boolean;\n  groupVideoConference: boolean;\n  groupVoiceConference: boolean;\n}\n\ninterface CallFeaturesConfig {\n  voiceAndVideoCalling: VoiceVideoCallingConfig;\n}\n\ninterface SettingsConfig {\n  chatFeatures: ChatFeaturesConfig;\n  callFeatures: CallFeaturesConfig;\n  layout: LayoutConfig;\n  style: StyleConfig;\n  noCode: NoCodeConfig;\n}\n\ninterface AppConfig {\n  builderId: string;\n  settings: SettingsConfig;\n  name: string;\n  type: string;\n  createdAt: number;\n  updatedAt: number;\n  expiresAt: number;\n}\n\n// Zustand store interface\ninterface ConfigStore {\n  config: AppConfig;\n  updateConfig: (newConfig: Partial<AppConfig>) => void;\n  resetConfig: () => void;\n}\n\n// Initialize config from storage\nconst initializeConfig = async (): Promise<AppConfig> => {\n  try {\n    const savedConfig = await AsyncStorage.getItem('@app_config');\n    if (savedConfig) {\n      const parsedConfig = JSON.parse(savedConfig);\n      \n      // Handle both old format (direct config) and new API response format (nested in data)\n      let actualConfig: AppConfig;\n      if (parsedConfig.data && parsedConfig.data.settings) {\n        // New API response format - extract data property\n        actualConfig = parsedConfig.data as AppConfig;\n      } else if (parsedConfig.settings && parsedConfig.builderId) {\n        // Old format - direct config\n        actualConfig = parsedConfig as AppConfig;\n      } else {\n        // Invalid structure, use default\n        return config as AppConfig;\n      }\n      \n      // Validate that the config has the expected structure\n      if (actualConfig && actualConfig.settings && actualConfig.builderId) {\n        return actualConfig;\n      }\n    }\n  } catch (error) {\n    console.error('Error loading config from storage:', error);\n  }\n  return config as AppConfig;\n};\n\n// Create the Zustand store\nexport const useConfigStore = create<ConfigStore>((set, _get) => ({\n  // Initialize with the config.json data\n  config: config as AppConfig,\n\n  // Update the entire config\n  updateConfig: (newConfig) =>\n    set((state) => ({\n      config: { ...state.config, ...newConfig },\n    })),\n\n  // Reset to original config\n  resetConfig: () =>\n    set({\n      config: config as AppConfig,\n    }),\n}));\n\n// Initialize the store with saved config if available\ninitializeConfig().then((initialConfig) => {\n  useConfigStore.setState({ config: initialConfig });\n}).catch((error) => {\n  console.error('Error initializing config:', error);\n  // Fallback to default config from JSON file\n  useConfigStore.setState({ config: config as AppConfig });\n});\n\nexport const useConfig = <T>(selector: (state: AppConfig) => T) =>\n  useConfigStore((state) => selector(state.config));\n"
  },
  {
    "path": "examples/SampleApp/src/declarations.d.ts",
    "content": "declare module '*.png' {\n    const value: any;\n    export default value;\n  }\n  "
  },
  {
    "path": "examples/SampleApp/src/hooks/useGroupMemberStatus.ts",
    "content": "import { useState, useEffect, useRef } from 'react';\n//@ts-ignore\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatUIEventHandler } from '@cometchat/chat-uikit-react-native';\n\n/**\n * Hook that detects whether the logged-in user is no longer a member\n * of the given group (kicked or banned).\n *\n * - On mount: checks current membership via CometChat.getGroup()\n * - Real-time: listens to SDK + UI events for kicked/banned/re-added\n *\n * @param group - The CometChat group object (pass undefined for 1-on-1 chats)\n * @returns `true` when the current user is no longer a member\n */\nexport const useGroupMemberStatus = (group?: CometChat.Group): boolean => {\n  const [isNoLongerMember, setIsNoLongerMember] = useState(false);\n  const loggedInUser = useRef<CometChat.User | null>(null);\n\n  // Fetch logged-in user once\n  useEffect(() => {\n    CometChat.getLoggedinUser().then((u: CometChat.User | null) => {\n      if (u) loggedInUser.current = u;\n    });\n  }, []);\n\n  // Check membership on mount by fetching fresh group data\n  useEffect(() => {\n    if (!group) return;\n\n    CometChat.getGroup(group.getGuid())\n      .then((freshGroup: CometChat.Group) => {\n        if (!freshGroup.getHasJoined()) {\n          setIsNoLongerMember(true);\n        }\n      })\n      .catch(() => {\n        setIsNoLongerMember(true);\n      });\n  }, [group]);\n\n  useEffect(() => {\n    if (!group) return;\n\n    const uiListenerId = 'composer_group_status_' + new Date().getTime();\n    const sdkListenerId = 'composer_sdk_group_status_' + new Date().getTime();\n\n    CometChatUIEventHandler.addGroupListener(uiListenerId, {\n      ccGroupMemberKicked: ({ kickedUser }: any) => {\n        if (kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()) {\n          setIsNoLongerMember(true);\n        }\n      },\n      ccGroupMemberBanned: ({ kickedUser, bannedUser }: any) => {\n        const affected = bannedUser || kickedUser;\n        if (affected?.getUid?.() === loggedInUser.current?.getUid?.()) {\n          setIsNoLongerMember(true);\n        }\n      },\n      ccGroupMemberAdded: ({ usersAdded }: any) => {\n        if (Array.isArray(usersAdded)) {\n          const wasReAdded = usersAdded.some(\n            (u: any) =>\n              u?.getUid?.() === loggedInUser.current?.getUid?.() ||\n              u?.uid === loggedInUser.current?.getUid?.()\n          );\n          if (wasReAdded) {\n            setIsNoLongerMember(false);\n          }\n        }\n      },\n    });\n\n    CometChat.addGroupListener(\n      sdkListenerId,\n      new CometChat.GroupListener({\n        onGroupMemberKicked: (\n          _message: any,\n          kickedUser: any,\n          _kickedBy: any,\n          kickedFrom: any\n        ) => {\n          if (\n            kickedFrom?.getGuid?.() === group.getGuid() &&\n            kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(true);\n          }\n        },\n        onGroupMemberBanned: (\n          _message: any,\n          bannedUser: any,\n          _bannedBy: any,\n          bannedFrom: any\n        ) => {\n          if (\n            bannedFrom?.getGuid?.() === group.getGuid() &&\n            bannedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(true);\n          }\n        },\n        onMemberAddedToGroup: (\n          _message: any,\n          addedUser: any,\n          _addedBy: any,\n          addedTo: any\n        ) => {\n          if (\n            addedTo?.getGuid?.() === group.getGuid() &&\n            addedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(false);\n          }\n        },\n      })\n    );\n\n    return () => {\n      CometChatUIEventHandler.removeGroupListener(uiListenerId);\n      CometChat.removeGroupListener(sdkListenerId);\n    };\n  }, [group]);\n\n  return isNoLongerMember;\n};\n"
  },
  {
    "path": "examples/SampleApp/src/hooks/useIsKeyboardVisible.ts",
    "content": "import { useState, useEffect } from 'react';\nimport { Keyboard, KeyboardEvent } from 'react-native';\n\n/**\n * Custom hook that returns whether the keyboard is currently visible.\n * Uses React Native's Keyboard API directly, independent of ChatUiKit.\n * @returns {boolean} True if keyboard is visible, false otherwise.\n */\nexport const useIsKeyboardVisible = (): boolean => {\n  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);\n\n  useEffect(() => {\n    const showSubscription = Keyboard.addListener('keyboardDidShow', () => {\n      setIsKeyboardVisible(true);\n    });\n\n    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {\n      setIsKeyboardVisible(false);\n    });\n\n    return () => {\n      showSubscription.remove();\n      hideSubscription.remove();\n    };\n  }, []);\n\n  return isKeyboardVisible;\n};\n"
  },
  {
    "path": "examples/SampleApp/src/navigation/AuthContext.tsx",
    "content": "import React from 'react';\n\nexport interface AuthContextProps {\n  isLoggedIn: boolean;\n  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const AuthContext = React.createContext<AuthContextProps>({\n  isLoggedIn: false,\n  setIsLoggedIn: () => {},\n});\n"
  },
  {
    "path": "examples/SampleApp/src/navigation/BottomTabNavigator.tsx",
    "content": "import React from 'react';\nimport {\n  StyleSheet,\n  Platform,\n  View,\n  TouchableWithoutFeedback,\n  Text,\n} from 'react-native';\nimport {\n  createBottomTabNavigator,\n  BottomTabBarButtonProps,\n} from '@react-navigation/bottom-tabs';\nimport {useTheme, Icon, useCometChatTranslation } from '@cometchat/chat-uikit-react-native';\nimport {SCREEN_CONSTANTS} from '../utils/AppConstants';\nimport { useIsKeyboardVisible } from '../hooks/useIsKeyboardVisible';\nimport ChatFill from '../assets/icons/Chatfill';\nimport Chat from '../assets/icons/Chat';\nimport PersonFill from '../assets/icons/PersonFill';\nimport Person from '../assets/icons/Person';\nimport GroupFill from '../assets/icons/GroupFill';\nimport CallFill from '../assets/icons/CallFill';\nimport Call from '../assets/icons/Call';\nimport Group from '../assets/icons/Group';\nimport Conversations from '../components/conversations/screens/Conversations';\nimport Calls from '../components/calls/Calls';\nimport Users from '../components/users/Users';\nimport Groups from '../components/groups/Groups';\nimport {BottomTabParamList} from './types';\nimport { useConfig } from '../config/store';\n\n// Create the tab navigator.\nconst Tab = createBottomTabNavigator<BottomTabParamList>();\n\n// Define a type for icon components that accept color, height, and width props.\ntype IconComponentType = React.ComponentType<{\n  color?: string;\n  height?: number;\n  width?: number;\n}>;\n\n// Update the icons mapping to use the imported image components.\nconst icons: Record<\n  string,\n  {active: IconComponentType; inactive: IconComponentType}\n> = {\n  Chats: {active: ChatFill, inactive: Chat},\n  Users: {active: PersonFill, inactive: Person},\n  Calls: {active: CallFill, inactive: Call},\n  Groups: {active: GroupFill, inactive: Group},\n};\n\nconst CustomTabBarButton = ({children, onPress}: BottomTabBarButtonProps) => (\n  <TouchableWithoutFeedback onPress={onPress}>\n    <View style={styles.tabButton}>{children}</View>\n  </TouchableWithoutFeedback>\n);\n\nconst BottomTabNavigator = () => {\n  const theme = useTheme();\n  const tabs = useConfig(state => state.settings.layout.tabs);\n  const { t } = useCometChatTranslation();\n  // Use the custom hook to track keyboard visibility\n  const isKeyboardVisible = useIsKeyboardVisible();\n\n  // Map tab keys to screen names and components\n  const TAB_COMPONENTS: Record<string, { name: string; component: React.ComponentType<any> }> = {\n    chats: { name: SCREEN_CONSTANTS.CHATS, component: Conversations },\n    calls: { name: SCREEN_CONSTANTS.CALLS, component: Calls },\n    users: { name: SCREEN_CONSTANTS.USERS, component: Users },\n    groups: { name: SCREEN_CONSTANTS.GROUPS, component: Groups },\n  };\n\n  return (\n    <Tab.Navigator\n      initialRouteName=\"Chats\"\n      screenOptions={({route}) => ({\n        headerShown: false,\n        // Hide tab bar when keyboard is visible\n        tabBarStyle: isKeyboardVisible \n          ? { display: 'none' } \n          : styles.tabBar,\n        animation: 'none',\n        tabBarIcon: ({focused}) => {\n          const iconSet = icons[route.name];\n          if (!iconSet) return null;\n\n          const IconComponent = focused ? iconSet.active : iconSet.inactive;\n          const iconColor = focused\n            ? theme.color.primary\n            : theme.color.iconSecondary;\n\n          return (\n            <Icon\n              icon={\n                <IconComponent\n                  color={iconColor as string}\n                  height={24}\n                  width={24}\n                />\n              }\n            />\n          );\n        },\n        tabBarShowLabel: true,\n        tabBarLabel: ({focused}) => \n          focused ? (\n            <View>\n              <Text\n                style={[\n                  styles.tabLabel,\n                  {\n                    color: theme.color.primary,\n                    fontFamily: theme.typography.heading1.bold.fontFamily,\n                  },\n                ]}\n              >\n                {t(route.name.toUpperCase())}\n              </Text>\n            </View>\n       ) : null,\n        tabBarButton: props => <CustomTabBarButton {...props} />,\n        tabBarBackground: () => (\n          <View style={{backgroundColor: theme.color.background1, flex: 1}} />\n        ),\n      })}\n    >\n      {tabs.map(tabKey => {\n        const tab = TAB_COMPONENTS[tabKey.toLowerCase()];\n        return tab ? (\n          <Tab.Screen\n            key={tab.name}\n            name={tab.name as keyof BottomTabParamList}\n            component={tab.component}\n          />\n        ) : null;\n      })}\n    </Tab.Navigator>\n  );\n};\n\nconst styles = StyleSheet.create({\n  tabBar: {\n    height: Platform.OS === 'ios' ? 60 : 70,\n    paddingBottom: Platform.OS === 'ios' ? 0 : 10,\n    paddingTop: 15,\n    borderTopWidth: 0,\n    elevation: 5,\n    shadowColor: '#000',\n    shadowOffset: {width: 0, height: -2},\n    shadowOpacity: 0.1,\n    shadowRadius: 3,\n  },\n  tabLabel: {\n    fontSize: 12,\n    marginBottom: 5,\n  },\n  tabButton: {\n    flex: 1,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n});\n\nexport default BottomTabNavigator;\n"
  },
  {
    "path": "examples/SampleApp/src/navigation/NavigationService.ts",
    "content": "import {createNavigationContainerRef} from '@react-navigation/native';\nimport {RootStackParamList} from './types';\n\nexport const navigationRef = createNavigationContainerRef<RootStackParamList>();\n\ninterface PendingNavigation {\n  name: keyof RootStackParamList;\n  params?: any;\n}\n\nlet pendingNavigation: PendingNavigation | null = null;\n\nexport function navigate<RouteName extends keyof RootStackParamList>(\n  name: RouteName,\n  params?: RootStackParamList[RouteName] extends undefined\n    ? undefined\n    : RootStackParamList[RouteName],\n) {\n  if (navigationRef.isReady()) {\n    // navigationRef.navigate(name as never);\n    navigationRef.navigate(name as any, params as any); \n  } else {\n    // Save the navigation intent for later processing\n    pendingNavigation = {name, params};\n  }\n}\n\nexport function processPendingNavigation() {\n  if (pendingNavigation && navigationRef.isReady()) {\n    const {name, params} = pendingNavigation;\n    navigationRef.navigate(name as any, params as any);\n    pendingNavigation = null;\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/src/navigation/RootStackNavigator.tsx",
    "content": "import React from 'react';\nimport { createNativeStackNavigator } from '@react-navigation/native-stack';\nimport { NavigationContainer, DefaultTheme } from '@react-navigation/native';\nimport BottomTabNavigator from './BottomTabNavigator';\nimport OngoingCallScreen from '../components/conversations/screens/OngoingCallScreen';\nimport { SCREEN_CONSTANTS } from '../utils/AppConstants';\nimport { useTheme } from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from './types';\nimport { navigationRef, processPendingNavigation } from './NavigationService';\nimport SampleUser from '../components/login/SampleUser';\nimport AppCredentials from '../components/login/AppCredentials';\nimport { StatusBar, useColorScheme } from 'react-native';\nimport Conversations from '../components/conversations/screens/Conversations';\nimport CreateConversation from '../components/conversations/screens/CreateConversation';\nimport Messages from '../components/conversations/screens/Messages';\nimport ThreadView from '../components/conversations/screens/ThreadView';\nimport UserInfo from '../components/conversations/screens/UserInfo';\nimport AddMember from '../components/conversations/screens/AddMember';\nimport BannedMember from '../components/conversations/screens/BannedMember';\nimport ViewMembers from '../components/conversations/screens/ViewMembers';\nimport GroupInfo from '../components/conversations/screens/GroupInfo';\nimport TransferOwnership from '../components/conversations/screens/TransferOwnership';\nimport Calls from '../components/calls/Calls';\nimport { CallDetails } from '../components/calls/CallDetails';\nimport Users from '../components/users/Users';\nimport Groups from '../components/groups/Groups';\nimport AIAgents from '../components/AIAgent/AIAgents';\nimport QRScreen from '../components/conversations/screens/qr_screen';\nimport SearchMessages from '../components/conversations/screens/SearchMessages';\n\ntype Props = {\n  isLoggedIn: boolean;\n  hasValidAppCredentials: boolean;\n};\n\nconst Stack = createNativeStackNavigator<RootStackParamList>();\n\nconst RootStackNavigator = ({isLoggedIn, hasValidAppCredentials: _hasValidAppCredentials}: Props) => {\n  const theme = useTheme();\n  const NavigationTheme = {\n    ...DefaultTheme,\n    colors: {\n      ...DefaultTheme.colors,\n      background: theme.color.background1 as string,\n    },\n  };\n\n  const isDark = useColorScheme() === 'dark';\n  const backgroundColor = theme.color.background2;\n  const barStyle = isDark ? 'light-content' : 'dark-content';\n\n  return (\n    <>\n      <StatusBar\n        backgroundColor={backgroundColor}\n        barStyle={barStyle}\n        translucent={false}\n      />\n      <NavigationContainer\n        ref={navigationRef}\n        onReady={() => {\n          processPendingNavigation();\n        }}\n        theme={NavigationTheme}\n      >\n        <Stack.Navigator\n          initialRouteName={\n            isLoggedIn\n              ? SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR\n              : _hasValidAppCredentials\n              ? SCREEN_CONSTANTS.SAMPLE_USER\n              : SCREEN_CONSTANTS.APP_CRED\n          }\n          screenOptions={{\n            gestureEnabled: true,\n            headerShown: false,\n            animation: 'slide_from_right',\n          }}\n        >\n          {/* Auth Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.APP_CRED}\n            component={AppCredentials}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SAMPLE_USER}\n            component={SampleUser}\n          />\n\n          {/* Tab Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR}\n            component={BottomTabNavigator}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.USERS} component={Users} />\n          <Stack.Screen name={SCREEN_CONSTANTS.GROUPS} component={Groups} />\n          <Stack.Screen name={SCREEN_CONSTANTS.AI_AGENTS} component={AIAgents} />\n\n          {/* Chat Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CONVERSATION}\n            component={Conversations}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CREATE_CONVERSATION}\n            component={CreateConversation}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.MESSAGES} component={Messages} />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.THREAD_VIEW}\n            component={ThreadView}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SEARCH_MESSAGES}\n            component={SearchMessages}\n          />\n\n          {/* Info Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.USER_INFO}\n            component={UserInfo}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.GROUP_INFO}\n            component={GroupInfo}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.ADD_MEMBER}\n            component={AddMember}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.TRANSFER_OWNERSHIP}\n            component={TransferOwnership}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BANNED_MEMBER}\n            component={BannedMember}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.VIEW_MEMBER}\n            component={ViewMembers}\n          />\n\n          {/* Call Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.ONGOING_CALL_SCREEN}\n            component={OngoingCallScreen}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.CALL_LOGS} component={Calls} />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CALL_DETAILS}\n            component={CallDetails}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.QR_SCREEN} component={QRScreen} />\n        </Stack.Navigator>\n      </NavigationContainer>\n    </>\n  );\n};\n\nexport default RootStackNavigator;\n"
  },
  {
    "path": "examples/SampleApp/src/navigation/types.ts",
    "content": "import { NavigatorScreenParams } from '@react-navigation/native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\n\nexport type CallType = 'audio' | 'video';\n\nexport type RootStackParamList = {\n  Login: undefined;\n  BottomTabNavigator: NavigatorScreenParams<BottomTabParamList>;\n  OngoingCallScreen: { sessionId: string; callType?: CallType } | { call: any };\n  AppCredentials: undefined;\n  SampleUser: undefined;\n  Conversation: undefined;\n  CreateConversation: undefined;\n  Messages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    fromMention?: boolean;\n    fromMessagePrivately?: boolean;\n    parentMessageId?: string;\n    messageId?: string;\n    searchKeyword?: string;\n    navigatedFromSearch?: boolean;\n  };\n  SearchMessages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  };\n  AIAgents: undefined;\n  BannedMembers: undefined;\n  UserInfo: {\n    user: CometChat.User;\n  };\n  GroupInfo: {\n    group: CometChat.Group;\n  };\n  ThreadView: {\n    message: CometChat.BaseMessage;\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    highlightMessageId?: string;\n  };\n  AddMember: {\n    group: CometChat.Group;\n  };\n  TransferOwnershipSection: {\n    group: CometChat.Group;\n  };\n  BannedMember: {\n    group: CometChat.Group;\n  };\n  ViewMembers: {\n    group: CometChat.Group;\n  };\n  CallLogs: undefined;\n  CallDetails: {\n    call: any;\n  };\n  Users: undefined;\n  Groups: undefined;\n  QRScreen: undefined;\n};\n\nexport type BottomTabParamList = {\n  Chats: undefined;\n  Calls: undefined;\n  Users: undefined;\n  Groups: undefined;\n};\n"
  },
  {
    "path": "examples/SampleApp/src/utils/ActiveChatContext.tsx",
    "content": "import React, { createContext, useContext, useState } from 'react';\n\ntype ActiveChat = {\n  type: 'user' | 'group';\n  id: string; // userUID or groupID\n} | null;\n\ntype ActiveChatContextType = {\n  activeChat: ActiveChat;\n  setActiveChat: React.Dispatch<React.SetStateAction<ActiveChat>>;\n};\n\nconst ActiveChatContext = createContext<ActiveChatContextType | undefined>(undefined);\n\nexport function ActiveChatProvider({ children }: { children: React.ReactNode }) {\n  const [activeChat, setActiveChat] = useState<ActiveChat>(null);\n\n  return (\n    <ActiveChatContext.Provider value={{ activeChat, setActiveChat }}>\n      {children}\n    </ActiveChatContext.Provider>\n  );\n}\n\n// Hook for using in any component\nexport function useActiveChat() {\n  const context = useContext(ActiveChatContext);\n  if (!context) {\n    throw new Error('useActiveChat must be used within an ActiveChatProvider');\n  }\n  return context;\n}\n"
  },
  {
    "path": "examples/SampleApp/src/utils/AppConstants.tsx",
    "content": "export const AppConstants = {\n  fcmProviderId: '',\n  apnsProviderId: '',\n  authKey: '',\n  appId: '',\n  region: '',\n  subscriptionType: 'ALL_USERS',\n  versionNumber: 'V5.3.4',\n  webClientId:\n    '',\n  iosClientId:\n    '',\n};\n\nexport const SCREEN_CONSTANTS = {\n  LOGIN: 'Login',\n  APP_CRED: 'AppCredentials',\n  SAMPLE_USER: 'SampleUser',\n  ONGOING_CALL_SCREEN: 'OngoingCallScreen',\n  BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator',\n  CHATS: 'Chats',\n  CALLS: 'Calls',\n  USERS: 'Users',\n  GROUPS: 'Groups',\n  CONVERSATION: 'Conversation',\n  CREATE_CONVERSATION: 'CreateConversation',\n  MESSAGES: 'Messages',\n  SEARCH_MESSAGES: 'SearchMessages',\n  THREAD_VIEW: 'ThreadView',\n  USER_INFO: 'UserInfo',\n  GROUP_INFO: 'GroupInfo',\n  ADD_MEMBER: 'AddMember',\n  TRANSFER_OWNERSHIP: 'TransferOwnershipSection',\n  BANNED_MEMBER: 'BannedMember',\n  VIEW_MEMBER: 'ViewMembers',\n  CALL_LOGS: 'CallLogs',\n  CALL_DETAILS: 'CallDetails',\n  QR_SCREEN: 'QRScreen',\n  AI_AGENTS: 'AIAgents',\n} as const;\n"
  },
  {
    "path": "examples/SampleApp/src/utils/CommonUtils.ts",
    "content": "export class CommonUtils {\n  static clone<T extends any>(arg: T): T {\n    /*\n    If there are additional properties attached to a function or an array object other than the standard properties, those properties will be ignored\n    Cannot copy private properties (those that start with a \"#\" symbol inside a class block)\n    Functions are copied by reference\n    */\n    if (typeof arg !== 'object' || !arg) {\n      return arg;\n    }\n    let res;\n    if (Array.isArray(arg)) {\n      // arg is an array, there's no hatch to fool the Array.isArray method, so lets create an array\n      res = [];\n      for (const value of arg) {\n        res.push(CommonUtils.clone(value));\n      }\n      return res as T;\n    } else {\n      // arg is an object\n      res = {};\n      const descriptor = Object.getOwnPropertyDescriptors(arg);\n      for (const k of Reflect.ownKeys(descriptor)) {\n        const curDescriptor = descriptor[k as any];\n        if (curDescriptor.hasOwnProperty('value')) {\n          // Property is a data property\n          Object.defineProperty(res, k, {\n            ...curDescriptor,\n            value: CommonUtils.clone(curDescriptor['value']),\n          });\n        } else {\n          // Property is an accessor property\n          Object.defineProperty(res, k, curDescriptor);\n        }\n      }\n      Object.setPrototypeOf(res, Object.getPrototypeOf(arg));\n    }\n    return res as T;\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/src/utils/TooltipMenu.tsx",
    "content": "import { Icon, useTheme } from \"@cometchat/chat-uikit-react-native\";\nimport { JSX, useMemo } from \"react\";\nimport {\n  ColorValue,\n  Dimensions,\n  ImageSourcePropType,\n  Modal,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  TouchableWithoutFeedback,\n  View,\n} from \"react-native\";\n\nconst { width: screenWidth, height: screenHeight } = Dimensions.get(\"window\");\n\ntype CometChatTooltipMenuProps = {\n  visible?: boolean;\n  onDismiss?: () => void;\n  onClose?: () => void;\n  event: {\n    nativeEvent: {\n      pageX: number;\n      pageY: number;\n    };\n  };\n  menuItems: {\n    text: string;\n    onPress: () => void;\n    textColor?: ColorValue;\n    iconColor?: ColorValue;\n    icon?: ImageSourcePropType | JSX.Element;\n  }[];\n};\n\nexport const TooltipMenu = (props: CometChatTooltipMenuProps) => {\n  const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;\n  const theme = useTheme();\n\n  const position = useMemo(() => {\n    let x = event.nativeEvent.pageX;\n    let y = event.nativeEvent.pageY;\n    const position: {\n      left?: number;\n      right?: number;\n      top?: number;\n      bottom?: number;\n    } = {};\n    if (x <= screenWidth / 3) {\n      position.left = x + 10;\n    } else {\n      position.right = 12;\n    }\n\n    if (y <= screenHeight / 2) {\n      position.top = y + 20;\n    } else if (y >= screenHeight / 2) {\n      position.bottom = Math.max(screenHeight - y + 10, 40);\n    }\n    return position;\n  }, [event]);\n\n  return (\n    <Modal\n      presentationStyle='overFullScreen'\n      transparent={true}\n      visible={visible}\n      onRequestClose={onClose}\n      onDismiss={onDismiss}\n      animationType='fade'\n    >\n      <TouchableWithoutFeedback onPress={onClose}>\n        <View style={tooltipStyles.overlay}>\n          <View\n            style={[\n              tooltipStyles.menu,\n              position,\n              {\n                backgroundColor: theme.color.background1,\n                borderWidth: 1,\n                borderColor: theme.color.borderLight,\n                borderRadius: theme.spacing.radius.r2,\n                shadowColor: theme.color.neutral900,\n              },\n            ]}\n          >\n            {menuItems.map((item, i) => {\n              return (\n                <TouchableOpacity\n                  key={i} // Ensure each item has a unique key\n                  onPress={() => {\n                    item.onPress();\n                    onClose();\n                  }}\n                  style={[\n                    {\n                      flexDirection: \"row\",\n                      alignItems: \"center\",\n                      paddingVertical: 10,\n                      paddingHorizontal: 16,\n                      gap: 8,\n                      backgroundColor: theme.color.background1,\n                      minWidth: 160,\n                    },\n                    i === 0\n                      ? {\n                          borderTopLeftRadius: theme.spacing.radius.r2,\n                          borderTopRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                    i === menuItems.length - 1\n                      ? {\n                          borderBottomLeftRadius: theme.spacing.radius.r2,\n                          borderBottomRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                  ]}\n                >\n                  <Icon\n                    color={item.iconColor ?? theme.color.textSecondary}\n                    size={theme.spacing.spacing.s6}\n                    icon={item.icon}\n                  />\n                  <Text\n                    style={[\n                      {\n                        color: item.textColor ?? theme.color.textPrimary,\n                        ...theme.typography.heading4.regular,\n                      },\n                    ]}\n                  >\n                    {item.text}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n      </TouchableWithoutFeedback>\n    </Modal>\n  );\n};\n\nconst tooltipStyles = StyleSheet.create({\n  overlay: {\n    flex: 1,\n    backgroundColor: \"transparent\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  menu: {\n    position: \"absolute\",\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.025,\n    shadowRadius: 4,\n    elevation: 3,\n  },\n  menuItem: {\n    fontSize: 16,\n    paddingVertical: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleApp/src/utils/helper.ts",
    "content": "import {Platform, PermissionsAndroid} from 'react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport Ironman from '../assets/icons/ironman.png';\nimport Captainamerica from '../assets/icons/captainamerica.png';\nimport Wolverine from '../assets/icons/wolverine.png';\nimport Spiderman from '../assets/icons/spiderman.png';\nimport Cyclops from '../assets/icons/cyclops.png';\nimport {\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  NavigationContainerRefWithCurrent,\n  StackActions,\n} from '@react-navigation/native';\nimport {RootStackParamList} from '../navigation/types';\nimport {SCREEN_CONSTANTS} from './AppConstants';\n\ninterface Translations {\n  lastSeen: string;\n  minutesAgo: (minutes: number) => string;\n  hoursAgo: (hours: number) => string;\n}\n\ninterface NotifeeData {\n  receiverType?: 'user' | 'group';\n  conversationId?: string;\n  sender?: string;\n  messageId?: string;\n  parentId?: string;\n  [key: string]: any;\n}\n\n/**\n * Request common Android permissions (notifications, camera, etc.)\n * Only needed on Android.\n */\nexport async function requestAndroidPermissions() {\n  if (Platform.OS !== 'android') return;\n  try {\n    await PermissionsAndroid.requestMultiple([\n      PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.CAMERA,\n      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,\n      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,\n    ]);\n  } catch (err) {\n    console.warn('Android permissions error:', err);\n  }\n}\n\n/**\n * getLastSeenTime UserInfoSection.\n */\nexport function getLastSeenTime(\n  timestamp: number | null,\n  translations: Translations,\n): string {\n  if (timestamp === null) {\n    return `${translations.lastSeen} Unknown`;\n  }\n\n  // If timestamp is in seconds (length = 10), convert to milliseconds.\n  if (String(timestamp).length === 10) {\n    timestamp *= 1000;\n  }\n\n  const now = new Date();\n  const lastSeen = new Date(timestamp);\n\n  // Calculate the time differences\n  const diffInMillis = now.getTime() - lastSeen.getTime();\n  const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));\n  const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));\n\n  // Check if within last hour\n  if (diffInMinutes === 0) {\n    return `${translations.lastSeen} ${translations.minutesAgo(1)}`;\n  } else if (diffInMinutes < 60) {\n    return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;\n  }\n\n  // Check if within the last 24 hours\n  if (diffInHours < 24) {\n    return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;\n  }\n\n  // Determine if timestamp is within the current year\n  const isSameYear = lastSeen.getFullYear() === now.getFullYear();\n\n  // Options for date formatting\n  const dateOptions: Intl.DateTimeFormatOptions = {\n    day: '2-digit',\n    month: 'short',\n    ...(isSameYear ? {} : { year: 'numeric' }),\n  };\n\n  // Options for time formatting\n  const timeOptions: Intl.DateTimeFormatOptions = {\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: true,\n  };\n\n  const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);\n  const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);\n  if (formattedDate === 'Invalid Date' || formattedTime === 'Invalid Date') {\n    return `Offline`;\n  }\n\n  return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;\n}\n\n/**\n * UNBLOCK\n */\nexport const unblock = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n  setUserObj: React.Dispatch<React.SetStateAction<CometChat.User>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.unblockUsers([uid]);\n    const unBlockedUser = await CometChat.getUser(uid);\n    if (response) {\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, {\n        user: unBlockedUser,\n      });\n      setBlocked(false);\n      setUserObj(unBlockedUser);\n    } else {\n      console.log(\n        `Failed to unblock user with UID ${uid}. Response:`,\n        response,\n      );\n    }\n  } catch (error) {\n    console.error('Error unblocking user:', error);\n  }\n};\n\n/**\n * BLOCK\n */\nexport const blockUser = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.blockUsers([uid]);\n    if (response) {\n      user.setBlockedByMe(true);\n      setBlocked(true);\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, {\n        user,\n      });\n    } else {\n      console.log(`Failed to block user with UID ${uid}. Response:`, response);\n    }\n  } catch (error) {\n    console.error('Error blocking user:', error);\n  }\n};\n\n/**\n * LEAVE GROUP\n */\nexport const leaveGroup = (\n  group: CometChat.Group,\n  navigation: any,\n  pop: number,\n) => {\n  if (group) {\n    const groupID = group.getGuid();\n    CometChat.leaveGroup(groupID).then(\n      () => {\n        let actionMessage: CometChat.Action = new CometChat.Action(\n          groupID,\n          CometChat.MESSAGE_TYPE.TEXT,\n          CometChat.RECEIVER_TYPE.GROUP,\n          CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n        );\n        actionMessage.setMessage(\n          `${CometChatUIKit.loggedInUser!.getName()} has left`,\n        );\n        // Initialize data to prevent crash when SDK accesses getData().metadata during render\n        actionMessage.setData({ metadata: {} });\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          message: actionMessage, //Note: Add Action message after discussion\n          leftUser: CometChatUIKit.loggedInUser,\n          leftGroup: group,\n        });\n        navigation.pop(pop);\n      },\n      error => {\n        console.log('Group leaving failed:', error);\n      },\n    );\n  } else {\n    console.log('Group is not defined');\n  }\n};\n\n/**\n * Sample Users Data\n */\nexport const sampleData = {\n  users: [\n    {uid: 'superhero1', name: 'Iron Man', avatar: Ironman},\n    {uid: 'superhero2', name: 'Captain America', avatar: Captainamerica},\n    {uid: 'superhero3', name: 'Spiderman', avatar: Spiderman},\n    {uid: 'superhero4', name: 'Wolverine', avatar: Wolverine},\n    {uid: 'superhero5', name: 'Cyclops', avatar: Cyclops},\n  ],\n};\n\n/**\n * Navigate to conversation based on notification data.\n */\nexport async function navigateToConversation(\n  navigationRef: NavigationContainerRefWithCurrent<RootStackParamList>,\n  data?: NotifeeData,\n) {\n  if (!data || !navigationRef.current) {\n    return;\n  }\n  \n  try {\n    // Handle group\n    if (data.receiverType === 'group') {\n      const extractedId =\n        typeof data.conversationId === 'string'\n          ? data.conversationId.split('_').slice(1).join('_')\n          : '';\n      const group = await CometChat.getGroup(extractedId);\n\n      // Navigate with parent message ID if available (for agentic conversations)\n      const params: any = {group};\n      if (data.parentId) {\n        params.parentMessageId = data.parentId;\n      }\n\n      navigationRef.current?.dispatch(StackActions.push(SCREEN_CONSTANTS.MESSAGES, params));\n    }\n\n    // Handle user\n    else if (data.receiverType === 'user') {\n      const ccUser = await CometChat.getUser(data.sender);\n\n      // Navigate with parent message ID if available (for agentic conversations)\n      const params: any = {user: ccUser};\n      if (data.parentId) {\n        params.parentMessageId = data.parentId;\n      }\n\n      navigationRef.current?.dispatch(\n        StackActions.push(SCREEN_CONSTANTS.MESSAGES, params),\n      );\n    }\n  } catch (error) {\n    console.log('Error in navigateToConversation:', error);\n  }\n}\n"
  },
  {
    "path": "examples/SampleApp/src/utils/themeTypography.ts",
    "content": "import { Platform } from 'react-native';\n\nexport type TypographyVariant = {\n  regular: { fontFamily: string };\n  medium: { fontFamily: string };\n  bold: { fontFamily: string };\n};\n\nexport type Typography = {\n  fontFamily: string;\n  link: { fontFamily: string };\n} & Record<\n  | \"title\"\n  | \"heading1\"\n  | \"heading2\"\n  | \"heading3\"\n  | \"heading4\"\n  | \"body\"\n  | \"caption1\"\n  | \"caption2\"\n  | \"button\",\n  TypographyVariant\n>;\n\n// Simple font mapping - response name to file names (without extension)\nconst FONT_MAP: Record<string, { regular: string; medium: string; bold: string }> = {\n  'times new roman': {\n    regular: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_regular',\n    medium: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_medium',\n    bold: Platform.OS === 'ios' ? 'TimesNewRomanPS-BoldMT' : 'times_new_roman_bold',\n  },\n  'inter': {\n    regular: Platform.OS === 'ios' ? 'Inter-Regular' : 'inter_regular',\n    medium: Platform.OS === 'ios' ? 'Inter-Medium' : 'inter_medium',\n    bold: Platform.OS === 'ios' ? 'Inter-Bold' : 'inter_bold',\n  },\n  'roboto': {\n    regular: Platform.OS === 'ios' ? 'Roboto-Regular' : 'roboto_regular',\n    medium: Platform.OS === 'ios' ? 'Roboto-Medium' : 'roboto_medium',\n    bold: Platform.OS === 'ios' ? 'Roboto-Bold' : 'roboto_bold',\n  },\n};\n\n/**\n * Creates a complete Typography object for CometChat theme.\n * Normalizes font name, provides fallback, and builds all variants.\n * \n * @param font - The font name from backend (e.g., 'inter', 'roboto', 'times new roman')\n * @returns Complete Typography object with all variants configured\n */\nexport const createTypography = (font: string): Typography => {\n  const types = [\n    \"title\",\n    \"heading1\",\n    \"heading2\",\n    \"heading3\",\n    \"heading4\",\n    \"body\",\n    \"caption1\",\n    \"caption2\",\n    \"button\",\n  ] as const;\n\n  // Normalize font name from backend and provide fallback\n  const fontKey = font ? font.toLowerCase().trim() : '';\n  const fontVariants = FONT_MAP[fontKey] || FONT_MAP['times new roman'];\n\n  // Defensive: ensure all variants exist\n  const baseStyle: TypographyVariant = {\n    regular: { fontFamily: fontVariants.regular },\n    medium: { fontFamily: fontVariants.medium },\n    bold: { fontFamily: fontVariants.bold },\n  };\n\n  const typography: Typography = {\n    fontFamily: fontVariants.regular,\n    link: { fontFamily: fontVariants.regular },\n  } as Typography;\n\n  types.forEach((type) => {\n    typography[type] = { ...baseStyle };\n  });\n\n  return typography;\n};"
  },
  {
    "path": "examples/SampleApp/tsconfig.json",
    "content": "{\n  \"extends\": \"@react-native/typescript-config\",\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"**/node_modules\", \"**/Pods\"]\n}\n"
  },
  {
    "path": "examples/SampleAppAI/.bundle/config",
    "content": "BUNDLE_PATH: \"vendor/bundle\"\nBUNDLE_FORCE_RUBY_PLATFORM: 1\n"
  },
  {
    "path": "examples/SampleAppAI/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: '@react-native',\n};\n"
  },
  {
    "path": "examples/SampleAppAI/.gitignore",
    "content": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\n**/.xcode.env.local\n\n# Android/IntelliJ\n#\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n*.hprof\n.cxx/\n*.keystore\n!debug.keystore\n.kotlin/\n\n# node.js\n#\nnode_modules/\nnpm-debug.log\nyarn-error.log\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/\n\n**/fastlane/report.xml\n**/fastlane/Preview.html\n**/fastlane/screenshots\n**/fastlane/test_output\n\n# Bundle artifact\n*.jsbundle\n\n# Ruby / CocoaPods\n**/Pods/\n/vendor/bundle/\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# testing\n/coverage\n\n# Yarn\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n"
  },
  {
    "path": "examples/SampleAppAI/.prettierrc.js",
    "content": "module.exports = {\n  arrowParens: 'avoid',\n  singleQuote: true,\n  trailingComma: 'all',\n};\n"
  },
  {
    "path": "examples/SampleAppAI/.watchmanconfig",
    "content": "{}\n"
  },
  {
    "path": "examples/SampleAppAI/App.tsx",
    "content": "import { StatusBar, useColorScheme } from 'react-native';\nimport './gesture-handler';\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n  Platform,\n  View,\n  PlatformColor,\n  AppState,\n  AppStateStatus,\n} from 'react-native';\nimport { enableScreens } from 'react-native-screens';\nenableScreens();\nimport {\n  CometChatIncomingCall,\n  CometChatThemeProvider,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  UIKitSettings,\n} from '@cometchat/chat-uikit-react-native';\n\nimport { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';\nimport { GestureHandlerRootView } from 'react-native-gesture-handler';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport RootStackNavigator from './src/navigation/RootStackNavigator';\nimport { AppConstants } from './src/utils/AppConstants';\nimport { requestAndroidPermissions } from './src/utils/helper';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\nfunction App() {\n  const isDarkMode = useColorScheme() === 'dark';\n\n  return (\n    <GestureHandlerRootView style={{ flex: 1 }}>\n    <SafeAreaProvider>\n      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />\n      <AppContent />\n    </SafeAreaProvider>\n    </GestureHandlerRootView>\n  );\n}\n\nconst AppContent = (): React.ReactElement => {\n  const [callReceived, setCallReceived] = useState(false);\n  const incomingCall = useRef<CometChat.Call | CometChat.CustomMessage | null>(\n    null,\n  );\n  const [isInitializing, setIsInitializing] = useState(true);\n  const [isLoggedIn, setIsLoggedIn] = useState(false);\n  const [userLoggedIn, setUserLoggedIn] = useState(false);\n  const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);\n  // Listener ID for registering and removing CometChat listeners.\n  const listenerId = 'app';\n\n  /**\n   * Initialize CometChat UIKit and configure Google Sign-In.\n   * Retrieves credentials from AsyncStorage and uses fallback constants if needed.\n   */\n  useEffect(() => {\n    async function init() {\n      try {\n        // Retrieve stored app credentials or default to an empty object.\n        const AppData = (await AsyncStorage.getItem('appCredentials')) || '{}';\n        const storedCredentials = JSON.parse(AppData);\n\n        // Determine the final credentials (from AsyncStorage or AppConstants).\n        const finalAppId = storedCredentials.appId || AppConstants.appId;\n        const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;\n        const finalRegion = storedCredentials.region || AppConstants.region;\n\n        // Set hasValidAppCredentials based on whether all values are available.\n        if (finalAppId && finalAuthKey && finalRegion) {\n          setHasValidAppCredentials(true);\n        } else {\n          setHasValidAppCredentials(false);\n        }\n\n        await CometChatUIKit.init({\n          appId: finalAppId,\n          authKey: finalAuthKey,\n          region: finalRegion,\n          subscriptionType: CometChat.AppSettings\n            .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n        });\n\n        // If a user is already logged in, update the state.\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        if (loggedInUser) {\n          setIsLoggedIn(true);\n        }\n      } catch (error) {\n        console.log('Error during initialization', error);\n      } finally {\n        // Mark initialization as complete.\n        setIsInitializing(false);\n      }\n    }\n    init();\n  }, []);\n\n  /**\n   * Monitor app state changes to verify the logged-in status and clear notifications.\n   * When the app becomes active, it cancels Android notifications and checks the login status.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android') {\n      // Request required Android permissions for notifications.\n      requestAndroidPermissions();\n    }\n    const handleAppStateChange = async (nextState: AppStateStatus) => {\n      if (nextState === 'active') {\n        try {\n          // Verify if there is a valid logged-in user.\n          const chatUser = await CometChat.getLoggedinUser();\n          setIsLoggedIn(!!chatUser);\n        } catch (error) {\n          console.log('Error verifying CometChat user on resume:', error);\n        }\n      }\n    };\n    const subscription = AppState.addEventListener(\n      'change',\n      handleAppStateChange,\n    );\n    return () => subscription.remove();\n  }, []);\n\n  /**\n   * Attach CometChat login listener to handle login and logout events.\n   * Updates user login status accordingly.\n   */\n  useEffect(() => {\n    CometChat.addLoginListener(\n      listenerId,\n      new CometChat.LoginListener({\n        loginSuccess: () => {\n          setUserLoggedIn(true);\n        },\n        loginFailure: (e: CometChat.CometChatException) => {\n          console.log('LoginListener :: loginFailure', e.message);\n        },\n        logoutSuccess: () => {\n          setUserLoggedIn(false);\n        },\n        logoutFailure: (e: CometChat.CometChatException) => {\n          console.log('LoginListener :: logoutFailure', e.message);\n        },\n      }),\n    );\n\n    // Clean up the login listener on component unmount.\n    return () => {\n      CometChat.removeLoginListener(listenerId);\n    };\n  }, []);\n\n  /**\n   * Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.\n   * Also handles UI events for call end.\n   */\n  useEffect(() => {\n    // Listener for call events.\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onIncomingCallReceived: (call: CometChat.Call) => {\n          // Check if there's already an active call\n          try {\n            const activeCall = CometChat.getActiveCall();\n            if (activeCall) {\n              // If there's an active call, reject the incoming call with busy status\n              setTimeout(() => {\n                CometChat.rejectCall(\n                  call.getSessionId(),\n                  CometChat.CALL_STATUS.BUSY,\n                )\n                  .then(() => {\n                    console.log('Incoming call rejected due to active call');\n                  })\n                  .catch(error => {\n                    console.error(\n                      'Error rejecting call with busy status:',\n                      error,\n                    );\n                  });\n              }, 2000);\n            } else {\n              // No active call, proceed with normal incoming call handling\n              // Hide any bottom sheet UI before showing the incoming call screen.\n              CometChatUIEventHandler.emitUIEvent(\n                CometChatUIEvents.ccToggleBottomSheet,\n                {\n                  isBottomSheetVisible: false,\n                },\n              );\n              // Store the incoming call and update state.\n              incomingCall.current = call;\n              setCallReceived(true);\n            }\n          } catch (error) {\n            console.error('Error getting active call:', error);\n            // If error getting active call, proceed with normal handling\n            CometChatUIEventHandler.emitUIEvent(\n              CometChatUIEvents.ccToggleBottomSheet,\n              {\n                isBottomSheetVisible: false,\n              },\n            );\n            incomingCall.current = call;\n            setCallReceived(true);\n          }\n        },\n        onOutgoingCallRejected: () => {\n          // Clear the call state if outgoing call is rejected.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n        onIncomingCallCancelled: () => {\n          // Clear the call state if the incoming call is cancelled.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n      }),\n    );\n\n    // Additional listener to handle call end events.\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallEnded: () => {\n        incomingCall.current = null;\n        setCallReceived(false);\n      },\n    });\n\n    // Remove call listeners on cleanup.\n    return () => {\n      CometChatUIEventHandler.removeCallListener(listenerId);\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [userLoggedIn]);\n\n  // Show a blank/splash screen while the app is initializing.\n  if (isInitializing) {\n    return (\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: Platform.select({\n            ios: PlatformColor('systemBackgroundColor'),\n            android: PlatformColor('?android:attr/colorBackground'),\n          }),\n        }}\n      />\n    );\n  }\n\n  // Once initialization is complete, render the main app UI.\n  return (\n    <SafeAreaView edges={['top', 'bottom']} style={{ flex: 1 }}>\n      <CometChatThemeProvider>\n        {/* Render the incoming call UI if the user is logged in and a call is received */}\n        {isLoggedIn && callReceived && incomingCall.current ? (\n          <CometChatIncomingCall\n            call={incomingCall.current}\n            onDecline={() => {\n              // Handle call decline by clearing the incoming call state.\n              incomingCall.current = null;\n              setCallReceived(false);\n            }}\n            style={{\n              containerStyle: {\n                marginTop: Platform.OS === 'android' ? 40 : 0,\n              },\n            }}\n          />\n        ) : null}\n        {/* Render the main navigation stack, passing the login status as a prop */}\n        <RootStackNavigator\n          isLoggedIn={isLoggedIn}\n          hasValidAppCredentials={hasValidAppCredentials}\n        />\n      </CometChatThemeProvider>\n    </SafeAreaView>\n  );\n};\n\nexport default App;\n"
  },
  {
    "path": "examples/SampleAppAI/AppErrorBoundary.tsx",
    "content": "import React from 'react';\nimport { View, Text, StyleSheet, Appearance, Button } from 'react-native';\n\ninterface State {\n  hasError: boolean;\n  error: Error | null;\n  colorScheme: 'light' | 'dark' | null;\n}\n\ninterface Props {\n  children: React.ReactNode;\n}\n\nclass AppErrorBoundary extends React.Component<Props, State> {\n  constructor(props: Props) {\n    super(props);\n    const colorScheme = Appearance.getColorScheme() ?? null;\n    this.state = { hasError: false, error: null, colorScheme };\n  }\n\n  static getDerivedStateFromError(error: Error) {\n    // Update state so the next render shows the fallback UI.\n    return { hasError: true, error };\n  }\n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    // Log error to an error reporting service here if needed.\n    console.log('ErrorBoundary caught an error', error, errorInfo);\n  }\n\n  componentDidMount() {\n    // Listen for changes in color scheme.\n    Appearance.addChangeListener(({ colorScheme }) => {\n      this.setState({ colorScheme: colorScheme ?? null });\n    });\n  }\n\n  // Reset error state to allow retrying\n  handleRetry = () => {\n    this.setState({ hasError: false, error: null });\n  };\n\n  render() {\n    if (this.state.hasError) {\n      const styles = createStyles(this.state.colorScheme);\n      return (\n        <View style={styles.container}>\n          <View style={styles.card}>\n            <Text style={styles.title}>Something went wrong</Text>\n             {/* Uncomment the next line to show the error message */}\n            {/* <Text style={styles.errorText}>\n              {this.state.error ? this.state.error.toString() : 'Unknown error'}\n            </Text> */}\n            <View style={styles.buttonContainer}>\n              <Button title=\"Retry\" onPress={this.handleRetry} color={this.state.colorScheme === 'dark' ? \"#bbbbbb\" : \"#333333\"} />\n            </View>\n          </View>\n        </View>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst createStyles = (colorScheme: 'light' | 'dark' | null) => {\n  const isDark = colorScheme === 'dark';\n  return StyleSheet.create({\n    container: {\n      flex: 1,\n      backgroundColor: isDark ? '#121212' : '#f2f2f2',\n      justifyContent: 'center',\n      alignItems: 'center',\n      padding: 16,\n    },\n    card: {\n      backgroundColor: isDark ? '#1e1e1e' : '#ffffff',\n      padding: 24,\n      borderRadius: 12,\n      alignItems: 'center',\n      shadowColor: isDark ? '#000000' : '#aaa',\n      shadowOffset: { width: 0, height: 2 },\n      shadowOpacity: 0.3,\n      shadowRadius: 4,\n      elevation: 5,\n      width: '100%',\n      maxWidth: 400,\n    },\n    title: {\n      fontSize: 20,\n      fontWeight: '700',\n      marginBottom: 12,\n      color: isDark ? '#ffffff' : '#333333',\n    },\n    errorText: {\n      fontSize: 16,\n      textAlign: 'center',\n      color: isDark ? '#cccccc' : '#666666',\n      marginBottom: 20,\n    },\n    buttonContainer: {\n      width: '100%',\n    },\n  });\n};\n\nexport default AppErrorBoundary;\n"
  },
  {
    "path": "examples/SampleAppAI/Gemfile",
    "content": "source 'https://rubygems.org'\n\n# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version\nruby \">= 2.6.10\"\n\n# Exclude problematic versions of cocoapods and activesupport that causes build failures.\ngem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'\ngem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'\ngem 'xcodeproj', '< 1.26.0'\ngem 'concurrent-ruby', '< 1.3.4'\n\n# Ruby 3.4.0 has removed some libraries from the standard library.\ngem 'bigdecimal'\ngem 'logger'\ngem 'benchmark'\ngem 'mutex_m'\n"
  },
  {
    "path": "examples/SampleAppAI/README.md",
    "content": "\n<p align=\"center\">\n  <img alt=\"CometChat\" src=\"https://assets.cometchat.io/website/images/logos/banner.png\">\n</p>\n\n# React Native AI Agents Sample App by CometChat\n\nThis reference application demonstrates how to seamlessly integrate CometChat’s [React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) into a React Native project. It showcases the implementation of AI-powered chat agents using the UI Kit, highlighting how developers can leverage its prebuilt components to create rich, interactive messaging experiences with minimal effort.\n\n<div style=\"display: flex; align-items: center; justify-content: center\">\n   <img src=\"../../screenshots/ai-agents-overview.png\" />\n</div>\n\n\n## Prerequisites\n\nSign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_\n\n- **Node.js** 18 or higher\n- **React Native** Version 0.77 or later (up to the latest version) \n\n**iOS**\n- XCode\n- Pod (CocoaPods) for iOS\n- An iOS device or emulator with iOS 12.0 or above.\n- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.\n\n**Android**\n- Android Studio\n- Android device or emulator with Android version 5.0 or above.\n\n\n## Installation\n\n1. Clone the repository:\n   ```sh\n   git clone https://github.com/cometchat/cometchat-uikit-react-native.git\n   ```\n\n1. Change into the specific app's directory (e.g., SampleAppAI).\n   ```sh\n     cd examples/SampleAppAI\n   ```\n1. Run `npm install` to install the dependencies.\n\n1. `[Optional]` Configure CometChat credentials:\n    - Open the `AppConstants.tsx` file located at `examples/SampleApp/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:\n      ```ts\n      export const AppConstants = {\n          appId: 'YOUR_APP_ID',\n          authKey: 'YOUR_AUTH_KEY',\n          region: 'REGION',\n          //other properties\n      }\n      ```\n\n1. For iOS, install dependencies after navigating to ios:\n   ```sh\n    cd ios\n    pod install\n   ```\n\n1. Run the app on a device or emulator from the repo root.\n   ```sh\n    npm start\n    npm run android\n    npm run ios\n   ```\n\n\n## Help and Support\n\nFor issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/)."
  },
  {
    "path": "examples/SampleAppAI/android/app/build.gradle",
    "content": "apply plugin: \"com.android.application\"\napply plugin: \"org.jetbrains.kotlin.android\"\napply plugin: \"com.facebook.react\"\n\n/**\n * This is the configuration block to customize your React Native Android app.\n * By default you don't need to apply any configuration, just uncomment the lines you need.\n */\nreact {\n    /* Folders */\n    //   The root of your project, i.e. where \"package.json\" lives. Default is '../..'\n    // root = file(\"../../\")\n    //   The folder where the react-native NPM package is. Default is ../../node_modules/react-native\n    // reactNativeDir = file(\"../../node_modules/react-native\")\n    //   The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen\n    // codegenDir = file(\"../../node_modules/@react-native/codegen\")\n    //   The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js\n    // cliFile = file(\"../../node_modules/react-native/cli.js\")\n\n    /* Variants */\n    //   The list of variants to that are debuggable. For those we're going to\n    //   skip the bundling of the JS bundle and the assets. By default is just 'debug'.\n    //   If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.\n    // debuggableVariants = [\"liteDebug\", \"prodDebug\"]\n\n    /* Bundling */\n    //   A list containing the node command and its flags. Default is just 'node'.\n    // nodeExecutableAndArgs = [\"node\"]\n    //\n    //   The command to run when bundling. By default is 'bundle'\n    // bundleCommand = \"ram-bundle\"\n    //\n    //   The path to the CLI configuration file. Default is empty.\n    // bundleConfig = file(../rn-cli.config.js)\n    //\n    //   The name of the generated asset file containing your JS bundle\n    // bundleAssetName = \"MyApplication.android.bundle\"\n    //\n    //   The entry file for bundle generation. Default is 'index.android.js' or 'index.js'\n    // entryFile = file(\"../js/MyApplication.android.js\")\n    //\n    //   A list of extra flags to pass to the 'bundle' commands.\n    //   See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle\n    // extraPackagerArgs = []\n\n    /* Hermes Commands */\n    //   The hermes compiler command to run. By default it is 'hermesc'\n    // hermesCommand = \"$rootDir/my-custom-hermesc/bin/hermesc\"\n    //\n    //   The list of flags to pass to the Hermes compiler. By default is \"-O\", \"-output-source-map\"\n    // hermesFlags = [\"-O\", \"-output-source-map\"]\n\n    /* Autolinking */\n    autolinkLibrariesWithApp()\n}\n\n/**\n * Set this to true to Run Proguard on Release builds to minify the Java bytecode.\n */\ndef enableProguardInReleaseBuilds = false\n\n/**\n * The preferred build flavor of JavaScriptCore (JSC)\n *\n * For example, to use the international variant, you can use:\n * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`\n *\n * The international variant includes ICU i18n library and necessary data\n * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that\n * give correct results when using with locales other than en-US. Note that\n * this variant is about 6MiB larger per architecture than default.\n */\ndef jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'\n\nandroid {\n    ndkVersion rootProject.ext.ndkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n    compileSdk rootProject.ext.compileSdkVersion\n\n    namespace \"com.cometchat.ai.sampleapp\"\n    defaultConfig {\n        applicationId \"com.cometchat.ai.sampleapp\"\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"5.3.4\"\n    }\n    signingConfigs {\n        debug {\n            storeFile file('debug.keystore')\n            storePassword 'android'\n            keyAlias 'androiddebugkey'\n            keyPassword 'android'\n        }\n    }\n    buildTypes {\n        debug {\n            signingConfig signingConfigs.debug\n        }\n        release {\n            // Caution! In production, you need to generate your own keystore file.\n            // see https://reactnative.dev/docs/signed-apk-android.\n            signingConfig signingConfigs.debug\n            minifyEnabled enableProguardInReleaseBuilds\n            proguardFiles getDefaultProguardFile(\"proguard-android.txt\"), \"proguard-rules.pro\"\n        }\n    }\n}\n\ndependencies {\n    // The version of react-native is set by the React Native Gradle Plugin\n    implementation(\"com.facebook.react:react-android\")\n\n    if (hermesEnabled.toBoolean()) {\n        implementation(\"com.facebook.react:hermes-android\")\n    } else {\n        implementation jscFlavor\n    }\n\n    implementation 'com.facebook.fresco:animated-gif:3.6.0' //gif android\n}\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package='com.cometchat.ai.sampleapp'> \n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />\n    <uses-permission android:name=\"android.permission.BIND_TELECOM_CONNECTION_SERVICE\"/>\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.CALL_PHONE\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n\n    <application\n      android:name=\".MainApplication\"\n      android:label=\"@string/app_name\"\n      android:icon=\"@mipmap/ic_launcher\"\n      android:roundIcon=\"@mipmap/ic_launcher_round\"\n      android:allowBackup=\"false\"\n      android:theme=\"@style/AppTheme\"\n      android:usesCleartextTraffic=\"${usesCleartextTraffic}\"\n      android:supportsRtl=\"true\">\n      <activity\n        android:name=\".MainActivity\"\n        android:label=\"@string/app_name\"\n        android:configChanges=\"keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|fontScale\"\n        android:launchMode=\"singleTask\"\n        android:windowSoftInputMode=\"adjustResize\"\n        android:exported=\"true\">\n        <intent-filter>\n            <action android:name=\"android.intent.action.MAIN\" />\n            <category android:name=\"android.intent.category.LAUNCHER\" />\n        </intent-filter>\n      </activity>\n    </application>\n</manifest>\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/java/com/cometchat/ai/sampleapp/MainActivity.kt",
    "content": "package com.cometchat.ai.sampleapp\nimport android.os.Bundle\n\nimport com.facebook.react.ReactActivity\nimport com.facebook.react.ReactActivityDelegate\nimport com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled\nimport com.facebook.react.defaults.DefaultReactActivityDelegate\n\nclass MainActivity : ReactActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n    // REQUIRED for react-native-screens\n    super.onCreate(null)\n  }\n\n\n  /**\n   * Returns the name of the main component registered from JavaScript. This is used to schedule\n   * rendering of the component.\n   */\n  override fun getMainComponentName(): String = \"ai_sample_app\"\n\n  /**\n   * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]\n   * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]\n   */\n  override fun createReactActivityDelegate(): ReactActivityDelegate =\n      DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)\n}\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/java/com/cometchat/ai/sampleapp/MainApplication.kt",
    "content": "package com.cometchat.ai.sampleapp\n\nimport android.app.Application\nimport com.facebook.react.PackageList\nimport com.facebook.react.ReactApplication\nimport com.facebook.react.ReactHost\nimport com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative\nimport com.facebook.react.ReactNativeHost\nimport com.facebook.react.ReactPackage\nimport com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost\nimport com.facebook.react.defaults.DefaultReactNativeHost\n\nclass MainApplication : Application(), ReactApplication {\n\n  override val reactNativeHost: ReactNativeHost =\n      object : DefaultReactNativeHost(this) {\n        override fun getPackages(): List<ReactPackage> =\n            PackageList(this).packages.apply {\n              // Packages that cannot be autolinked yet can be added manually here, for example:\n              // add(MyReactNativePackage())\n            }\n\n        override fun getJSMainModuleName(): String = \"index\"\n\n        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG\n\n        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED\n        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED\n      }\n\n  override val reactHost: ReactHost\n    get() = getDefaultReactHost(applicationContext, reactNativeHost)\n\n  override fun onCreate() {\n    super.onCreate()\n    loadReactNative(this)\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/res/drawable/rn_edit_text_material.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<inset xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:insetLeft=\"@dimen/abc_edit_text_inset_horizontal_material\"\n       android:insetRight=\"@dimen/abc_edit_text_inset_horizontal_material\"\n       android:insetTop=\"@dimen/abc_edit_text_inset_top_material\"\n       android:insetBottom=\"@dimen/abc_edit_text_inset_bottom_material\"\n       >\n\n    <selector>\n        <!--\n          This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).\n          The item below with state_pressed=\"false\" and state_focused=\"false\" causes a NullPointerException.\n          NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'\n\n          <item android:state_pressed=\"false\" android:state_focused=\"false\" android:drawable=\"@drawable/abc_textfield_default_mtrl_alpha\"/>\n\n          For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.\n        -->\n        <item android:state_enabled=\"false\" android:drawable=\"@drawable/abc_textfield_default_mtrl_alpha\"/>\n        <item android:drawable=\"@drawable/abc_textfield_activated_mtrl_alpha\"/>\n    </selector>\n\n</inset>\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/res/values/colors.xml",
    "content": "<resources>\n    <!-- Other color definitions -->\n    <color name=\"notification_color\">#FFFFFF</color> <!-- Replace with your desired color -->\n</resources>\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AI Sample App</string>\n</resources>\n"
  },
  {
    "path": "examples/SampleAppAI/android/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"android:editTextBackground\">@drawable/rn_edit_text_material</item>\n        <item name=\"android:windowOptOutEdgeToEdgeEnforcement\">true</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "examples/SampleAppAI/android/build.gradle",
    "content": "buildscript {\n    ext {\n        buildToolsVersion = \"36.0.0\"\n        minSdkVersion = 24\n        compileSdkVersion = 36\n        targetSdkVersion = 36\n        ndkVersion = \"27.1.12297006\"\n        kotlinVersion = \"2.1.20\"\n        version = \"V5.3.4\"\n    }\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath(\"com.android.tools.build:gradle:8.1.4\")\n        classpath(\"com.facebook.react:react-native-gradle-plugin\")\n        classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion\")\n    }\n}\n\napply plugin: \"com.facebook.react.rootproject\"\n"
  },
  {
    "path": "examples/SampleAppAI/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/SampleAppAI/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m\norg.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n\n# Use this property to specify which architecture you want to build.\n# You can also override it from the CLI using\n# ./gradlew <task> -PreactNativeArchitectures=x86_64\nreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64\n\n# Use this property to enable support to the new architecture.\n# This will allow you to use TurboModules and the Fabric render in\n# your application. You should enable this flag either if you want\n# to write custom TurboModules/Fabric components OR use libraries that\n# are providing them.\nnewArchEnabled=true\n\n# Use this property to enable or disable the Hermes JS engine.\n# If set to false, you will be using JSC instead.\nhermesEnabled=true\n\n# Use this property to enable edge-to-edge display support.\n# This allows your app to draw behind system bars for an immersive UI.\n# Note: Only works with ReactActivity and should not be used with custom Activity.\nedgeToEdgeEnabled=false\n"
  },
  {
    "path": "examples/SampleAppAI/android/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/SampleAppAI/android/gradlew.bat",
    "content": "@REM Copyright (c) Meta Platforms, Inc. and affiliates.\n@REM\n@REM This source code is licensed under the MIT license found in the\n@REM LICENSE file in the root directory of this source tree.\n\n@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "examples/SampleAppAI/android/settings.gradle",
    "content": "pluginManagement { includeBuild(\"../node_modules/@react-native/gradle-plugin\") }\nplugins { id(\"com.facebook.react.settings\") }\nextensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }\nrootProject.name = 'SampleApp'\ninclude ':app'\nincludeBuild('../node_modules/@react-native/gradle-plugin')\n"
  },
  {
    "path": "examples/SampleAppAI/app.json",
    "content": "{\n  \"name\": \"ai_sample_app\",\n  \"displayName\": \"AI Sample App\"\n}\n"
  },
  {
    "path": "examples/SampleAppAI/babel.config.js",
    "content": "module.exports = {\n  presets: ['module:@react-native/babel-preset'],\n};\n"
  },
  {
    "path": "examples/SampleAppAI/gesture-handler.js",
    "content": ""
  },
  {
    "path": "examples/SampleAppAI/gesture-handler.native.js",
    "content": "// Only import react-native-gesture-handler on native platforms\nimport 'react-native-gesture-handler';"
  },
  {
    "path": "examples/SampleAppAI/index.js",
    "content": "import {AppRegistry} from 'react-native';\nimport App from './App';\nimport {name as appName} from './app.json';\nimport AppErrorBoundary from './AppErrorBoundary';\nimport {ActiveChatProvider} from './src/utils/ActiveChatContext';\n\nif (global?.ErrorUtils) {\n  const defaultHandler = global.ErrorUtils.getGlobalHandler();\n\n  function globalErrorHandler(error, isFatal) {\n    console.log(\n      '[GlobalErrorHandler]:',\n      isFatal ? 'Fatal:' : 'Non-Fatal:',\n      error,\n    );\n    defaultHandler?.(error, isFatal);\n  }\n\n  global.ErrorUtils.setGlobalHandler(globalErrorHandler);\n}\n\nif (typeof process === 'object' && process.on) {\n  process.on('unhandledRejection', (reason, promise) => {\n    console.log('[Unhandled Promise Rejection]:', reason);\n  });\n}\n\nconst Root = () => (\n  <AppErrorBoundary>\n    <ActiveChatProvider>\n      <App />\n    </ActiveChatProvider>\n  </AppErrorBoundary>\n);\n\nAppRegistry.registerComponent(appName, () => Root);\n"
  },
  {
    "path": "examples/SampleAppAI/ios/.xcode.env",
    "content": "# This `.xcode.env` file is versioned and is used to source the environment\n# used when running script phases inside Xcode.\n# To customize your local environment, you can create an `.xcode.env.local`\n# file that is not versioned.\n\n# NODE_BINARY variable contains the PATH to the node executable.\n#\n# Customize the NODE_BINARY variable here.\n# For example, to use nvm with brew, add the following line\n# . \"$(brew --prefix nvm)/nvm.sh\" --no-use\nexport NODE_BINARY=$(command -v node)\n"
  },
  {
    "path": "examples/SampleAppAI/ios/Podfile",
    "content": "# Resolve react_native_pods.rb with node to allow for hoisting\nrequire Pod::Executable.execute_command('node', ['-p',\n  'require.resolve(\n    \"react-native/scripts/react_native_pods.rb\",\n    {paths: [process.argv[1]]},\n  )', __dir__]).strip\n\nplatform :ios, min_ios_version_supported\nprepare_react_native_project!\n\n# Specify the Xcode project\nproject 'SampleApp.xcodeproj'\n\nuse_frameworks! :linkage => :static\n\ntarget 'SampleApp' do\n  config = use_native_modules!\n\n  pod 'SPTPersistentCache', :modular_headers => true\n  pod 'DVAssetLoaderDelegate', :modular_headers => true\n\n  use_react_native!(\n    :path => config[:reactNativePath],\n    # An absolute path to your application root.\n    :app_path => \"#{Pod::Config.instance.installation_root}/..\"\n  )\n\n  post_install do |installer|\n    # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202\n    react_native_post_install(\n      installer,\n      config[:reactNativePath],\n      :mac_catalyst_enabled => false,\n      # :ccache_enabled => true\n    )\n\n    # Fix fmt consteval compilation error with Xcode 26+\n    # https://github.com/expo/expo/issues/44229\n    fmt_base = File.join(installer.sandbox.pod_dir('fmt'), 'include', 'fmt', 'base.h')\n    if File.exist?(fmt_base)\n      content = File.read(fmt_base)\n      patched = content.gsub(/^#\\s*define FMT_USE_CONSTEVAL 1$/, '# define FMT_USE_CONSTEVAL 0')\n      if patched != content\n        File.chmod(0644, fmt_base)\n        File.write(fmt_base, patched)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp/AppDelegate.swift",
    "content": "import UIKit\nimport React\nimport React_RCTAppDelegate\nimport ReactAppDependencyProvider\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n  var window: UIWindow?\n\n  var reactNativeDelegate: ReactNativeDelegate?\n  var reactNativeFactory: RCTReactNativeFactory?\n\n  func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n  ) -> Bool {\n    let delegate = ReactNativeDelegate()\n    let factory = RCTReactNativeFactory(delegate: delegate)\n    delegate.dependencyProvider = RCTAppDependencyProvider()\n\n    reactNativeDelegate = delegate\n    reactNativeFactory = factory\n\n    window = UIWindow(frame: UIScreen.main.bounds)\n\n    factory.startReactNative(\n      withModuleName: \"ai_sample_app\",\n      in: window,\n      launchOptions: launchOptions\n    )\n\n    return true\n  }\n}\n\nclass ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {\n  override func sourceURL(for bridge: RCTBridge) -> URL? {\n    self.bundleURL()\n  }\n\n  override func bundleURL() -> URL? {\n#if DEBUG\n    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: \"index\")\n#else\n    Bundle.main.url(forResource: \"main\", withExtension: \"jsbundle\")\n#endif\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CometChat_React_Native_40x40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_60x60.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_58x58.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_87x87.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_80x80.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_120x120 1.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_120x120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_180x180.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"CometChat React native.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp/Images.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>AI Sample App</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(MARKETING_VERSION)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t<key>NSAllowsArbitraryLoads</key>\n\t<false/>\n\t<key>NSAllowsLocalNetworking</key>\n\t<true/>\n\t</dict>\n\t<key>NSCameraUsageDescription</key>\n\t<string>This is for Camera permission</string>\n\t<key>NSLocationWhenInUseUsageDescription</key>\n\t<string></string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>This is for Mic permission</string>\n\t<key>RCTNewArchEnabled</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>arm64</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"23504\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina6_1\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"23506\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"414\" height=\"896\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView clipsSubviews=\"YES\"\n                                       userInteractionEnabled=\"NO\"\n                                       contentMode=\"scaleAspectFit\"\n                                       horizontalHuggingPriority=\"251\"\n                                       verticalHuggingPriority=\"251\"\n                                       image=\"CometChat React native.png\"\n                                       translatesAutoresizingMaskIntoConstraints=\"NO\"\n                                       id=\"CtN-Dg-ods\">\n                                <!-- The rect is now just an initial placeholder -->\n                                <rect key=\"frame\" x=\"0\" y=\"0\" width=\"414\" height=\"128\"/>\n                            </imageView>\n                        </subviews>\n                        <!-- Auto Layout constraints for centering and sizing the image view -->\n                        <constraints>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"centerXConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"centerYConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"width\" constant=\"414\" id=\"widthConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"height\" constant=\"128\" id=\"heightConstraint\"/>\n                        </constraints>\n                        <viewLayoutGuide key=\"safeArea\" id=\"Bcu-3y-fUS\"/>\n                        <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"52.173913043478265\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"CometChat React native.png\" width=\"1024\" height=\"1024\"/>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp/PrivacyInfo.xcprivacy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrivacyAccessedAPITypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>C617.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryUserDefaults</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>CA92.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategorySystemBootTime</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>35F9.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSPrivacyCollectedDataTypes</key>\n\t<array/>\n\t<key>NSPrivacyTracking</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };\n\t\t3FB2022EB49561EBA8A32491 /* Pods_SampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 578B0B2D70ABC3C90153065B /* Pods_SampleApp.framework */; };\n\t\t761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };\n\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };\n\t\tBDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-SampleApp.debug.xcconfig\"; path = \"Target Support Files/Pods-SampleApp/Pods-SampleApp.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = SampleApp/Images.xcassets; sourceTree = \"<group>\"; };\n\t\t13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleApp/Info.plist; sourceTree = \"<group>\"; };\n\t\t13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = \"<group>\"; };\n\t\t24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-SampleApp.release.xcconfig\"; path = \"Target Support Files/Pods-SampleApp/Pods-SampleApp.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t578B0B2D70ABC3C90153065B /* Pods_SampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = SampleApp/AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SampleApp/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t3FB2022EB49561EBA8A32491 /* Pods_SampleApp.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t13B07FAE1A68108700A75B9A /* SampleApp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FB51A68108700A75B9A /* Images.xcassets */,\n\t\t\t\t761780EC2CA45674006654EE /* AppDelegate.swift */,\n\t\t\t\t13B07FB61A68108700A75B9A /* Info.plist */,\n\t\t\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,\n\t\t\t\t13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,\n\t\t\t);\n\t\t\tname = SampleApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */,\n\t\t\t\t578B0B2D70ABC3C90153065B /* Pods_SampleApp.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t832341AE1AAA6A7D00B99B32 /* Libraries */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Libraries;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t83CBB9F61A601CBA00E9B192 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FAE1A68108700A75B9A /* SampleApp */,\n\t\t\t\t832341AE1AAA6A7D00B99B32 /* Libraries */,\n\t\t\t\t83CBBA001A601CBA00E9B192 /* Products */,\n\t\t\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */,\n\t\t\t\tBBD78D7AC51CEA395F1C20DB /* Pods */,\n\t\t\t);\n\t\t\tindentWidth = 2;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 2;\n\t\t\tusesTabs = 0;\n\t\t};\n\t\t83CBBA001A601CBA00E9B192 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07F961A680F5B00A75B9A /* SampleApp.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBBD78D7AC51CEA395F1C20DB /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */,\n\t\t\t\t24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t13B07F861A680F5B00A75B9A /* SampleApp */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"SampleApp\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t13B07F871A680F5B00A75B9A /* Sources */,\n\t\t\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */,\n\t\t\t\t13B07F8E1A680F5B00A75B9A /* Resources */,\n\t\t\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,\n\t\t\t\tB26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */,\n\t\t\t\t313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = SampleApp;\n\t\t\tproductName = SampleApp;\n\t\t\tproductReference = 13B07F961A680F5B00A75B9A /* SampleApp.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t83CBB9F71A601CBA00E9B192 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1210;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t13B07F861A680F5B00A75B9A = {\n\t\t\t\t\t\tLastSwiftMigration = 1120;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"SampleApp\" */;\n\t\t\tcompatibilityVersion = \"Xcode 12.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 83CBB9F61A601CBA00E9B192;\n\t\t\tproductRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t13B07F861A680F5B00A75B9A /* SampleApp */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t13B07F8E1A680F5B00A75B9A /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,\n\t\t\t\tBDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"$(SRCROOT)/.xcode.env.local\",\n\t\t\t\t\"$(SRCROOT)/.xcode.env\",\n\t\t\t);\n\t\t\tname = \"Bundle React Native code and images\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"set -e\\n\\nWITH_ENVIRONMENT=\\\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\\\"\\nREACT_NATIVE_XCODE=\\\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\\\"\\n\\n/bin/sh -c \\\"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\\\"\\n\";\n\t\t};\n\t\t313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-SampleApp-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"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\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tB26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t13B07F871A680F5B00A75B9A /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t13B07F941A680F5B00A75B9A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = T9F7QDSC2S;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = SampleApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = V5.3.4;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.cometchat.ai.sampleapp;\n\t\t\t\tPRODUCT_NAME = SampleApp;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t13B07F951A680F5B00A75B9A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = T9F7QDSC2S;\n\t\t\t\tINFOPLIST_FILE = SampleApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = V5.3.4;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.cometchat.ai.sampleapp;\n\t\t\t\tPRODUCT_NAME = SampleApp;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t83CBBA201A601CBA00E9B192 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphonesimulator*]\" = \"\";\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\",\n\t\t\t\t);\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(SDKROOT)/usr/lib/swift\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tOTHER_CPLUSPLUSFLAGS = (\n\t\t\t\t\t\"$(OTHER_CFLAGS)\",\n\t\t\t\t\t\"-DFOLLY_NO_CONFIG\",\n\t\t\t\t\t\"-DFOLLY_MOBILE=1\",\n\t\t\t\t\t\"-DFOLLY_USE_LIBCPP=1\",\n\t\t\t\t\t\"-DFOLLY_CFG_NO_COROUTINES=1\",\n\t\t\t\t\t\"-DFOLLY_HAVE_CLOCK_GETTIME=1\",\n\t\t\t\t);\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"$(inherited) DEBUG\";\n\t\t\t\tUSE_HERMES = true;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t83CBBA211A601CBA00E9B192 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphonesimulator*]\" = \"\";\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\",\n\t\t\t\t);\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(SDKROOT)/usr/lib/swift\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tOTHER_CPLUSPLUSFLAGS = (\n\t\t\t\t\t\"$(OTHER_CFLAGS)\",\n\t\t\t\t\t\"-DFOLLY_NO_CONFIG\",\n\t\t\t\t\t\"-DFOLLY_MOBILE=1\",\n\t\t\t\t\t\"-DFOLLY_USE_LIBCPP=1\",\n\t\t\t\t\t\"-DFOLLY_CFG_NO_COROUTINES=1\",\n\t\t\t\t\t\"-DFOLLY_HAVE_CLOCK_GETTIME=1\",\n\t\t\t\t);\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tUSE_HERMES = true;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"SampleApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t13B07F941A680F5B00A75B9A /* Debug */,\n\t\t\t\t13B07F951A680F5B00A75B9A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"SampleApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t83CBBA201A601CBA00E9B192 /* Debug */,\n\t\t\t\t83CBBA211A601CBA00E9B192 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;\n}\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1210\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n               BuildableName = \"SampleApp.app\"\n               BlueprintName = \"SampleApp\"\n               ReferencedContainer = \"container:SampleApp.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"00E356ED1AD99517003FC87E\"\n               BuildableName = \"SampleAppTests.xctest\"\n               BlueprintName = \"SampleAppTests\"\n               ReferencedContainer = \"container:SampleApp.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"SampleApp.app\"\n            BlueprintName = \"SampleApp\"\n            ReferencedContainer = \"container:SampleApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"SampleApp.app\"\n            BlueprintName = \"SampleApp\"\n            ReferencedContainer = \"container:SampleApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:SampleApp.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "examples/SampleAppAI/ios/SampleApp.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppAI/jest.config.js",
    "content": "module.exports = {\n  preset: 'react-native',\n};\n"
  },
  {
    "path": "examples/SampleAppAI/metro.config.js",
    "content": "const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('@react-native/metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);\n"
  },
  {
    "path": "examples/SampleAppAI/package.json",
    "content": "{\n  \"name\": \"ai_sample_app\",\n  \"version\": \"5.3.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"android\": \"react-native run-android\",\n    \"ios\": \"react-native run-ios\",\n    \"lint\": \"eslint .\",\n    \"start\": \"react-native start\",\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"@cometchat/calls-sdk-react-native\": \"^4.4.0\",\n    \"@cometchat/chat-sdk-react-native\": \"^4.0.21\",\n    \"@cometchat/chat-uikit-react-native\": \"^5.3.4\",\n    \"@react-native-async-storage/async-storage\": \"^2.2.0\",\n    \"@react-native-clipboard/clipboard\": \"^1.16.3\",\n    \"@react-native-community/datetimepicker\": \"^8.4.5\",\n    \"@react-native-community/netinfo\": \"^11.4.1\",\n    \"@react-native/new-app-screen\": \"0.81.4\",\n    \"@react-navigation/bottom-tabs\": \"^7.4.7\",\n    \"@react-navigation/native\": \"^7.1.17\",\n    \"@react-navigation/native-stack\": \"^7.14.12\",\n    \"@react-navigation/stack\": \"^7.4.8\",\n    \"dayjs\": \"^1.11.18\",\n    \"react\": \"19.1.0\",\n    \"react-native\": \"0.81.4\",\n    \"react-native-background-timer\": \"^2.4.1\",\n    \"react-native-callstats\": \"^3.73.22\",\n    \"react-native-gesture-handler\": \"^2.28.0\",\n    \"react-native-localize\": \"^3.5.4\",\n    \"react-native-safe-area-context\": \"^5.5.2\",\n    \"react-native-screens\": \"^4.16.0\",\n    \"react-native-svg\": \"^15.13.0\",\n    \"react-native-video\": \"^6.16.1\",\n    \"react-native-webrtc\": \"^124.0.6\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.25.2\",\n    \"@babel/preset-env\": \"^7.25.3\",\n    \"@babel/runtime\": \"^7.25.0\",\n    \"@react-native-community/cli\": \"20.0.0\",\n    \"@react-native-community/cli-platform-android\": \"20.0.0\",\n    \"@react-native-community/cli-platform-ios\": \"20.0.0\",\n    \"@react-native/babel-preset\": \"0.81.4\",\n    \"@react-native/eslint-config\": \"0.81.4\",\n    \"@react-native/metro-config\": \"0.81.4\",\n    \"@react-native/typescript-config\": \"0.81.4\",\n    \"@types/jest\": \"^29.5.13\",\n    \"@types/react\": \"^19.1.0\",\n    \"@types/react-test-renderer\": \"^19.1.0\",\n    \"eslint\": \"^8.19.0\",\n    \"jest\": \"^29.6.3\",\n    \"prettier\": \"2.8.8\",\n    \"react-test-renderer\": \"19.1.0\",\n    \"typescript\": \"^5.8.3\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/AccountCircle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.743 17.438q1.485-1.047 3-1.609a9.3 9.3 0 0 1 3.256-.562q1.742 0 3.26.562 1.52.563 3.02 1.608 1.046-1.262 1.508-2.601a8.6 8.6 0 0 0 .462-2.835q0-3.497-2.377-5.874T12 3.75 6.126 6.127Q3.75 8.505 3.75 12.001q0 1.496.471 2.835.471 1.34 1.523 2.601m6.255-4.638q-1.44 0-2.428-.989-.987-.989-.987-2.429t.988-2.428 2.43-.987 2.428.989q.987.988.987 2.428t-.989 2.428-2.429.988m.005 9.033a9.5 9.5 0 0 1-3.822-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.124 9.5 9.5 0 0 1-.777-3.824q0-2.026.777-3.817a10 10 0 0 1 2.113-3.125 10 10 0 0 1 3.124-2.11 9.5 9.5 0 0 1 3.824-.776q2.025 0 3.818.777a10 10 0 0 1 3.124 2.113 10 10 0 0 1 2.11 3.125 9.5 9.5 0 0 1 .776 3.814q0 2.027-.777 3.822a10 10 0 0 1-2.112 3.129 10 10 0 0 1-3.126 2.11 9.5 9.5 0 0 1-3.814.776m-.004-1.583q1.342 0 2.586-.387 1.244-.388 2.447-1.296a10.3 10.3 0 0 0-2.455-1.277A7.7 7.7 0 0 0 12 16.85a7.9 7.9 0 0 0-2.584.433q-1.26.434-2.451 1.284 1.204.907 2.447 1.296A8.6 8.6 0 0 0 12 20.25m0-9.033q.795 0 1.315-.519.518-.519.518-1.315t-.518-1.314-1.315-.519-1.314.519-.519 1.314.519 1.315 1.314.519'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/AddComment.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.212 10.783v2.338q0 .333.228.564.228.232.562.232t.564-.232a.77.77 0 0 0 .229-.564v-2.338h2.338q.333 0 .564-.232a.77.77 0 0 0 .232-.562.76.76 0 0 0-.232-.56.77.77 0 0 0-.564-.229h-2.338V6.854a.76.76 0 0 0-.232-.556.77.77 0 0 0-.567-.231.75.75 0 0 0-.56.231.77.77 0 0 0-.224.556V9.2H8.866a.75.75 0 0 0-.56.234.78.78 0 0 0-.227.56q0 .327.227.558a.76.76 0 0 0 .56.231zm-5.15 7.067L3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/ArrowBack.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m7.193 12.787 5.366 5.367a.8.8 0 0 1 .24.563.75.75 0 0 1-.236.554.79.79 0 0 1-.562.243.74.74 0 0 1-.554-.243L4.73 12.554a.75.75 0 0 1-.185-.256.8.8 0 0 1-.057-.298.8.8 0 0 1 .057-.298.8.8 0 0 1 .185-.265l6.717-6.716a.76.76 0 0 1 .552-.234q.318 0 .564.234a.8.8 0 0 1 .238.564q0 .319-.238.557l-5.37 5.362h11.854a.76.76 0 0 1 .558.23.77.77 0 0 1 .229.566.76.76 0 0 1-.229.562.77.77 0 0 1-.558.225z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Block.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.002 21.833a9.6 9.6 0 0 1-3.834-.774 10 10 0 0 1-3.123-2.105 9.9 9.9 0 0 1-2.104-3.12 9.6 9.6 0 0 1-.773-3.833q0-2.043.775-3.835a10 10 0 0 1 2.104-3.122 9.9 9.9 0 0 1 3.12-2.104A9.6 9.6 0 0 1 12 2.166q2.043 0 3.835.775a10 10 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.105 3.12 9.6 9.6 0 0 1 .772 3.833q0 2.043-.774 3.835a10 10 0 0 1-2.104 3.122 9.9 9.9 0 0 1-3.12 2.105 9.6 9.6 0 0 1-3.834.772m0-1.583q1.469 0 2.816-.488a8 8 0 0 0 2.45-1.412L5.655 6.733a8.6 8.6 0 0 0-1.408 2.46A8 8 0 0 0 3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399m6.33-2.983a8.8 8.8 0 0 0 1.407-2.449A7.9 7.9 0 0 0 20.25 12q0-3.452-2.399-5.851t-5.85-2.399q-1.464 0-2.807.496a8.4 8.4 0 0 0-2.46 1.42z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/CallFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Chat.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.062 17.85 3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275zm1.5-2.334h6.15a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.229h-6.15a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.557q0 .335.232.564a.77.77 0 0 0 .564.23m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.23H6.895a.77.77 0 0 0-.564.234.76.76 0 0 0-.232.557q0 .335.232.564.231.23.564.229m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.563.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.229H6.895a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.558q0 .334.232.564.231.229.564.229'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Chatfill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.039 17.5-2.002 2.002q-.427.426-.982.192-.555-.235-.555-.84V4.308q0-.758.525-1.283T4.308 2.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zM7 13.75h6q.319 0 .534-.216A.73.73 0 0 0 13.75 13a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 13.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 10a.73.73 0 0 0-.216-.534A.73.73 0 0 0 17 9.25H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 10.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 7a.73.73 0 0 0-.216-.535A.73.73 0 0 0 17 6.25H7a.73.73 0 0 0-.534.216A.73.73 0 0 0 6.25 7q0 .32.216.535A.73.73 0 0 0 7 7.75'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/CheckFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m9.551 15.516 8.639-8.639a.73.73 0 0 1 .522-.228q.299-.005.532.228a.74.74 0 0 1 .232.535q0 .302-.232.534l-9.06 9.075a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.27l-4.175-4.176a.71.71 0 0 1-.22-.53.75.75 0 0 1 .236-.539.74.74 0 0 1 .534-.233q.303 0 .535.233z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Close.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 13.117 6.895 18.22a.763.763 0 0 1-1.117-.004.766.766 0 0 1 .004-1.1l5.1-5.117-5.1-5.117a.74.74 0 0 1-.229-.544q0-.315.23-.556a.74.74 0 0 1 .55-.244.78.78 0 0 1 .562.232l5.104 5.112 5.112-5.112a.75.75 0 0 1 .555-.232.78.78 0 0 1 .562.244.76.76 0 0 1 .223.556.77.77 0 0 1-.235.544L13.116 12l5.1 5.117a.74.74 0 0 1 .229.543q0 .314-.23.557a.74.74 0 0 1-.55.243.74.74 0 0 1-.553-.24z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Delete.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.734 20.833q-.655 0-1.12-.464a1.53 1.53 0 0 1-.464-1.119V5.55h-.204a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.562q0-.33.232-.56a.77.77 0 0 1 .564-.23h3.89v-.012a.76.76 0 0 1 .23-.558.77.77 0 0 1 .564-.23h4.766q.324 0 .556.232.231.232.232.556v.013h3.9q.32 0 .552.232a.76.76 0 0 1 .231.558.76.76 0 0 1-.232.564.76.76 0 0 1-.555.229h-.213v13.7q0 .655-.464 1.119-.465.464-1.12.464zM17.284 5.55H6.734v13.7h10.55zm-7.29 11.62a.77.77 0 0 0 .559-.226.76.76 0 0 0 .23-.56V8.4a.77.77 0 0 0-.232-.565.76.76 0 0 0-.558-.23.76.76 0 0 0-.563.23.77.77 0 0 0-.23.565v7.983q0 .335.234.56a.78.78 0 0 0 .56.228m4.034 0a.77.77 0 0 0 .558-.226.76.76 0 0 0 .231-.56V8.4a.77.77 0 0 0-.233-.565.76.76 0 0 0-.557-.23.76.76 0 0 0-.564.23.77.77 0 0 0-.23.565v7.983q0 .335.235.56a.78.78 0 0 0 .56.228'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Group.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M1.21 17.381q0-.85.431-1.54.43-.69 1.2-1.04 1.72-.776 3.2-1.137 1.478-.36 3.024-.36t3.013.36q1.468.361 3.186 1.136.771.35 1.211 1.04t.44 1.541v.819q0 .653-.465 1.118t-1.118.465H2.794q-.66 0-1.122-.465a1.53 1.53 0 0 1-.461-1.118zm20 2.402h-3.1q.159-.383.274-.77.114-.386.114-.813v-.817q0-1.41-.654-2.384-.654-.975-1.979-1.628 1.537.191 2.89.535 1.351.344 2.29.831.82.463 1.285 1.121.464.66.464 1.473v.865q0 .66-.465 1.123-.465.464-1.118.464M9.066 12q-1.62 0-2.677-1.056T5.332 8.267 6.388 5.59t2.677-1.057q1.62 0 2.677 1.057t1.056 2.677-1.056 2.677Q10.685 12 9.065 12m9.033-3.743q0 1.61-1.056 2.67t-2.677 1.06q-.255 0-.596-.042a3 3 0 0 1-.604-.13 4.4 4.4 0 0 0 .944-1.565 5.9 5.9 0 0 0 .323-1.986q0-1.08-.323-1.962a5.2 5.2 0 0 0-.944-1.602q.271-.084.596-.126a5 5 0 0 1 .604-.04q1.62 0 2.677 1.059t1.056 2.664M2.794 18.2h12.538v-.815q0-.364-.209-.69a1.3 1.3 0 0 0-.512-.474q-1.655-.755-2.9-1.044-1.246-.29-2.642-.29-1.4 0-2.662.29-1.262.289-2.909 1.044-.312.15-.508.476-.195.327-.196.686zm6.27-7.783q.922 0 1.536-.613.615-.614.615-1.536t-.613-1.537-1.535-.614-1.537.613-.615 1.535q0 .922.613 1.537t1.535.615'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/GroupAdd.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.596 11.642q.638-.694.944-1.594t.306-1.856-.306-1.856a4.4 4.4 0 0 0-.944-1.594q1.317.151 2.187 1.138t.87 2.312q0 1.327-.87 2.313a3.33 3.33 0 0 1-2.187 1.137m4.863 7.666q.188-.345.288-.733.099-.389.099-.787v-.827q0-.817-.333-1.556a3.6 3.6 0 0 0-.944-1.267q1.15.384 2.117 1.033.967.65.967 1.79v.827q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm2.194-8.558h-1.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.25V8q0-.319.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v1.25h1.25q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .319-.215.534a.73.73 0 0 1-.535.216h-1.25V12q0 .318-.215.534a.73.73 0 0 1-.535.216.72.72 0 0 1-.534-.216.73.73 0 0 1-.216-.534zm-11.307.942q-1.444 0-2.472-1.028T4.846 8.192 5.874 5.72t2.472-1.028 2.472 1.028 1.028 2.472-1.028 2.472-2.472 1.028m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.991-1.09 12.95 12.95 0 0 1 6.087 0q1.509.364 2.991 1.09.667.337 1.066.963.4.625.4 1.36v.704q0 .633-.444 1.076a1.47 1.47 0 0 1-1.075.444H2.364q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/GroupFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M1.797 17.085q0-.774.399-1.38a2.7 2.7 0 0 1 1.066-.944q1.426-.697 2.866-1.075t3.169-.378 3.168.377 2.867 1.076q.666.338 1.066.944.399.606.399 1.38v.703q0 .605-.444 1.062a1.44 1.44 0 0 1-1.076.458H3.316q-.633 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076zm16.613 2.223q.189-.345.288-.733t.099-.787v-.826q0-.985-.482-1.877a4.54 4.54 0 0 0-1.368-1.531q1.005.15 1.91.464.903.315 1.724.743.775.414 1.197.975t.422 1.226v.826q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm-9.113-7.616q-1.444 0-2.472-1.028T5.797 8.192q0-1.443 1.028-2.471t2.472-1.029 2.472 1.029 1.028 2.471-1.028 2.472q-1.029 1.028-2.472 1.028m8.634-3.5q0 1.444-1.028 2.472t-2.472 1.028a3 3 0 0 1-.43-.038 4 4 0 0 1-.431-.085q.59-.711.91-1.578a5.2 5.2 0 0 0-.007-3.593 5.8 5.8 0 0 0-.903-1.582q.215-.078.43-.1.216-.024.431-.024 1.444 0 2.472 1.029t1.028 2.471'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Info.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/InfoIcon.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Logout.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h5.952q.318 0 .534.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.952q.318 0 .534.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215zm12.31-7.75H9.845a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h7.771l-1.923-1.923a.7.7 0 0 1-.212-.507.74.74 0 0 1 .212-.531.72.72 0 0 1 .527-.241.72.72 0 0 1 .543.225l3.094 3.094q.27.271.27.633 0 .361-.27.633l-3.094 3.094a.71.71 0 0 1-.53.22.75.75 0 0 1-.54-.236.73.73 0 0 1-.21-.534.74.74 0 0 1 .226-.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/Person.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06q1.628 0 2.689 1.06 1.06 1.06 1.06 2.69t-1.06 2.69-2.69 1.06m-7.85 6.2v-.817q0-.9.451-1.568a2.9 2.9 0 0 1 1.185-1.018 18 18 0 0 1 3.14-1.116 12.8 12.8 0 0 1 3.073-.377q1.545 0 3.065.381a18.4 18.4 0 0 1 3.134 1.118q.75.348 1.201 1.012.45.664.45 1.568v.821q0 .648-.465 1.114-.465.465-1.118.465H5.732q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.533v-.814q0-.366-.208-.686a1.36 1.36 0 0 0-.513-.48q-1.512-.728-2.831-1.03A12 12 0 0 0 12 14.887q-1.403 0-2.732.303-1.328.302-2.825 1.03-.312.16-.512.481-.2.322-.2.686zm6.266-7.783q.925 0 1.546-.621t.621-1.546-.62-1.546-1.547-.62-1.545.62-.621 1.546.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/PersonAdd.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.13 10.817h-2.345a.75.75 0 0 1-.56-.234.78.78 0 0 1-.228-.561q0-.327.227-.558a.76.76 0 0 1 .56-.23h2.347V6.887q0-.325.227-.556a.76.76 0 0 1 .563-.232q.334 0 .564.232t.229.556v2.345h2.337q.333 0 .565.233a.76.76 0 0 1 .231.558q0 .334-.231.564a.77.77 0 0 1-.565.229h-2.337v2.337q0 .333-.232.565a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.565zM9.019 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06 2.69 1.06 1.06 2.69-1.06 2.69T9.018 12m-7.85 6.2v-.818q0-.85.427-1.539a2.67 2.67 0 0 1 1.21-1.04q1.746-.795 3.22-1.147a12.8 12.8 0 0 1 5.973 0q1.467.352 3.22 1.144.78.363 1.215 1.046a2.8 2.8 0 0 1 .435 1.537v.821q0 .648-.465 1.114-.465.465-1.118.465H2.75q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.534v-.813q0-.364-.196-.686a1.23 1.23 0 0 0-.525-.48q-1.63-.763-2.882-1.048a12 12 0 0 0-2.662-.286q-1.419 0-2.677.286t-2.88 1.048q-.337.158-.524.479a1.33 1.33 0 0 0-.188.683zm6.267-7.783q.925 0 1.546-.621.62-.621.62-1.546t-.62-1.546q-.622-.62-1.546-.62-.925 0-1.546.62-.62.621-.62 1.546t.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/PersonFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 11.692q-1.448 0-2.474-1.026A3.37 3.37 0 0 1 8.5 8.192q0-1.448 1.026-2.474A3.37 3.37 0 0 1 12 4.692q1.448 0 2.474 1.026A3.37 3.37 0 0 1 15.5 8.192q0 1.449-1.026 2.474A3.37 3.37 0 0 1 12 11.692m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.992-1.09 12.95 12.95 0 0 1 6.086 0q1.509.364 2.992 1.09.667.337 1.066.963t.399 1.36v.704q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444H6.019q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/PersonOff.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill={color ?? \"#none\"}\n    viewBox=\"0 0 24 24\"\n  >\n    <Path\n    fill={color ?? \"#000\"}\n      d=\"m18.777 20.6-2.108-2.092H6.622q-.552 0-.937-.385a1.27 1.27 0 0 1-.385-.935v-.404q0-.529.274-.982a2.1 2.1 0 0 1 .766-.74q1.171-.68 2.449-1.07 1.275-.39 2.61-.448h.158q.081 0 .159.01L3.385 5.223a.6.6 0 0 1-.195-.45.66.66 0 0 1 .21-.47.64.64 0 0 1 .464-.207q.255 0 .455.208l15.375 15.39a.65.65 0 0 1 .205.454.6.6 0 0 1-.203.452.62.62 0 0 1-.455.208.64.64 0 0 1-.464-.208M6.6 17.208h8.77l-2.328-2.352q-.27-.02-.52-.034a9.93 9.93 0 0 0-3.109.326 9.5 9.5 0 0 0-2.384 1.002.9.9 0 0 0-.315.278.6.6 0 0 0-.114.356zm11.296-2.04q.222.14.358.34.136.202.177.46l-1.187-1.187.33.18q.165.09.322.206m-4.18-4.009-.975-.95q.481-.22.77-.662.29-.44.289-.964 0-.741-.526-1.266a1.73 1.73 0 0 0-1.264-.525q-.522 0-.963.29-.442.288-.664.77l-.95-.975a2.85 2.85 0 0 1 1.112-1.027q.69-.358 1.455-.358 1.298 0 2.199.901.9.9.901 2.2 0 .764-.358 1.454-.357.69-1.027 1.112\"\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/UserEmptyIcon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Mask, Path, Rect } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 120 120'>\n    <Mask\n      id='mask0_5082_129886'\n      style={{ maskType: \"alpha\" }}\n      maskUnits='userSpaceOnUse'\n      x='-20'\n      y='-20'\n      width='160'\n      height='160'\n    >\n      <Rect x='-20' y='-20' width='160' height='160' fill='#D9D9D9' />\n    </Mask>\n    <G mask='url(#mask0_5082_129886)'>\n      <Path\n        d='M60 57.9479C53.5644 57.9479 48.0667 55.6679 43.5067 51.1079C38.9467 46.549 36.6667 41.0512 36.6667 34.6146C36.6667 28.179 38.9467 22.6825 43.5067 18.1235C48.0667 13.5644 53.5644 11.2844 60 11.2844C66.4356 11.2844 71.9333 13.5644 76.4933 18.1235C81.0533 22.6825 83.3333 28.179 83.3333 34.6146C83.3333 41.0512 81.0533 46.549 76.4933 51.1079C71.9333 55.6679 66.4356 57.9479 60 57.9479ZM19.3166 105.007C16.1811 105.007 13.4568 103.856 11.1437 101.555C8.8306 99.2539 7.67344 96.5196 7.67344 93.3513V91.0288C7.67344 86.7545 8.70044 82.9018 10.7544 79.4706C12.8084 76.0385 15.6466 73.4095 19.268 71.5826C24.3797 69.1016 29.6071 67.2153 34.95 65.9235C40.2929 64.6317 46.0113 63.9858 52.105 63.9858H67.8949C73.987 63.9858 79.7055 64.6317 85.0491 65.9235C90.3928 67.2153 95.6215 69.1016 100.735 71.5826C104.357 73.4095 107.195 76.0385 109.249 79.4706C111.303 82.9018 112.33 86.7545 112.33 91.0288V93.3513C112.33 96.5196 111.173 99.2539 108.86 101.555C106.547 103.856 103.823 105.007 100.687 105.007H19.3166Z'\n        fill={color}\n      />\n    </G>\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/assets/icons/VideoCam.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.779 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h12.537q.627 0 1.104.471.475.471.475 1.112v5.1L21.13 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.104.47zm0-1.583h12.533V5.733H3.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppAI/src/components/AIAgents.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUsers, useTheme, CometChatUIKit, CometChatAvatar} from '@cometchat/chat-uikit-react-native';\nimport React, {useLayoutEffect, useState, useRef, useCallback, useContext} from 'react';\nimport {StyleSheet, TouchableOpacity, View} from 'react-native';\nimport {useNavigation, CommonActions} from '@react-navigation/native';\nimport {RootStackParamList} from '../navigation/types';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {TooltipMenu} from '../utils/TooltipMenu';\nimport {AppConstants} from '../utils/AppConstants';\nimport Logout from '../assets/icons/Logout';\nimport Info from '../assets/icons/Info';\nimport {AuthContext} from '../navigation/AuthContext';\nimport {navigationRef} from '../navigation/NavigationService';\nimport AccountCircle from '../assets/icons/AccountCircle';\n\ntype AIAgentNavigationProp = StackNavigationProp<RootStackParamList, 'Messages'>;\n\nconst AppBarOptions: React.FC<{\n  onPress: () => void;\n  loggedInUser: CometChat.User;\n  avatarContainerRef: React.RefObject<View | null>;\n}> = ({ onPress, loggedInUser, avatarContainerRef }) => {\n  return (\n    <View ref={avatarContainerRef}>\n      <TouchableOpacity onPress={onPress}>\n        <CometChatAvatar\n          style={{\n            containerStyle: {\n              height: 40,\n              width: 40,\n              justifyContent: 'center',\n              alignItems: 'center',\n              overflow: 'hidden',\n            },\n            textStyle: {\n              fontSize: 22,\n              lineHeight: 28,\n              textAlign: 'center',\n            },\n          }}\n          image={\n            loggedInUser?.getAvatar()\n              ? {uri: loggedInUser?.getAvatar()}\n              : undefined\n          }\n          name={loggedInUser?.getName() ?? ''}\n        />\n      </TouchableOpacity>\n    </View>\n  );\n};\n\nconst AIAgents: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<AIAgentNavigationProp>();\n  const {setIsLoggedIn: setLogout} = useContext(AuthContext);\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const [isLoggingOut, setIsLoggingOut] = useState(false);\n  const tooltipPosition = useRef({pageX: 0, pageY: 0});\n  const avatarContainerRef = useRef<View>(null);\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n\n  // Configure header with back button\n  useLayoutEffect(() => {\n    navigation.setOptions({\n      headerShown: false,\n      title: 'AI Assistants',\n      headerStyle: {\n        backgroundColor: theme.color.background1,\n      },\n      headerTitleStyle: {\n        color: theme.color.textPrimary,\n      },\n    });\n  }, [navigation, theme]);\n\n  const handleUserPress = (user: CometChat.User) => {\n    navigation.navigate('Messages', { user });\n  };\n\n  const handleAvatarPress = useCallback(() => {\n    try {\n      if (avatarContainerRef.current) {\n        avatarContainerRef.current.measureInWindow((x, y, width, height) => {\n          // Add spacing from the avatar - move tooltip to the right and down\n          tooltipPosition.current = {\n            pageX: x + width + 20, // 20px to the right of the avatar\n            pageY: y + height + 10 // 10px below the avatar\n          };\n        });\n        setTooltipVisible(true);\n      }\n    } catch (error) {\n      console.error('Error while handling avatar press:', error);\n    }\n  }, [avatarContainerRef, tooltipPosition, setTooltipVisible]);\n\n  const handleLogout = async () => {\n    if (isLoggingOut) return;\n    setIsLoggingOut(true);\n\n    // Step 1: Logout from CometChat\n    try {\n      await CometChat.logout();\n    } catch (error) {\n      console.error('CometChat logout failed:', error);\n      setIsLoggingOut(false);\n      return; // Exit if CometChat logout fails\n    }\n\n\n    // If all operations succeed, navigate to the LoginScreen\n    setIsLoggingOut(false);\n    setLogout(false);\n    // navigate('Login');\n    navigationRef.dispatch(\n      CommonActions.reset({\n        index: 0,\n        routes: [{ name: 'SampleUser' }],\n      }),\n    );\n  };\n\n  const AppBarWrapper = React.useMemo(\n    () => () => (\n      <View style={styles.appBarWrapper}>\n        <AppBarOptions\n          onPress={handleAvatarPress}\n          loggedInUser={loggedInUser}\n          avatarContainerRef={avatarContainerRef}\n        />\n      </View>\n    ),\n    [handleAvatarPress, loggedInUser, avatarContainerRef]\n  );\n\n\n\n\n\n  return (\n    <View style={styles.container}>\n      <View style={styles.container}>\n        <CometChatUsers\n          onItemPress={handleUserPress}\n          title='Assistants'\n          showBackButton={false}\n          AppBarOptions={AppBarWrapper}\n          usersRequestBuilder={new CometChat.UsersRequestBuilder()\n            .setLimit(30)\n            .hideBlockedUsers(false)\n            .setRoles(['@agentic'])\n            .friendsOnly(false)\n            .setStatus('')\n            .setTags([])\n            .sortBy('name')\n            .setUIDs([])}\n        />\n      </View>\n\n      <View>\n        <TooltipMenu\n          visible={tooltipVisible}\n          onClose={() => {\n            setTooltipVisible(false);\n          }}\n          onDismiss={() => {\n            setTooltipVisible(false);\n          }}\n          event={{\n            nativeEvent: tooltipPosition.current,\n          }}\n          menuItems={[\n            {\n              text: loggedInUser?.getName() || 'User',\n              onPress: () => {\n                setTooltipVisible(false);\n              },\n              icon: (\n                <AccountCircle\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AccountCircle>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: AppConstants.versionNumber,\n              onPress: () => {},\n              icon: (\n                <Info\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}\n                />\n              ),\n            },\n            {\n              text: 'Logout',\n              onPress: () => {\n                handleLogout();\n              },\n              icon: (\n                <Logout\n                  height={24}\n                  width={24}\n                  color={theme.color.error}\n                />\n              ),\n              textColor: theme.color.error,\n              iconColor: theme.color.error,\n            },\n          ]}\n        />\n      </View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  appBarWrapper: {\n    marginRight: 15,\n  },\n});\n\nexport default AIAgents;"
  },
  {
    "path": "examples/SampleAppAI/src/components/Messages.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  TouchableOpacity,\n  View,\n  StyleSheet,\n  Text,\n  BackHandler,\n  Platform,\n  Modal,\n  Animated,\n  Dimensions,\n} from 'react-native';\nimport {\n  CometChatUIKit,\n  CometChatMessageHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  useTheme,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatAIAssistantChatHistory,\n  CometChatAIAssistantTools,\n  CometChatThemeProvider,\n  useCometChatTranslation\n} from '@cometchat/chat-uikit-react-native';\nimport {StackScreenProps} from '@react-navigation/stack';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CommonUtils} from '../utils/CommonUtils';\nimport {useActiveChat} from '../utils/ActiveChatContext';\nimport {useSafeAreaInsets} from 'react-native-safe-area-context';\nimport {RootStackParamList} from '../navigation/types';\n\nconst {width} = Dimensions.get('window');\n\ntype Props = StackScreenProps<RootStackParamList, 'Messages'>;\n\nconst Messages: React.FC<Props> = ({route, navigation}) => {\n  const {user, group, parentMessageId: routeParentMessageId} = route.params;\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const themeRef = useRef(theme);\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n  const userListenerId = 'app_messages' + new Date().getTime();\n  const openmessageListenerId = 'message_' + new Date().getTime();\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(user);\n  const [messageListKey, setMessageListKey] = useState(0);\n  const [messageComposerKey, setMessageComposerKey] = useState(0);\n  const [showHistoryModal, setShowHistoryModal] = useState(false);\n  \n  // Manage parentMessageId in parent component\n  const [parentMessageId, setParentMessageId] = useState<string | undefined>(routeParentMessageId);\n  \n  const {setActiveChat} = useActiveChat();\n  const insets = useSafeAreaInsets();\n  \n  // Add ref to track streaming state\n  const messageComposerRef = useRef<any>(null);\n\n  /** Animation state for drawer */\n  const slideAnim = useRef(new Animated.Value(width)).current;\n\n  useEffect(() => {\n    if (showHistoryModal) {\n      Animated.timing(slideAnim, {\n        toValue: 0,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    } else {\n      Animated.timing(slideAnim, {\n        toValue: width,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    }\n  }, [showHistoryModal, slideAnim]);\n\n    /** Agentic user check */\n  const isAgenticUser = useCallback((): boolean => {\n    if (localUser) {\n      return localUser.getRole?.() === '@agentic';\n    }\n    return false;\n  }, [localUser]);\n  const agentic = isAgenticUser();\n\n  /** Active chat setup */\n  useEffect(() => {\n    // if it's a user chat\n    if (user) {\n      setActiveChat({type: 'user', id: user.getUid()});\n    } else if (group) {\n      setActiveChat({type: 'group', id: group.getGuid()});\n    }\n\n    // Cleanup on unmount => setActiveChat(null)\n    return () => {\n      setActiveChat(null);\n      // Reset streaming state when leaving chat\n      if (messageComposerRef.current?.resetStreaming) {\n        messageComposerRef.current.resetStreaming();\n      }\n    };\n  }, [user, group, setActiveChat]);\n\n  useEffect(() => {\n    themeRef.current = theme;\n    navigationRef.current = navigation;\n    routeRef.current = route;\n  }, [theme, navigation, route]);\n\n  const toggleTab = useCallback(() => {\n    // Simple toggle function without dependency on external toggleBottomTab\n    return () => {};\n  }, []);\n\n  useLayoutEffect(() => {\n    const cleanup = toggleTab();\n    return () => {\n      cleanup();\n    };\n  }, [toggleTab]);\n\n  useEffect(() => {\n    const backAction = () => {\n      // Reset streaming state when navigating back\n      if (messageComposerRef.current?.resetStreaming) {\n        messageComposerRef.current.resetStreaming();\n      }\n      navigation.popToTop();\n      return true;\n    };\n    // Add event listener for hardware back press\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n    return () => backHandler.remove();\n  }, [navigation]);\n\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (item: {user: CometChat.User}) =>\n        handleccUserBlocked(item),\n      ccUserUnBlocked: (item: {user: CometChat.User}) =>\n        handleccUserUnBlocked(item),\n    });\n\n    const statusListenerId = 'user_status_messages_' + new Date().getTime();\n    if (localUser) {\n\n      CometChat.addUserListener(\n        statusListenerId,\n        new CometChat.UserListener({\n          onUserOnline: (onlineUser: CometChat.User) => {\n            if (onlineUser.getUid() === localUser.getUid()) {\n              console.log('🚀 ~ onUserOnline ~ onlineUser:', onlineUser);\n              setLocalUser(onlineUser); \n            }\n          },\n          onUserOffline: (offlineUser: CometChat.User) => {\n            console.log('🚀 ~ onUserOffline ~ offlineUser:', offlineUser);\n            if (offlineUser.getUid() === localUser.getUid()) {\n              setLocalUser(offlineUser); \n            }\n          },\n        }),\n      );\n    }\n\n    CometChatUIEventHandler.addUIListener(openmessageListenerId, {\n      openChat: ({user}) => {\n        if (user != undefined) {\n          navigation.push('Messages', {\n            user,\n          });\n        }\n      },\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n      CometChat.removeUserListener(statusListenerId);\n      CometChatUIEventHandler.removeUIListener(openmessageListenerId);\n    };\n  }, [localUser]);\n\n  const handleccUserBlocked = ({user}: {user: CometChat.User}) => {\n    setLocalUser(CommonUtils.clone(user));\n  };\n\n  const handleccUserUnBlocked = ({user}: {user: CometChat.User}) => {\n    setLocalUser(CommonUtils.clone(user));\n  };\n\n  const unblock = async (userToUnblock: CometChat.User) => {\n    let uid = userToUnblock.getUid();\n    try {\n      const response = await CometChat.unblockUsers([uid]);\n      const unBlockedUser = await CometChat.getUser(uid);\n      if (response) {\n        setLocalUser(unBlockedUser);\n\n        // Optionally emit an event or let the server call do the job\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: unBlockedUser,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user:', error);\n    }\n  };\n\n\n  /** Reset messages */\nconst handleNewChatClick = useCallback(() => {\n  if (messageComposerRef.current?.resetStreaming) {\n    messageComposerRef.current.resetStreaming();\n  }\n  setParentMessageId(undefined);\n  setMessageListKey(prev => prev + 1);\n  setMessageComposerKey(prev => prev + 1);\n  setShowHistoryModal(false);\n  navigation.replace('Messages', {\n    user,\n    group,\n  });\n}, [navigation, user, group]);\n\n  /** Open chat history modal */\n  const handleChatHistoryClick = useCallback(() => {\n    setShowHistoryModal(true);\n  }, []);\n\n  /** Handle history message click */\n  const handleHistoryMessageClick = useCallback(\n    (message: CometChat.BaseMessage) => {\n      if (messageComposerRef.current && messageComposerRef.current.stopStreaming) {\n        messageComposerRef.current.stopStreaming();\n      }\n      setShowHistoryModal(false);\n      setParentMessageId(message.getId().toString());\n      setMessageListKey(prev => prev + 1);\n      setMessageComposerKey(prev => prev + 1);\n    },\n    [],\n  );\n\n  /** Handle history error */\n  const handleChatHistoryError = useCallback(\n    (_error: CometChat.CometChatException) => {},\n    [],\n  );\n\n  /** Theme override for agentic outgoing bubble */\n  const providerTheme = useMemo(() => {\n    const defaultOutgoingBg =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.containerStyle?.backgroundColor;\n    const defaultTextColor =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.textBubbleStyles?.textStyle?.color;\n\n    const outgoingOverride = {\n      messageComposerStyles: {\n        containerStyle: {\n          backgroundColor: agentic\n            ? theme.color.background3\n            : theme?.messageComposerStyles?.containerStyle?.backgroundColor,\n        },\n      },\n      messageListStyles: {\n        outgoingMessageBubbleStyles: {\n          containerStyle: {\n            backgroundColor: agentic\n              ? theme.color.background4\n              : defaultOutgoingBg,\n          },\n          textBubbleStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textPrimary\n                : defaultTextColor,\n            },\n          },\n          dateStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textSecondary\n                : defaultTextColor,\n            },\n          },\n        },\n      },\n    };\n\n    return {\n      light: outgoingOverride,\n      dark: outgoingOverride,\n      mode: \"auto\" as \"auto\",\n    };\n  }, [agentic, theme]);\n\n  return (\n    <CometChatThemeProvider theme={providerTheme}>\n      <View style={styles.flexOne}>\n        <CometChatMessageHeader\n          user={localUser}\n          group={group}\n          onBack={() => navigation.popToTop()}\n          showBackButton\n          hideChatHistoryButton={false}\n          hideNewChatButton={false}\n          onChatHistoryButtonClick={handleChatHistoryClick}\n          onNewChatButtonClick={handleNewChatClick}\n        />\n        <View style={styles.flexOne}>\n          <CometChatMessageList\n            key={messageListKey}\n            user={user}\n            group={group}\n            parentMessageId={parentMessageId}\n            aiAssistantTools={new CometChatAIAssistantTools({\n              getCurrentWeather: (args: any) => console.log('Weather args', args),\n            })}\n            streamingSpeed={10}\n          />\n        </View>\n\n        {/* Chat History Drawer */}\n        {agentic && (\n          <Modal visible={showHistoryModal} transparent animationType=\"none\" onRequestClose={() => setShowHistoryModal(false)}>\n            <View style={drawerStyles.backdrop}>\n              <Animated.View\n                style={[\n                  drawerStyles.drawer,\n                  { \n                    backgroundColor: theme.color.background1,\n                    paddingTop: Platform.OS === 'ios' ? insets.top : 0,\n                  },\n                  { transform: [{ translateX: slideAnim }] },\n                ]}\n              >\n                <CometChatAIAssistantChatHistory\n                  user={localUser}\n                  group={group}\n                  onClose={() => setShowHistoryModal(false)}\n                  onMessageClicked={handleHistoryMessageClick}\n                  onError={handleChatHistoryError}\n                  onNewChatButtonClick={handleNewChatClick}\n                />\n              </Animated.View>\n            </View>\n          </Modal>\n        )}\n\n        {localUser?.getBlockedByMe() ? (\n          <View style={[styles.blockedContainer, { backgroundColor: theme.color.background3 }]}>\n            <Text\n              style={[\n                theme.typography.button.regular,\n                { color: theme.color.textSecondary, textAlign: 'center', paddingBottom: 10 },\n              ]}\n            >\n              {t('BLOCKED_USER_DESC')}\n            </Text>\n            <TouchableOpacity\n              onPress={() => unblock(localUser)}\n              style={[styles.button, { borderColor: theme.color.borderDefault }]}\n            >\n              <Text style={[theme.typography.button.medium, styles.buttontext, { color: theme.color.textPrimary }]}>\n                {t('UNBLOCK')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        ) : (\n          <CometChatCompactMessageComposer\n            key={messageComposerKey}\n            ref={messageComposerRef}\n            parentMessageId={parentMessageId}\n            user={localUser}\n            group={group}\n            keyboardAvoidingViewProps={{\n              ...(Platform.OS === 'android' ? {} : { behavior: 'padding' }),\n            }}\n          />\n        )}\n      </View>\n    </CometChatThemeProvider>\n  );\n};\n\nconst styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n  appBarContainer: {\n    flexDirection: 'row',\n    marginLeft: 16,\n  },\n});\n\nconst drawerStyles = StyleSheet.create({\n  backdrop: {\n    justifyContent: 'flex-start',\n    alignItems: 'flex-end',\n  },\n  drawer: {\n    width: '100%',\n    height: '100%',\n    overflow: 'hidden',\n  },\n});\n\nexport default Messages;"
  },
  {
    "path": "examples/SampleAppAI/src/components/login/AppCredentials.tsx",
    "content": "import React, {useState} from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TextInput,\n  TouchableOpacity,\n  Image,\n  useColorScheme,\n  Platform,\n  Dimensions,\n  SafeAreaView,\n  ScrollView,\n  KeyboardAvoidingView,\n  BackHandler,\n} from 'react-native';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport {\n  CometChatUIKit,\n  UIKitSettings,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {navigate, navigationRef} from '../../navigation/NavigationService';\nimport {SCREEN_CONSTANTS} from '../../utils/AppConstants';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {useFocusEffect} from '@react-navigation/native';\n\nconst AppCredentials: React.FC = () => {\n  const [storedAppId, setStoredAppId] = useState<string>('');\n  const [storedAuthKey, setStoredAuthKey] = useState<string>('');\n  const [storedRegion, setStoredRegion] = useState<string>('US');\n\n  // These are the *editable* states bound to the TextInput fields\n  const [appId, setAppId] = useState<string>('');\n  const [authKey, setAuthKey] = useState<string>('');\n  const [selectedRegion, setSelectedRegion] = useState<string>('US');\n\n  // Toast state for showing error messages\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  const theme = useTheme();\n    const { t } = useCometChatTranslation();\n\n  const {width} = Dimensions.get('window');\n  const mode = useColorScheme();\n\n  // Compute if form is valid (all fields provided)\n  const isFormValid =\n    appId.trim().length > 0 &&\n    authKey.trim().length > 0 &&\n    selectedRegion.trim().length > 0;\n\n  // Load existing credentials if any\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        navigationRef.goBack();\n        return true; // Prevent default behavior\n      };\n      const backHandler = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      async function loadCredentials() {\n        try {\n          const credentialsStr = await AsyncStorage.getItem('appCredentials');\n          if (credentialsStr) {\n            const credentials = JSON.parse(credentialsStr);\n            // Update 'stored' states\n            setStoredAppId(credentials.appId || '');\n            setStoredAuthKey(credentials.authKey || '');\n            setStoredRegion(credentials.region || 'US');\n\n            // Also set the fields so user sees them pre-populated\n            setAppId(credentials.appId || '');\n            setAuthKey(credentials.authKey || '');\n            setSelectedRegion(credentials.region || 'US');\n          }\n        } catch (error) {\n          console.log('Error loading stored credentials:', error);\n        }\n      }\n\n      loadCredentials();\n\n      return () => backHandler.remove();\n    }, []),\n  );\n\n  const showToast = (message: string) => {\n    setToastMessage(message);\n    setTimeout(() => {\n      setToastMessage(null);\n    }, 2500);\n  };\n\n  const handleContinue = async (): Promise<void> => {\n    // Validate the inputs.\n    // If the user has modified a field and cleared it (i.e. the input becomes empty),\n    // show a toast message.\n    if (!appId.trim()) {\n      showToast('Please enter App ID');\n      return;\n    }\n    if (!authKey.trim()) {\n      showToast('Please enter Auth Key');\n      return;\n    }\n    if (!selectedRegion.trim()) {\n      showToast('Please select a region');\n      return;\n    }\n\n    // Since all fields are non-empty, use their current values.\n    const newAppId = appId.trim();\n    const newAuthKey = authKey.trim();\n    const newRegion = selectedRegion.trim();\n\n    try {\n      const credentials = {\n        region: newRegion,\n        appId: newAppId,\n        authKey: newAuthKey,\n      };\n      console.log('Saving credentials:', credentials);\n      await AsyncStorage.setItem('appCredentials', JSON.stringify(credentials));\n\n      // Re-initialize with updated credentials\n      await CometChatUIKit.init({\n        appId: newAppId,\n        authKey: newAuthKey,\n        region: newRegion,\n        subscriptionType: CometChat.AppSettings\n          .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n      });\n    } catch (error) {\n      console.error('Failed to save credentials', error);\n    }\n\n    // Navigate to the next screen.\n    navigate('BottomTabNavigator');\n    navigationRef.reset({\n      index: 0,\n      routes: [{name: SCREEN_CONSTANTS.SAMPLE_USER}],\n    });\n  };\n\n  return (\n    <SafeAreaView\n      style={[styles.container, {backgroundColor: theme.color.background2}]}>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === 'ios' ? 'padding' : undefined}>\n        <View style={styles.contentContainer}>\n          <ScrollView\n            contentContainerStyle={styles.scrollContent}\n            keyboardShouldPersistTaps=\"handled\">\n            {/* Header/Logo */}\n            <View style={styles.logoContainer}>\n              <Image\n                source={\n                  mode === 'dark'\n                    ? require('../../assets/icons/Dark.png')\n                    : require('../../assets/icons/Light.png')\n                }\n                style={{\n                  width: width * 0.25,\n                  height: width * 0.25,\n                  resizeMode: 'contain',\n                }}\n              />\n            </View>\n\n            {/* Title */}\n            <Text\n              style={[\n                theme.typography.heading2.bold,\n                {\n                  color: theme.color.textPrimary,\n                  marginBottom: 20,\n                  alignSelf: 'center',\n                },\n              ]}>\n              {t('APP_CREDENTIALS')}\n            </Text>\n\n            {/* Region Selector */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  {color: theme.color.textPrimary, marginBottom: 10},\n                ]}>\n                {t('REGION')}\n              </Text>\n              <View style={styles.regionRow}>\n                {/* US */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'US'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'US'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('US')}>\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/US.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        {color: theme.color.textSecondary},\n                      ]}>\n                      US\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* EU */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('EU')}>\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/EU.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        {color: theme.color.textSecondary},\n                      ]}>\n                      EU\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* IN */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('IN')}>\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/India.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        {color: theme.color.textSecondary},\n                      ]}>\n                      IN\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n              </View>\n            </View>\n\n            {/* App ID */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  {color: theme.color.textPrimary, paddingBottom: 5},\n                ]}>\n                APP ID\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={appId}\n                onChangeText={setAppId}\n                placeholder=\"Enter the App ID\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n\n            {/* Auth Key */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  {color: theme.color.textPrimary, paddingBottom: 5},\n                ]}>\n                Auth Key\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={authKey}\n                onChangeText={setAuthKey}\n                placeholder=\"Enter the Auth Key\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n          </ScrollView>\n\n          {/* Continue Button */}\n          <TouchableOpacity\n            style={[\n              styles.continueButton,\n              {\n                backgroundColor: theme.color.primaryButtonBackground,\n                opacity: isFormValid ? 1 : 0.6,\n              },\n            ]}\n            onPress={handleContinue}>\n            <Text\n              style={[\n                theme.typography.button.medium,\n                {textAlign: 'center', color: theme.color.staticWhite},\n              ]}>\n              {t('CONTINUE')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      </KeyboardAvoidingView>\n      {/* Toast Message */}\n      {toastMessage && (\n        <View style={styles.toastContainer}>\n          <Text style={styles.toastText}>{toastMessage}</Text>\n        </View>\n      )}\n    </SafeAreaView>\n  );\n};\n\nexport default AppCredentials;\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  contentContainer: {\n    flex: 1,\n    justifyContent: 'space-between',\n    paddingHorizontal: 16,\n    paddingBottom: 16,\n  },\n  scrollContent: {\n    paddingBottom: 16,\n  },\n  logoContainer: {\n    alignItems: 'center',\n    marginTop: Platform.OS === 'android' ? 30 : 50,\n    marginBottom: 20,\n  },\n  inputContainer: {\n    width: '100%',\n    marginTop: 20,\n  },\n  regionRow: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n  },\n  flagContainer: {\n    width: '32%',\n    borderWidth: 2,\n    borderColor: 'transparent',\n    borderRadius: 12,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  flagInnerContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 10,\n    gap: 5,\n  },\n  flagImage: {\n    width: 30,\n    height: 30,\n    resizeMode: 'contain',\n  },\n  input: {\n    paddingHorizontal: 12,\n    paddingVertical: 10,\n    borderRadius: 8,\n    borderWidth: 1,\n  },\n  continueButton: {\n    borderRadius: 8,\n    paddingVertical: 12,\n    width: '100%',\n  },\n  toastContainer: {\n    position: 'absolute',\n    bottom: '8%',\n    left: 20,\n    right: 20,\n    backgroundColor: '#C73C3E',\n    padding: 6,\n    borderRadius: 8,\n    alignItems: 'center',\n  },\n  toastText: {\n    color: '#fff',\n    fontSize: 14,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppAI/src/components/login/SampleUser.tsx",
    "content": "import React, {useEffect, useState} from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TouchableOpacity,\n  TextInput,\n  Image,\n  SafeAreaView,\n  Dimensions,\n  useColorScheme,\n  Pressable,\n  ImageSourcePropType,\n  KeyboardAvoidingView,\n  Platform,\n  ScrollView,\n  ActivityIndicator,\n} from 'react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatAvatar,\n  CometChatUIKit,\n  Icon,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport Check from '../../assets/icons/CheckFill';\nimport {sampleData} from '../../utils/helper';\nimport {SCREEN_CONSTANTS} from '../../utils/AppConstants';\nimport {navigate, navigationRef} from '../../navigation/NavigationService';\nimport Skeleton from './Skeleton'\n\ntype GridItem = CometChat.User | {dummy: true};\n\nconst LoginScreen: React.FC = () => {\n  const [users, setUsers] = useState<CometChat.User[]>([]);\n  const [selectedUser, setSelectedUser] = useState<string | null>(null);\n  const [userUID, setUserUID] = useState<string>('');\n  const [isLoading, setIsLoading] = useState<boolean>(false);\n  const [loadingUsers, setLoadingUsers] = useState<boolean>(true);\n\n  const theme = useTheme();\n  const mode = useColorScheme();\n  const {width} = Dimensions.get('window');\n\n  useEffect(() => {\n    (async function loadUsers(): Promise<void> {\n      try {\n        setLoadingUsers(true);\n        const fetchedUsers = await fetchUsers();\n        setUsers(fetchedUsers);\n      } catch (error) {\n        console.error(error);\n      } finally {\n        setLoadingUsers(false);\n      }\n    })();\n  }, []);\n\n  const handleSelectUser = (user: CometChat.User): void => {\n    setSelectedUser(user.getUid());\n    setUserUID('');\n  };\n\n  const handleContinue = async () => {\n    if ((!selectedUser && !userUID.trim()) || isLoading) return;\n    setIsLoading(true);\n    const uid: string = userUID.trim() || selectedUser!;\n    try {\n      await CometChatUIKit.login({uid});\n      navigate('BottomTabNavigator');\n      navigationRef.reset({\n        index: 0,\n        routes: [{name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR}],\n      });\n    } catch (error: any) {\n      console.log('Login failed with exception:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  /**\n   * Fetch users from a remote sample JSON file.\n   * Falls back to local sample data if there's an error.\n   */\n  async function fetchUsers(): Promise<CometChat.User[]> {\n    try {\n      const response = await fetch(\n        'https://assets.cometchat.io/sampleapp/sampledata.json',\n      );\n\n      if (response.ok) {\n        const data = await response.json();\n        const fetchedUsers = data.users || [];\n        return fetchedUsers.map((user: any) => new CometChat.User(user));\n      } else {\n        throw new Error('Failed to load users');\n      }\n    } catch (error) {\n      console.error('Exception while fetching users:', error);\n      return await getDefaultUsers();\n    }\n  }\n\n  /**\n   * Get users from local sample data (used in case the remote fetch fails).\n   */\n  async function getDefaultUsers(): Promise<CometChat.User[]> {\n    const localUsers = sampleData.users || [];\n    return localUsers.map((user: any) => new CometChat.User(user));\n  }\n\n  /**\n   * Returns the appropriate image source object for the avatar.\n   */\n  const getAvatarSource = (\n    avatar: string | ImageSourcePropType,\n  ): ImageSourcePropType => {\n    if (typeof avatar === 'string') {\n      if (avatar.startsWith('http://') || avatar.startsWith('https://')) {\n        return {uri: avatar};\n      }\n    }\n    return avatar as ImageSourcePropType;\n  };\n\n  // Show skeleton if the API is still loading or if no users are available.\n  const showSkeleton = loadingUsers || users.length === 0;\n\n  // Compute grid data only if users are available.\n  let gridData: GridItem[] = [];\n  if (users.length > 0) {\n    gridData = [...users];\n    const numColumns = 3;\n    const numberOfElementsLastRow = users.length % numColumns;\n    if (numberOfElementsLastRow !== 0) {\n      for (let i = 0; i < numColumns - numberOfElementsLastRow; i++) {\n        gridData.push({dummy: true});\n      }\n    }\n  }\n\n  const Loading = () => {\n    return (\n      <View\n        style={{\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}>\n        <ActivityIndicator\n          size=\"small\"\n          color={theme.color.staticWhite}\n          style={{alignSelf: 'center', justifyContent: 'center'}}\n        />\n      </View>\n    );\n  };\n\n  return (\n    <KeyboardAvoidingView\n      style={styles.keyboardAvoidingContainer}\n      behavior={Platform.OS === 'ios' ? 'padding' : undefined}>\n      <SafeAreaView\n        style={[styles.container, {backgroundColor: theme.color.background2}]}>\n        <ScrollView contentContainerStyle={styles.scrollContainer}>\n          {/* App Logo */}\n          <View style={styles.logoContainer}>\n            <Image\n              source={\n                mode === 'dark'\n                  ? require('../../assets/icons/Dark.png')\n                  : require('../../assets/icons/Light.png')\n              }\n              style={{\n                width: width * 0.25,\n                height: width * 0.25,\n                resizeMode: 'contain',\n              }}\n            />\n          </View>\n\n          {/* Title */}\n          <Text\n            style={[\n              theme.typography.heading2.bold,\n              styles.logInTitle,\n              {color: theme.color.textPrimary},\n            ]}>\n            Log In\n          </Text>\n\n          {/* Subtitle */}\n          <Text\n            style={[\n              theme.typography.body.medium,\n              styles.subtitle,\n              {color: theme.color.textPrimary},\n            ]}>\n            Choose a Sample User\n          </Text>\n\n          {/* Sample Users Grid */}\n          <View style={styles.userGridWrapper}>\n            {showSkeleton ? (\n              <Skeleton />\n            ) : (\n              <View style={styles.usersContainer}>\n                {gridData.map((item, index) => {\n                  // Render a blank view for dummy items\n                  if ('dummy' in item && item.dummy) {\n                    return <View key={`dummy-${index}`} style={styles.userCard} />;\n                  }\n\n                  // Otherwise, render a user\n                  const user = item as CometChat.User;\n                  const isSelected = selectedUser === user.getUid();\n                  const firstName = user.getName();\n\n                  return (\n                    <Pressable\n                      key={user.getUid()}\n                      style={[\n                        styles.userCard,\n                        {\n                          borderWidth: isSelected ? 1.5 : 1,\n                          borderColor: isSelected\n                            ? theme.color.borderHighlight\n                            : theme.color.borderLight,\n                          backgroundColor: isSelected\n                            ? theme.color.extendedPrimary50\n                            : theme.color.background1,\n                        },\n                      ]}\n                      onPress={() => handleSelectUser(user)}>\n                      {/* Show the check icon ONLY if selected */}\n                      {isSelected && (\n                        <View style={styles.checkIconContainer}>\n                          <Icon\n                            icon={\n                              <Check\n                                color={theme.color.staticWhite}\n                                height={18}\n                                width={18}\n                              />\n                            }\n                          />\n                        </View>\n                      )}\n                      <CometChatAvatar\n                        name={user.getName()}\n                        image={getAvatarSource(user.getAvatar())}\n                      />\n                      {/* Display only the first name */}\n                      <Text\n                        style={[\n                          theme.typography.body.medium,\n                          styles.firstNameText,\n                          {color: theme.color.textPrimary},\n                        ]}>\n                        {firstName}\n                      </Text>\n                      <Text\n                        style={[\n                          theme.typography.caption1.regular,\n                          styles.uidText,\n                          {color: theme.color.textSecondary},\n                        ]}>\n                        {user.getUid()}\n                      </Text>\n                    </Pressable>\n                  );\n                })}\n              </View>\n            )}\n          </View>\n\n          {/* Horizontal divider with \"Or\" in the middle */}\n          <View style={styles.dividerRow}>\n            <View\n              style={[styles.divider, {borderColor: theme.color.borderDefault}]}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                {color: theme.color.textTertiary},\n              ]}>\n              Or\n            </Text>\n            <View\n              style={[styles.divider, {borderColor: theme.color.borderDefault}]}\n            />\n          </View>\n\n          {/* UID Input */}\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.uidLabel,\n              {color: theme.color.textPrimary},\n            ]}>\n            Enter Your UID\n          </Text>\n          <TextInput\n            placeholder=\" Enter UID\"\n            placeholderTextColor={theme.color.textTertiary}\n            style={[\n              theme.typography.body.regular,\n              styles.uidInput,\n              {\n                borderColor: theme.color.borderLight,\n                color: theme.color.textPrimary,\n              },\n            ]}\n            value={userUID}\n            onChangeText={(text: string) => {\n              setUserUID(text);\n              setSelectedUser(null);\n            }}\n          />\n        </ScrollView>\n\n        {/* Bottom container with \"Continue\" button and \"Change App Credentials\" */}\n        <View style={styles.bottomContainer}>\n          <TouchableOpacity\n            style={[\n              styles.continueButton,\n              {\n                backgroundColor: theme.color.primaryButtonBackground,\n              },\n            ]}\n            onPress={handleContinue}\n            disabled={(!selectedUser && !userUID.trim()) || isLoading}>\n            {isLoading ? (\n              <Loading />\n            ) : (\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.continueButtonText,\n                  {color: theme.color.staticWhite},\n                ]}>\n                Continue\n              </Text>\n            )}\n          </TouchableOpacity>\n\n          <View style={styles.changeCredentialsWrapper}>\n            <Text\n              style={[\n                theme.typography.body.regular,\n                {color: theme.color.textSecondary},\n              ]}>\n              Change\n            </Text>\n            <TouchableOpacity\n              style={styles.changeCredentialsContainer}\n              onPress={() => {\n                navigationRef.navigate(SCREEN_CONSTANTS.APP_CRED);\n              }}>\n              <Text\n                style={[\n                  theme.typography.body.regular,\n                  {color: theme.color.primary},\n                ]}>\n                App Credentials\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </SafeAreaView>\n    </KeyboardAvoidingView>\n  );\n};\n\nexport default LoginScreen;\n\nconst styles = StyleSheet.create({\n  keyboardAvoidingContainer: {\n    flex: 1,\n  },\n  container: {\n    flex: 1,\n    justifyContent: 'space-between',\n  },\n  scrollContainer: {\n    flexGrow: 1,\n    paddingHorizontal: 16,\n  },\n  logoContainer: {\n    alignItems: 'center',\n    marginBottom: 16,\n  },\n  logInTitle: {\n    marginBottom: 16,\n    alignSelf: 'center',\n  },\n  subtitle: {\n    marginBottom: 6,\n  },\n  usersContainer: {\n    flexDirection: 'row',\n    flexWrap: 'wrap',\n    justifyContent: 'space-around',\n  },\n  userCard: {\n    position: 'relative',\n    width: '30%',\n    borderRadius: 8,\n    paddingVertical: 16,\n    paddingHorizontal: 8,\n    marginBottom: 12,\n    alignItems: 'center',\n    overflow: 'hidden',\n  },\n  checkIconContainer: {\n    position: 'absolute',\n    top: 0,\n    right: 0,\n    borderBottomLeftRadius: 10,\n    borderTopRightRadius: 7,\n    width: '27%',\n    height: '22%',\n    backgroundColor: '#7367F0',\n    alignItems: 'center',\n    justifyContent: 'center',\n    zIndex: 2,\n  },\n  firstNameText: {\n    marginTop: 8,\n    textAlign: 'center',\n  },\n  uidText: {\n    marginTop: 4,\n    textAlign: 'center',\n  },\n  dividerRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginVertical: 16,\n    gap: 10,\n  },\n  divider: {\n    flex: 1,\n    height: 1,\n    borderWidth: 0.5,\n  },\n  uidLabel: {\n    paddingBottom: 5,\n  },\n  uidInput: {\n    borderWidth: 1,\n    borderRadius: 8,\n    padding: 10,\n    marginBottom: 16,\n  },\n  bottomContainer: {\n    paddingBottom: 20,\n    paddingHorizontal: 16,\n  },\n  continueButton: {\n    paddingVertical: 12,\n    borderRadius: 6,\n    marginBottom: 12,\n  },\n  continueButtonText: {\n    alignSelf: 'center',\n  },\n  changeCredentialsWrapper: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    gap: 5,\n    justifyContent: 'center',\n  },\n  changeCredentialsContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  userGridWrapper: {\n    minHeight: 240,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n});"
  },
  {
    "path": "examples/SampleAppAI/src/components/login/Skeleton.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Dimensions, Easing, StyleSheet, useColorScheme, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\n// import { useThemeInternal } from \"../../../theme/hook\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonBox = ({ index, boxSize, gradientColors }: any) => {\n  // Determine if the box is the last in its row\n  const isLastInRow = (index + 1) % 3 === 0;\n\n  return (\n    <Svg\n      height={boxSize}\n      width={boxSize}\n      viewBox=\"0 0 100 100\"\n      fill=\"none\"\n      style={[\n        styles.skeletonBox,\n        {\n          width: boxSize,\n          height: boxSize,\n          marginRight: isLastInRow ? 0 : 8,\n        },\n      ]}\n    >\n      <Defs>\n        <LinearGradient\n          id={`paint0_linear_${index}`}\n          x1={10}\n          y1={50}\n          x2={90}\n          y2={50}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <Stop stopColor={gradientColors[0]} />\n          <Stop offset={1} stopColor={gradientColors[1]} />\n        </LinearGradient>\n      </Defs>\n      <Rect\n        x={10}\n        y={10}\n        width={80}\n        height={80}\n        rx={15}\n        ry={15}\n        fill={`url(#paint0_linear_${index})`}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const animatedValue = useRef(new Animated.Value(0)).current;\n  const [isLoading, setIsLoading] = useState(true);\n  const  mode  = useColorScheme();\n\n  // Define static colors\n  const color = {\n    staticBlack: \"#000000\",\n    staticWhite: \"#FFFFFF\",\n  };\n\n  // Define skeletonStyle based on the theme mode\n  const skeletonStyle =\n    mode === \"light\"\n      ? {\n          linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n          shimmerBackgroundColor: color.staticBlack,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        }\n      : {\n          linearGradientColors: [\"#383838\", \"#272727\"],\n          shimmerBackgroundColor: color.staticWhite,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        };\n\n  const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } =\n    skeletonStyle;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n\n    // Simulate a loading time of 3 seconds\n    const loadData = setTimeout(() => {\n      setIsLoading(false);\n    }, 3000);\n\n    return () => clearTimeout(loadData);\n  }, [animatedValue, speed]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth, screenWidth],\n  });\n\n  if (!isLoading) {\n    return null;\n  }\n\n  const boxSize = (screenWidth - 22) / 3;\n  const shimmerWidth = screenWidth;\n  const shimmerHeight = boxSize + 16;\n\n  return (\n    <View style={styles.container}>\n      <View style={styles.grid}>\n        {new Array(6).fill(0).map((_, index) => (\n          <SkeletonBox\n            key={index}\n            index={index}\n            boxSize={boxSize}\n            gradientColors={linearGradientColors}\n          />\n        ))}\n      </View>\n      <Animated.View\n        style={[\n          styles.animatedShimmer,\n          {\n            width: shimmerWidth,\n            height: shimmerHeight,\n            transform: [{ translateX: shimmerTranslateX }],\n            backgroundColor: shimmerBackgroundColor,\n            opacity: shimmerOpacity,\n          },\n        ]}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    position: \"relative\",\n    borderRadius: 16,\n    overflow: \"hidden\",\n  },\n  grid: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n  },\n  skeletonBox: {\n    marginBottom: 10,\n    marginHorizontal: -10,\n  },\n  animatedShimmer: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n  },\n});\n\nexport default Skeleton;\n"
  },
  {
    "path": "examples/SampleAppAI/src/declarations.d.ts",
    "content": "declare module '*.png' {\n    const value: any;\n    export default value;\n  }\n  "
  },
  {
    "path": "examples/SampleAppAI/src/navigation/AuthContext.tsx",
    "content": "import React from 'react';\n\nexport interface AuthContextProps {\n  isLoggedIn: boolean;\n  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const AuthContext = React.createContext<AuthContextProps>({\n  isLoggedIn: false,\n  setIsLoggedIn: () => {},\n});\n"
  },
  {
    "path": "examples/SampleAppAI/src/navigation/BottomTabNavigator.tsx",
    "content": "import React from 'react';\nimport {createBottomTabNavigator} from '@react-navigation/bottom-tabs';\nimport {SCREEN_CONSTANTS} from '../utils/AppConstants';\nimport AIAgents from '../components/AIAgents';\nimport {BottomTabParamList} from './types';\n\n// Create the tab navigator.\nconst Tab = createBottomTabNavigator<BottomTabParamList>();\n\nconst BottomTabNavigator = () => {\n\n  return (\n    <Tab.Navigator\n      initialRouteName=\"Agents\"\n      screenOptions={{\n        headerShown: false,\n        tabBarStyle: {display: 'none'},\n      }}>\n      <Tab.Screen name={SCREEN_CONSTANTS.AGENTS} component={AIAgents} />\n    </Tab.Navigator>\n  );\n};\n\nexport default BottomTabNavigator;\n"
  },
  {
    "path": "examples/SampleAppAI/src/navigation/NavigationService.ts",
    "content": "import {createNavigationContainerRef} from '@react-navigation/native';\nimport {RootStackParamList} from './types';\n\nexport const navigationRef = createNavigationContainerRef<RootStackParamList>();\n\ninterface PendingNavigation {\n  name: keyof RootStackParamList;\n  params?: any;\n}\n\nlet pendingNavigation: PendingNavigation | null = null;\n\nexport function navigate<RouteName extends keyof RootStackParamList>(\n  name: RouteName,\n  params?: RootStackParamList[RouteName] extends undefined\n    ? undefined\n    : RootStackParamList[RouteName],\n) {\n  if (navigationRef.isReady()) {\n    // navigationRef.navigate(name as never);\n    navigationRef.navigate(name as any, params as any); \n  } else {\n    // Save the navigation intent for later processing\n    pendingNavigation = {name, params};\n  }\n}\n\nexport function processPendingNavigation() {\n  if (pendingNavigation && navigationRef.isReady()) {\n    const {name, params} = pendingNavigation;\n    navigationRef.navigate(name as any, params as any);\n    pendingNavigation = null;\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/src/navigation/RootStackNavigator.tsx",
    "content": "import React from 'react';\nimport { createNativeStackNavigator } from '@react-navigation/native-stack';\nimport { NavigationContainer, DefaultTheme } from '@react-navigation/native';\nimport BottomTabNavigator from './BottomTabNavigator';\nimport { SCREEN_CONSTANTS } from '../utils/AppConstants';\nimport { useTheme } from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from './types';\nimport { navigationRef, processPendingNavigation } from './NavigationService';\nimport SampleUser from '../components/login/SampleUser';\nimport AppCredentials from '../components/login/AppCredentials';\nimport { StatusBar, useColorScheme } from 'react-native';\nimport Messages from '../components/Messages';\n\n\ntype Props = {\n  isLoggedIn: boolean;\n  hasValidAppCredentials: boolean;\n};\n\nconst Stack = createNativeStackNavigator<RootStackParamList>();\n\nconst RootStackNavigator = ({ isLoggedIn, hasValidAppCredentials }: Props) => {\n  const theme = useTheme();\n  const NavigationTheme = {\n    ...DefaultTheme,\n    colors: {\n      ...DefaultTheme.colors,\n      background: theme.color.background1 as string,\n    },\n  };\n\n  const isDark = useColorScheme() === 'dark';\n  const backgroundColor = theme.color.background2;\n  const barStyle = isDark ? 'light-content' : 'dark-content';\n\n  return (\n    <>\n      <StatusBar\n        backgroundColor={backgroundColor}\n        barStyle={barStyle}\n        translucent={false}\n      />\n      <NavigationContainer\n        ref={navigationRef}\n        onReady={() => {\n          processPendingNavigation();\n        }}\n        theme={NavigationTheme}\n      >\n        <Stack.Navigator\n          initialRouteName={\n            isLoggedIn\n              ? SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR\n              : hasValidAppCredentials\n              ? SCREEN_CONSTANTS.SAMPLE_USER\n              : SCREEN_CONSTANTS.APP_CRED\n          }\n          screenOptions={{\n            gestureEnabled: true,\n            headerShown: false,\n            animation: 'slide_from_right',\n          }}\n        >\n          {/* Auth Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.APP_CRED}\n            component={AppCredentials}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SAMPLE_USER}\n            component={SampleUser}\n          />\n\n          {/* Tab Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR}\n            component={BottomTabNavigator}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.MESSAGES} component={Messages} />\n        </Stack.Navigator>\n      </NavigationContainer>\n    </>\n  );\n};\n\nexport default RootStackNavigator;\n"
  },
  {
    "path": "examples/SampleAppAI/src/navigation/types.ts",
    "content": "import {NavigatorScreenParams} from '@react-navigation/native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\n\nexport type RootStackParamList = {\n  Login: undefined;\n  BottomTabNavigator: NavigatorScreenParams<BottomTabParamList>;\n  AppCredentials: undefined;\n  SampleUser: undefined;\n  Messages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    fromMention?: boolean;\n    parentMessageId?: string;\n  };\n};\n\nexport type BottomTabParamList = {\n  Agents: undefined;\n};\n"
  },
  {
    "path": "examples/SampleAppAI/src/utils/ActiveChatContext.tsx",
    "content": "import React, { createContext, useContext, useState } from 'react';\n\ntype ActiveChat = {\n  type: 'user' | 'group';\n  id: string; // userUID or groupID\n} | null;\n\ntype ActiveChatContextType = {\n  activeChat: ActiveChat;\n  setActiveChat: React.Dispatch<React.SetStateAction<ActiveChat>>;\n};\n\nconst ActiveChatContext = createContext<ActiveChatContextType | undefined>(undefined);\n\nexport function ActiveChatProvider({ children }: { children: React.ReactNode }) {\n  const [activeChat, setActiveChat] = useState<ActiveChat>(null);\n\n  return (\n    <ActiveChatContext.Provider value={{ activeChat, setActiveChat }}>\n      {children}\n    </ActiveChatContext.Provider>\n  );\n}\n\n// Hook for using in any component\nexport function useActiveChat() {\n  const context = useContext(ActiveChatContext);\n  if (!context) {\n    throw new Error('useActiveChat must be used within an ActiveChatProvider');\n  }\n  return context;\n}\n"
  },
  {
    "path": "examples/SampleAppAI/src/utils/AppConstants.tsx",
    "content": "export const AppConstants = {\n  fcmProviderId: '',\n  apnsProviderId: '',\n  authKey: '',\n  appId: '',\n  region: '',\n  subscriptionType: 'ALL_USERS',\n  versionNumber: 'V5.3.4',\n  webClientId:\n    '',\n  iosClientId:\n    '',\n};\n\nexport const SCREEN_CONSTANTS = {\n  LOGIN: 'Login',\n  APP_CRED: 'AppCredentials',\n  SAMPLE_USER: 'SampleUser',\n  BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator',\n  AGENTS: 'Agents',\n  MESSAGES: 'Messages',\n} as const;\n"
  },
  {
    "path": "examples/SampleAppAI/src/utils/CommonUtils.ts",
    "content": "export class CommonUtils {\n  static clone<T extends any>(arg: T): T {\n    /*\n    If there are additional properties attached to a function or an array object other than the standard properties, those properties will be ignored\n    Cannot copy private properties (those that start with a \"#\" symbol inside a class block)\n    Functions are copied by reference\n    */\n    if (typeof arg !== 'object' || !arg) {\n      return arg;\n    }\n    let res;\n    if (Array.isArray(arg)) {\n      // arg is an array, there's no hatch to fool the Array.isArray method, so lets create an array\n      res = [];\n      for (const value of arg) {\n        res.push(CommonUtils.clone(value));\n      }\n      return res as T;\n    } else {\n      // arg is an object\n      res = {};\n      const descriptor = Object.getOwnPropertyDescriptors(arg);\n      for (const k of Reflect.ownKeys(descriptor)) {\n        const curDescriptor = descriptor[k as any];\n        if (curDescriptor.hasOwnProperty('value')) {\n          // Property is a data property\n          Object.defineProperty(res, k, {\n            ...curDescriptor,\n            value: CommonUtils.clone(curDescriptor['value']),\n          });\n        } else {\n          // Property is an accessor property\n          Object.defineProperty(res, k, curDescriptor);\n        }\n      }\n      Object.setPrototypeOf(res, Object.getPrototypeOf(arg));\n    }\n    return res as T;\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/src/utils/TooltipMenu.tsx",
    "content": "import { Icon, useTheme } from \"@cometchat/chat-uikit-react-native\";\nimport { JSX, useMemo } from \"react\";\nimport {\n  ColorValue,\n  Dimensions,\n  ImageSourcePropType,\n  Modal,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  TouchableWithoutFeedback,\n  View,\n} from \"react-native\";\n\nconst { width: screenWidth, height: screenHeight } = Dimensions.get(\"window\");\n\ntype CometChatTooltipMenuProps = {\n  visible?: boolean;\n  onDismiss?: () => void;\n  onClose?: () => void;\n  event: {\n    nativeEvent: {\n      pageX: number;\n      pageY: number;\n    };\n  };\n  menuItems: {\n    text: string;\n    onPress: () => void;\n    textColor?: ColorValue;\n    iconColor?: ColorValue;\n    icon?: ImageSourcePropType | JSX.Element;\n  }[];\n};\n\nexport const TooltipMenu = (props: CometChatTooltipMenuProps) => {\n  const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;\n  const theme = useTheme();\n\n  const position = useMemo(() => {\n    let x = event.nativeEvent.pageX;\n    let y = event.nativeEvent.pageY;\n    const position: {\n      left?: number;\n      right?: number;\n      top?: number;\n      bottom?: number;\n    } = {};\n    if (x <= screenWidth / 3) {\n      position.left = x + 10;\n    } else {\n      position.right = 12;\n    }\n\n    if (y <= screenHeight / 2) {\n      position.top = y + 20;\n    } else if (y >= screenHeight / 2) {\n      position.bottom = Math.max(screenHeight - y + 10, 40);\n    }\n    return position;\n  }, [event]);\n\n  return (\n    <Modal\n      presentationStyle='overFullScreen'\n      transparent={true}\n      visible={visible}\n      onRequestClose={onClose}\n      onDismiss={onDismiss}\n      animationType='fade'\n    >\n      <TouchableWithoutFeedback onPress={onClose}>\n        <View style={tooltipStyles.overlay}>\n          <View\n            style={[\n              tooltipStyles.menu,\n              position,\n              {\n                backgroundColor: theme.color.background1,\n                borderWidth: 1,\n                borderColor: theme.color.borderLight,\n                borderRadius: theme.spacing.radius.r2,\n                shadowColor: theme.color.neutral900,\n              },\n            ]}\n          >\n            {menuItems.map((item, i) => {\n              return (\n                <TouchableOpacity\n                  key={i} // Ensure each item has a unique key\n                  onPress={() => {\n                    item.onPress();\n                    onClose();\n                  }}\n                  style={[\n                    {\n                      flexDirection: \"row\",\n                      alignItems: \"center\",\n                      paddingVertical: 10,\n                      paddingHorizontal: 16,\n                      gap: 8,\n                      backgroundColor: theme.color.background1,\n                      minWidth: 160,\n                    },\n                    i === 0\n                      ? {\n                          borderTopLeftRadius: theme.spacing.radius.r2,\n                          borderTopRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                    i === menuItems.length - 1\n                      ? {\n                          borderBottomLeftRadius: theme.spacing.radius.r2,\n                          borderBottomRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                  ]}\n                >\n                  <Icon\n                    color={item.iconColor ?? theme.color.textSecondary}\n                    size={theme.spacing.spacing.s6}\n                    icon={item.icon}\n                  />\n                  <Text\n                    style={[\n                      {\n                        color: item.textColor ?? theme.color.textPrimary,\n                        ...theme.typography.heading4.regular,\n                      },\n                    ]}\n                  >\n                    {item.text}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n      </TouchableWithoutFeedback>\n    </Modal>\n  );\n};\n\nconst tooltipStyles = StyleSheet.create({\n  overlay: {\n    flex: 1,\n    backgroundColor: \"transparent\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  menu: {\n    position: \"absolute\",\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.025,\n    shadowRadius: 4,\n    elevation: 3,\n  },\n  menuItem: {\n    fontSize: 16,\n    paddingVertical: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppAI/src/utils/helper.ts",
    "content": "import {Platform, PermissionsAndroid} from 'react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport Ironman from '../assets/icons/ironman.png';\nimport Captainamerica from '../assets/icons/captainamerica.png';\nimport Wolverine from '../assets/icons/wolverine.png';\nimport Spiderman from '../assets/icons/spiderman.png';\nimport Cyclops from '../assets/icons/cyclops.png';\nimport {\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  NavigationContainerRefWithCurrent,\n  StackActions,\n} from '@react-navigation/native';\nimport {RootStackParamList} from '../navigation/types';\nimport {SCREEN_CONSTANTS} from './AppConstants';\n\ninterface Translations {\n  lastSeen: string;\n  minutesAgo: (minutes: number) => string;\n  hoursAgo: (hours: number) => string;\n}\n\ninterface NotifeeData {\n  receiverType?: 'user' | 'group';\n  conversationId?: string;\n  sender?: string;\n  [key: string]: any;\n}\n\n/**\n * Request common Android permissions (notifications, camera, etc.)\n * Only needed on Android.\n */\nexport async function requestAndroidPermissions() {\n  if (Platform.OS !== 'android') return;\n  try {\n    await PermissionsAndroid.requestMultiple([\n      PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.CAMERA,\n      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,\n      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,\n    ]);\n  } catch (err) {\n    console.warn('Android permissions error:', err);\n  }\n}\n\n/**\n * getLastSeenTime UserInfoSection.\n */\nexport function getLastSeenTime(\n  timestamp: number | null,\n  translations: Translations,\n): string {\n  if (timestamp === null) {\n    return `${translations.lastSeen} Unknown`;\n  }\n\n  // If timestamp is in seconds (length = 10), convert to milliseconds.\n  if (String(timestamp).length === 10) {\n    timestamp *= 1000;\n  }\n\n  const now = new Date();\n  const lastSeen = new Date(timestamp);\n\n  // Calculate the time differences\n  const diffInMillis = now.getTime() - lastSeen.getTime();\n  const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));\n  const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));\n\n  // Check if within last hour\n  if (diffInMinutes === 0) {\n    return `${translations.lastSeen} ${translations.minutesAgo(1)}`;\n  } else if (diffInMinutes < 60) {\n    return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;\n  }\n\n  // Check if within the last 24 hours\n  if (diffInHours < 24) {\n    return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;\n  }\n\n  // Determine if timestamp is within the current year\n  const isSameYear = lastSeen.getFullYear() === now.getFullYear();\n\n  // Options for date formatting\n  const dateOptions: Intl.DateTimeFormatOptions = {\n    day: '2-digit',\n    month: 'short',\n    ...(isSameYear ? {} : {year: 'numeric'}),\n  };\n\n  // Options for time formatting\n  const timeOptions: Intl.DateTimeFormatOptions = {\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: true,\n  };\n\n  const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);\n  const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);\n  if (formattedDate === 'Invalid Date' || formattedTime === 'Invalid Date') {\n    return `Offline`;\n  }\n\n  return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;\n}\n\n/**\n * UNBLOCK\n */\nexport const unblock = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n  setUserObj: React.Dispatch<React.SetStateAction<CometChat.User>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.unblockUsers([uid]);\n    const unBlockedUser = await CometChat.getUser(uid);\n    if (response) {\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, {\n        user: unBlockedUser,\n      });\n      setBlocked(false);\n      setUserObj(unBlockedUser);\n    } else {\n      console.log(\n        `Failed to unblock user with UID ${uid}. Response:`,\n        response,\n      );\n    }\n  } catch (error) {\n    console.error('Error unblocking user:', error);\n  }\n};\n\n/**\n * BLOCK\n */\nexport const blockUser = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.blockUsers([uid]);\n    if (response) {\n      user.setBlockedByMe(true);\n      setBlocked(true);\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, {\n        user,\n      });\n    } else {\n      console.log(`Failed to block user with UID ${uid}. Response:`, response);\n    }\n  } catch (error) {\n    console.error('Error blocking user:', error);\n  }\n};\n\n/**\n * LEAVE GROUP\n */\nexport const leaveGroup = (\n  group: CometChat.Group,\n  navigation: any,\n  pop: number,\n) => {\n  if (group) {\n    const groupID = group.getGuid();\n    CometChat.leaveGroup(groupID).then(\n      () => {\n        let actionMessage: CometChat.Action = new CometChat.Action(\n          groupID,\n          CometChat.MESSAGE_TYPE.TEXT,\n          CometChat.RECEIVER_TYPE.GROUP,\n          CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n        );\n        actionMessage.setMessage(\n          `${CometChatUIKit.loggedInUser!.getName()} has left`,\n        );\n        // Initialize data to prevent crash when SDK accesses getData().metadata during render\n        actionMessage.setData({ metadata: {} });\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          message: actionMessage, //Note: Add Action message after discussion\n          leftUser: CometChatUIKit.loggedInUser,\n          leftGroup: group,\n        });\n        navigation.pop(pop);\n      },\n      error => {\n        console.log('Group leaving failed:', error);\n      },\n    );\n  } else {\n    console.log('Group is not defined');\n  }\n};\n\n/**\n * Sample Users Data\n */\nexport const sampleData = {\n  users: [\n    {uid: 'superhero1', name: 'Iron Man', avatar: Ironman},\n    {uid: 'superhero2', name: 'Captain America', avatar: Captainamerica},\n    {uid: 'superhero3', name: 'Spiderman', avatar: Spiderman},\n    {uid: 'superhero4', name: 'Wolverine', avatar: Wolverine},\n    {uid: 'superhero5', name: 'Cyclops', avatar: Cyclops},\n  ],\n};\n\n/**\n * Navigate to conversation based on notification data.\n */\nexport async function navigateToConversation(\n  navigationRef: NavigationContainerRefWithCurrent<RootStackParamList>,\n  data?: NotifeeData,\n) {\n  if (!data) return;\n  if (!navigationRef.current) return;\n  try {\n    // Handle group\n    if (data.receiverType === 'group') {\n      const extractedId =\n        typeof data.conversationId === 'string'\n          ? data.conversationId.split('_').slice(1).join('_')\n          : '';\n      const group = await CometChat.getGroup(extractedId);\n\n      navigationRef.current?.dispatch(StackActions.push(SCREEN_CONSTANTS.MESSAGES, {group}));\n    }\n\n    // Handle user\n    else if (data.receiverType === 'user') {\n      const ccUser = await CometChat.getUser(data.sender);\n\n      navigationRef.current?.dispatch(\n        StackActions.push(SCREEN_CONSTANTS.MESSAGES, {user: ccUser}),\n      );\n    }\n  } catch (error) {\n    console.log('Error in navigateToConversation:', error);\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppAI/tsconfig.json",
    "content": "{\n  \"extends\": \"@react-native/typescript-config\",\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"**/node_modules\", \"**/Pods\"]\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/.gitattributes",
    "content": "*.pbxproj -text\n"
  },
  {
    "path": "examples/SampleAppExpo/.gitignore",
    "content": "# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files\n\n# dependencies\nnode_modules/\n\n# Expo\n.expo/\ndist/\nweb-build/\nexpo-env.d.ts\n\n# Native\n*.orig.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n\n# Metro\n.metro-health-check*\n\n# debug\nnpm-debug.*\nyarn-debug.*\nyarn-error.*\n\n# macOS\n.DS_Store\n*.pem\n\n# local env files\n.env*.local\n\n# typescript\n*.tsbuildinfo\n"
  },
  {
    "path": "examples/SampleAppExpo/App.tsx",
    "content": "import \"./gesture-handler\";\nimport React, { useState, useEffect, useRef } from \"react\";\nimport {\n  Platform,\n  View,\n  PlatformColor,\n  AppState,\n  AppStateStatus,\n} from \"react-native\";\nimport { enableScreens } from \"react-native-screens\";\nenableScreens();\nimport {\n  CometChatI18nProvider,\n  CometChatIncomingCall,\n  CometChatTheme,\n  CometChatThemeProvider,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  UIKitSettings,\n} from \"@cometchat/chat-uikit-react-native\";\n\nimport { SafeAreaProvider, SafeAreaView } from \"react-native-safe-area-context\";\nimport { GestureHandlerRootView } from 'react-native-gesture-handler';\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport RootStackNavigator from \"./src/navigation/RootStackNavigator\";\nimport { AppConstants } from \"./src/utils/AppConstants\";\nimport { requestAndroidPermissions } from \"./src/utils/helper\";\nimport AsyncStorage from \"@react-native-async-storage/async-storage\";\nimport { ActiveChatProvider } from \"./src/utils/ActiveChatContext\";\nimport { useConfig } from './src/config/store';\nimport { DeepPartial } from '@cometchat/chat-uikit-react-native/src/shared/helper/types';\nimport { createTypography } from './src/utils/themeTypography';\n\n// Listener ID for registering and removing CometChat listeners.\nconst listenerId = \"app\";\n\nconst App = (): React.ReactElement => {\n  const [callReceived, setCallReceived] = useState(false);\n  const incomingCall = useRef<CometChat.Call | CometChat.CustomMessage | null>(\n    null\n  );\n  const [isInitializing, setIsInitializing] = useState(true);\n  const [isLoggedIn, setIsLoggedIn] = useState(false);\n  const [userLoggedIn, setUserLoggedIn] = useState(false);\n  const [currentToken, setCurrentToken] = useState(\"\");\n  const [isTokenRegistered, setIsTokenRegistered] = useState(false);\n  const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);\n\n  const styleConfig = useConfig(state => state?.settings?.style);\n  const theme : { light:  DeepPartial<CometChatTheme>; dark: DeepPartial<CometChatTheme> } = {\n  light: {\n    color: {\n      primary: styleConfig.color.brandColor,\n      textPrimary: styleConfig.color.primaryTextLight,\n      textSecondary: styleConfig.color.secondaryTextLight,\n    },\n    typography: createTypography(styleConfig.typography.font),\n  },\n  dark: {\n    color: {\n      primary: styleConfig.color.brandColor,\n      textPrimary: styleConfig.color.primaryTextDark,\n      textSecondary: styleConfig.color.secondaryTextDark,\n    },\n    typography: createTypography(styleConfig.typography.font),\n  },\n};\n\n  /**\n   * Initialize CometChat UIKit and configure Google Sign-In.\n   * Retrieves credentials from AsyncStorage and uses fallback constants if needed.\n   */\n  useEffect(() => {\n    async function init() {\n      try {\n        // Retrieve stored app credentials or default to an empty object.\n        const AppData = (await AsyncStorage.getItem(\"appCredentials\")) || \"{}\";\n        const storedCredentials = JSON.parse(AppData);\n\n        // Determine the final credentials (from AsyncStorage or AppConstants).\n        const finalAppId = storedCredentials.appId || AppConstants.appId;\n        const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;\n        const finalRegion = storedCredentials.region || AppConstants.region;\n\n        // Set hasValidAppCredentials based on whether all values are available.\n        if (finalAppId && finalAuthKey && finalRegion) {\n          setHasValidAppCredentials(true);\n        } else {\n          setHasValidAppCredentials(false);\n        }\n\n        await CometChatUIKit.init({\n          appId: finalAppId,\n          authKey: finalAuthKey,\n          region: finalRegion,\n          subscriptionType: CometChat.AppSettings\n            .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings[\"subscriptionType\"],\n        });\n\n        // If a user is already logged in, update the state.\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        if (loggedInUser) {\n          setIsLoggedIn(true);\n        }\n      } catch (error) {\n        console.log(\"Error during initialization\", error);\n      } finally {\n        // Mark initialization as complete.\n        setIsInitializing(false);\n      }\n    }\n    init();\n  }, []);\n\n  /**\n   * Monitor app state changes to verify the logged-in status and clear notifications.\n   * When the app becomes active, it cancels Android notifications and checks the login status.\n   */\n  useEffect(() => {\n    if (Platform.OS === \"android\") {\n      // Request required Android permissions for notifications.\n      requestAndroidPermissions();\n    }\n    const handleAppStateChange = async (nextState: AppStateStatus) => {\n      if (nextState === \"active\") {\n        try {\n          // Verify if there is a valid logged-in user.\n          const chatUser = await CometChat.getLoggedinUser();\n          setIsLoggedIn(!!chatUser);\n        } catch (error) {\n          console.log(\"Error verifying CometChat user on resume:\", error);\n        }\n      }\n    };\n    const subscription = AppState.addEventListener(\n      \"change\",\n      handleAppStateChange\n    );\n    return () => subscription.remove();\n  }, []);\n\n  /**\n   * Attach CometChat login listener to handle login and logout events.\n   * Updates user login status accordingly.\n   */\n  useEffect(() => {\n    CometChat.addLoginListener(\n      listenerId,\n      new CometChat.LoginListener({\n        loginSuccess: () => {\n          setUserLoggedIn(true);\n        },\n        loginFailure: (e: CometChat.CometChatException) => {\n          console.log(\"LoginListener :: loginFailure\", e.message);\n        },\n        logoutSuccess: () => {\n          setUserLoggedIn(false);\n        },\n        logoutFailure: (e: CometChat.CometChatException) => {\n          console.log(\"LoginListener :: logoutFailure\", e.message);\n        },\n      })\n    );\n\n    // Clean up the login listener on component unmount.\n    return () => {\n      CometChat.removeLoginListener(listenerId);\n    };\n  }, []);\n\n  /**\n   * Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.\n   * Also handles UI events for call end.\n   */\n  useEffect(() => {\n    // Listener for call events.\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onIncomingCallReceived: (call: CometChat.Call) => {\n          // Check if there's already an active call\n          try {\n            const activeCall = CometChat.getActiveCall();\n            if (activeCall) {\n              // If there's an active call, reject the incoming call with busy status\n              setTimeout(() => {\n                CometChat.rejectCall(\n                  call.getSessionId(),\n                  CometChat.CALL_STATUS.BUSY\n                )\n                  .then(() => {\n                    console.log(\"Incoming call rejected due to active call\");\n                  })\n                  .catch((error) => {\n                    console.error(\n                      \"Error rejecting call with busy status:\",\n                      error\n                    );\n                  });\n              }, 2000);\n            } else {\n              // No active call, proceed with normal incoming call handling\n              // Hide any bottom sheet UI before showing the incoming call screen.\n              CometChatUIEventHandler.emitUIEvent(\n                CometChatUIEvents.ccToggleBottomSheet,\n                {\n                  isBottomSheetVisible: false,\n                }\n              );\n              // Store the incoming call and update state.\n              incomingCall.current = call;\n              setCallReceived(true);\n            }\n          } catch (error) {\n            console.error(\"Error getting active call:\", error);\n            // If error getting active call, proceed with normal handling\n            CometChatUIEventHandler.emitUIEvent(\n              CometChatUIEvents.ccToggleBottomSheet,\n              {\n                isBottomSheetVisible: false,\n              }\n            );\n            incomingCall.current = call;\n            setCallReceived(true);\n          }\n        },\n        onOutgoingCallRejected: () => {\n          // Clear the call state if outgoing call is rejected.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n        onIncomingCallCancelled: () => {\n          // Clear the call state if the incoming call is cancelled.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n      })\n    );\n\n    // Additional listener to handle call end events.\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallEnded: () => {\n        incomingCall.current = null;\n        setCallReceived(false);\n      },\n    });\n\n    // Remove call listeners on cleanup.\n    return () => {\n      CometChatUIEventHandler.removeCallListener(listenerId);\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [userLoggedIn]);\n\n  // Show a blank/splash screen while the app is initializing.\n  if (isInitializing) {\n    return (\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: Platform.select({\n            ios: PlatformColor(\"systemBackgroundColor\"),\n            android: PlatformColor(\"?android:attr/colorBackground\"),\n          }),\n        }}\n      />\n    );\n  }\n\n  // Once initialization is complete, render the main app UI.\n  return (\n    <GestureHandlerRootView style={{ flex: 1 }}>\n    <SafeAreaProvider>\n      {/* Render the incoming call UI if the user is logged in and a call is received */}\n      <ActiveChatProvider>\n        <CometChatThemeProvider theme={theme}>\n          <CometChatI18nProvider>\n              {isLoggedIn && callReceived && incomingCall.current ? (\n                <CometChatIncomingCall\n                  call={incomingCall.current}\n                  onDecline={() => {\n                    // Handle call decline by clearing the incoming call state.\n                    incomingCall.current = null;\n                    setCallReceived(false);\n                  }}\n                />\n              ) : null}\n            <SafeAreaView edges={['top', 'bottom']} style={{ flex: 1 }}>\n              {/* Render the main navigation stack, passing the login status as a prop */}\n              <RootStackNavigator\n                isLoggedIn={isLoggedIn}\n                hasValidAppCredentials={hasValidAppCredentials}\n              />\n            </SafeAreaView>\n          </CometChatI18nProvider>\n        </CometChatThemeProvider>\n      </ActiveChatProvider>\n    </SafeAreaProvider>\n    </GestureHandlerRootView>\n  );\n};\n\nexport default App;\n"
  },
  {
    "path": "examples/SampleAppExpo/README.md",
    "content": "<p align=\"center\">\n  <img alt=\"CometChat\" src=\"https://assets.cometchat.io/website/images/logos/banner.png\">\n</p>\n\n# React Native Sample App Expo by CometChat\n\nThis is a reference application showcasing the integration of [CometChat's React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) in a React Native Expo project. It demonstrates how to implement real-time messaging and voice/video calling features with ease in your React Native Expo App.\n\n<div style=\"display: flex; align-items: center; justify-content: center\">\n   <img src=\"../../screenshots/overview_cometchat_screens.png\" />\n</div>\n\n\n## Prerequisites\n\nSign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_\n\n- **Node.js** 18 or higher\n- **React Native** Version 0.77 or later (up to the latest version) \n\n**iOS**\n- XCode\n- Pod (CocoaPods) for iOS\n- An iOS device or emulator with iOS 12.0 or above.\n- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.\n\n**Android**\n- Android Studio\n- Android device or emulator with Android version 5.0 or above.\n\n\n## Installation\n\n1. Clone the repository:\n   ```sh\n   git clone https://github.com/cometchat/cometchat-uikit-react-native.git\n   ```\n\n1. Change into the specific app's directory (e.g., SampleApp).\n   ```sh\n     cd examples/SampleAppExpo\n   ```\n1. Run `npm install` to install the dependencies in the root folder.\n\n1. `[Optional]` Configure CometChat credentials:\n    - Open the `AppConstants.tsx` file located at `examples/SampleAppExpo/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:\n      ```ts\n      export const AppConstants = {\n          appId: 'YOUR_APP_ID',\n          authKey: 'YOUR_AUTH_KEY',\n          region: 'REGION',\n          //other properties\n      }\n      ```\n\n1. Generate the native Android and iOS project folders for the Expo app. This step is required for the \"custom dev client\" and \"bare workflow\" pipeline:\n   ```sh\n    npx expo prebuild\n   ```\n\n1. Run the app on a connected device or emulator:\n   ```sh\n    npx expo run:android\n    npx expo run:ios\n   ```\n\n\n> **⚠️ Compatibility:**  \n> The React Native UI Kit is **not compatible** with Expo Go because it requires custom native modules. Expo Go is not recommended for production-grade apps.  \n> **Development Builds:** To use the UI Kit in an Expo app, you must use development builds. Refer to the official Expo guide for more details.\n\n   \n## Help and Support\n\nFor issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/)."
  },
  {
    "path": "examples/SampleAppExpo/app.json",
    "content": "{\n  \"expo\": {\n    \"name\": \"SampleAppExpo\",\n    \"slug\": \"SampleAppExpo\",\n    \"version\": \"5.3.4\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/icon.png\",\n    \"newArchEnabled\": true,\n    \"userInterfaceStyle\": \"automatic\",\n    \"splash\": {\n      \"image\": \"./assets/splash-icon.png\",\n      \"resizeMode\": \"contain\",\n      \"backgroundColor\": \"#ffffff\"\n    },\n    \"ios\": {\n      \"supportsTablet\": true,\n      \"bundleIdentifier\": \"com.cometchat.internal.reactnative.ios\",\n      \"userInterfaceStyle\": \"automatic\",\n      \"infoPlist\": {\n        \"NSCameraUsageDescription\": \"This is for Camera permission\",\n        \"NSMicrophoneUsageDescription\": \"This is for Microphone permission\"\n      }\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"foregroundImage\": \"./assets/adaptive-icon.png\",\n        \"backgroundColor\": \"#ffffff\"\n      },\n      \"configChanges\": [\n        \"keyboard\",\n        \"keyboardHidden\",\n        \"orientation\",\n        \"screenLayout\",\n        \"screenSize\",\n        \"smallestScreenSize\",\n        \"uiMode\",\n        \"fontScale\"\n      ],\n      \"edgeToEdgeEnabled\": true,\n      \"package\": \"com.cometchat.sampleapp.reactnative.android\",\n      \"userInterfaceStyle\": \"automatic\",\n      \"permissions\": [\n        \"android.permission.INTERNET\",\n        \"android.permission.CAMERA\",\n        \"android.permission.MODIFY_AUDIO_SETTINGS\",\n        \"android.permission.RECORD_AUDIO\",\n        \"android.permission.ACCESS_NETWORK_STATE\"\n      ]\n    },\n    \"web\": {\n      \"favicon\": \"./assets/favicon.png\"\n    },\n    \"plugins\": [\n      [\n        \"expo-font\",\n        {\n          \"fonts\": [\n            \"./assets/fonts/inter_regular.ttf\",\n            \"./assets/fonts/inter_medium.ttf\",\n            \"./assets/fonts/inter_bold.ttf\",\n            \"./assets/fonts/roboto_regular.ttf\",\n            \"./assets/fonts/roboto_medium.ttf\",\n            \"./assets/fonts/roboto_bold.ttf\",\n            \"./assets/fonts/times_new_roman_regular.ttf\",\n            \"./assets/fonts/times_new_roman_medium.ttf\",\n            \"./assets/fonts/times_new_roman_bold.ttf\"\n          ]\n        }\n      ]\n    ]\n  }\n}"
  },
  {
    "path": "examples/SampleAppExpo/gesture-handler.js",
    "content": ""
  },
  {
    "path": "examples/SampleAppExpo/gesture-handler.native.js",
    "content": "// Only import react-native-gesture-handler on native platforms\nimport 'react-native-gesture-handler';"
  },
  {
    "path": "examples/SampleAppExpo/index.js",
    "content": "import { registerRootComponent } from 'expo';\n\nimport App from './App';\n\n// registerRootComponent calls AppRegistry.registerComponent('main', () => App);\n// It also ensures that whether you load the app in Expo Go or in a native build,\n// the environment is set up appropriately\nregisterRootComponent(App);\n"
  },
  {
    "path": "examples/SampleAppExpo/metro.config.js",
    "content": "// Learn more https://docs.expo.io/guides/customizing-metro\nconst { getDefaultConfig } = require('expo/metro-config');\n\n/** @type {import('expo/metro-config').MetroConfig} */\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = config;\n"
  },
  {
    "path": "examples/SampleAppExpo/package.json",
    "content": "{\n  \"name\": \"sampleappexpo\",\n  \"version\": \"5.3.4\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"expo start --dev-client\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@cometchat/calls-sdk-react-native\": \"^4.4.0\",\n    \"@cometchat/chat-sdk-react-native\": \"^4.0.21\",\n    \"@cometchat/chat-uikit-react-native\": \"^5.3.4\",\n    \"@react-native-async-storage/async-storage\": \"^2.2.0\",\n    \"@react-native-clipboard/clipboard\": \"^1.16.3\",\n    \"@react-native-community/datetimepicker\": \"^8.4.5\",\n    \"@react-native-community/netinfo\": \"^11.4.1\",\n    \"@react-navigation/bottom-tabs\": \"^7.4.7\",\n    \"@react-navigation/native\": \"^7.1.18\",\n    \"@react-navigation/native-stack\": \"^7.14.12\",\n    \"@react-navigation/stack\": \"^7.4.8\",\n    \"dayjs\": \"^1.11.18\",\n    \"expo\": \"~54.0.12\",\n    \"expo-audio\": \"^1.0.13\",\n    \"expo-camera\": \"^17.0.8\",\n    \"expo-dev-client\": \"^6.0.13\",\n    \"expo-font\": \"~14.0.9\",\n    \"expo-navigation-bar\": \"^5.0.8\",\n    \"expo-status-bar\": \"~3.0.8\",\n    \"expo-system-ui\": \"^6.0.7\",\n    \"react\": \"19.1.0\",\n    \"react-native\": \"0.81.4\",\n    \"react-native-background-timer\": \"^2.4.1\",\n    \"react-native-callstats\": \"^3.73.22\",\n    \"react-native-gesture-handler\": \"^2.28.0\",\n    \"react-native-localize\": \"^3.5.3\",\n    \"react-native-safe-area-context\": \"^5.6.1\",\n    \"react-native-screens\": \"^4.16.0\",\n    \"react-native-svg\": \"^15.13.0\",\n    \"react-native-video\": \"^6.16.1\",\n    \"react-native-vision-camera\": \"^4.7.2\",\n    \"react-native-webrtc\": \"^124.0.7\",\n    \"zustand\": \"^5.0.8\"\n  },\n  \"private\": true,\n  \"devDependencies\": {\n    \"@types/react\": \"~19.1.10\",\n    \"typescript\": \"~5.9.2\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/AccountCircle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.743 17.438q1.485-1.047 3-1.609a9.3 9.3 0 0 1 3.256-.562q1.742 0 3.26.562 1.52.563 3.02 1.608 1.046-1.262 1.508-2.601a8.6 8.6 0 0 0 .462-2.835q0-3.497-2.377-5.874T12 3.75 6.126 6.127Q3.75 8.505 3.75 12.001q0 1.496.471 2.835.471 1.34 1.523 2.601m6.255-4.638q-1.44 0-2.428-.989-.987-.989-.987-2.429t.988-2.428 2.43-.987 2.428.989q.987.988.987 2.428t-.989 2.428-2.429.988m.005 9.033a9.5 9.5 0 0 1-3.822-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.124 9.5 9.5 0 0 1-.777-3.824q0-2.026.777-3.817a10 10 0 0 1 2.113-3.125 10 10 0 0 1 3.124-2.11 9.5 9.5 0 0 1 3.824-.776q2.025 0 3.818.777a10 10 0 0 1 3.124 2.113 10 10 0 0 1 2.11 3.125 9.5 9.5 0 0 1 .776 3.814q0 2.027-.777 3.822a10 10 0 0 1-2.112 3.129 10 10 0 0 1-3.126 2.11 9.5 9.5 0 0 1-3.814.776m-.004-1.583q1.342 0 2.586-.387 1.244-.388 2.447-1.296a10.3 10.3 0 0 0-2.455-1.277A7.7 7.7 0 0 0 12 16.85a7.9 7.9 0 0 0-2.584.433q-1.26.434-2.451 1.284 1.204.907 2.447 1.296A8.6 8.6 0 0 0 12 20.25m0-9.033q.795 0 1.315-.519.518-.519.518-1.315t-.518-1.314-1.315-.519-1.314.519-.519 1.314.519 1.315 1.314.519'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/AddComment.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.212 10.783v2.338q0 .333.228.564.228.232.562.232t.564-.232a.77.77 0 0 0 .229-.564v-2.338h2.338q.333 0 .564-.232a.77.77 0 0 0 .232-.562.76.76 0 0 0-.232-.56.77.77 0 0 0-.564-.229h-2.338V6.854a.76.76 0 0 0-.232-.556.77.77 0 0 0-.567-.231.75.75 0 0 0-.56.231.77.77 0 0 0-.224.556V9.2H8.866a.75.75 0 0 0-.56.234.78.78 0 0 0-.227.56q0 .327.227.558a.76.76 0 0 0 .56.231zm-5.15 7.067L3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/AiIcon.tsx",
    "content": "import Svg, { Path, Defs, LinearGradient, Stop } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst AiIcon = ({ width = 24, height = 24 }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 14 14\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M4.06663 7.53366L4.485 8.37042C4.6266 8.6536 4.6974 8.7952 4.79198 8.9179C4.8759 9.02678 4.97351 9.12438 5.08239 9.20831C5.20509 9.30289 5.34668 9.37369 5.62987 9.51528L6.46663 9.93366L5.62987 10.352C5.34668 10.4936 5.20509 10.5644 5.08239 10.659C4.97351 10.7429 4.8759 10.8405 4.79198 10.9494C4.6974 11.0721 4.6266 11.2137 4.485 11.4969L4.06663 12.3337L3.64825 11.4969C3.50665 11.2137 3.43586 11.0721 3.34128 10.9494C3.25735 10.8405 3.15974 10.7429 3.05086 10.659C2.92816 10.5644 2.78657 10.4936 2.50338 10.352L1.66663 9.93366L2.50338 9.51528C2.78657 9.37369 2.92816 9.30289 3.05086 9.20831C3.15974 9.12438 3.25735 9.02678 3.34128 8.9179C3.43586 8.7952 3.50665 8.6536 3.64825 8.37041L4.06663 7.53366Z\"\n      fill=\"url(#paint0_linear)\"\n    />\n    <Path\n      d=\"M8.59996 1.66699L9.22856 3.30135C9.37896 3.6924 9.45417 3.88793 9.57111 4.0524C9.67476 4.19817 9.80212 4.32552 9.94788 4.42917C10.1124 4.54612 10.3079 4.62132 10.6989 4.77173L12.3333 5.40033L10.6989 6.02892C10.3079 6.17933 10.1124 6.25453 9.94788 6.37148C9.80212 6.47513 9.67476 6.60248 9.57111 6.74825C9.45417 6.91272 9.37896 7.10825 9.22856 7.4993L8.59996 9.13366L7.97136 7.4993C7.82095 7.10825 7.74575 6.91272 7.62881 6.74825C7.52516 6.60248 7.3978 6.47513 7.25204 6.37148C7.08757 6.25453 6.89204 6.17933 6.50098 6.02892L4.86663 5.40033L6.50098 4.77173C6.89204 4.62132 7.08757 4.54612 7.25203 4.42917C7.3978 4.32552 7.52516 4.19817 7.62881 4.0524C7.74575 3.88793 7.82095 3.6924 7.97136 3.30135L8.59996 1.66699Z\"\n      fill=\"url(#paint1_linear)\"\n    />\n    <Defs>\n      <LinearGradient\n        id=\"paint0_linear\"\n        x1=\"6.99996\"\n        y1=\"1.66699\"\n        x2=\"6.99996\"\n        y2=\"19.2003\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <Stop stopColor=\"#AD94F2\" />\n        <Stop offset=\"1\" stopColor=\"#3302B8\" />\n      </LinearGradient>\n      <LinearGradient\n        id=\"paint1_linear\"\n        x1=\"6.99996\"\n        y1=\"1.66699\"\n        x2=\"6.99996\"\n        y2=\"19.2003\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <Stop stopColor=\"#AD94F2\" />\n        <Stop offset=\"1\" stopColor=\"#3302B8\" />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\n\nexport default AiIcon;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/ArrowBack.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m7.193 12.787 5.366 5.367a.8.8 0 0 1 .24.563.75.75 0 0 1-.236.554.79.79 0 0 1-.562.243.74.74 0 0 1-.554-.243L4.73 12.554a.75.75 0 0 1-.185-.256.8.8 0 0 1-.057-.298.8.8 0 0 1 .057-.298.8.8 0 0 1 .185-.265l6.717-6.716a.76.76 0 0 1 .552-.234q.318 0 .564.234a.8.8 0 0 1 .238.564q0 .319-.238.557l-5.37 5.362h11.854a.76.76 0 0 1 .558.23.77.77 0 0 1 .229.566.76.76 0 0 1-.229.562.77.77 0 0 1-.558.225z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Block.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.002 21.833a9.6 9.6 0 0 1-3.834-.774 10 10 0 0 1-3.123-2.105 9.9 9.9 0 0 1-2.104-3.12 9.6 9.6 0 0 1-.773-3.833q0-2.043.775-3.835a10 10 0 0 1 2.104-3.122 9.9 9.9 0 0 1 3.12-2.104A9.6 9.6 0 0 1 12 2.166q2.043 0 3.835.775a10 10 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.105 3.12 9.6 9.6 0 0 1 .772 3.833q0 2.043-.774 3.835a10 10 0 0 1-2.104 3.122 9.9 9.9 0 0 1-3.12 2.105 9.6 9.6 0 0 1-3.834.772m0-1.583q1.469 0 2.816-.488a8 8 0 0 0 2.45-1.412L5.655 6.733a8.6 8.6 0 0 0-1.408 2.46A8 8 0 0 0 3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399m6.33-2.983a8.8 8.8 0 0 0 1.407-2.449A7.9 7.9 0 0 0 20.25 12q0-3.452-2.399-5.851t-5.85-2.399q-1.464 0-2.807.496a8.4 8.4 0 0 0-2.46 1.42z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Builder.tsx",
    "content": "import React from \"react\";\nimport Svg, { Path, SvgProps } from \"react-native-svg\";\n\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d=\"M2.30775 17.5C1.81058 17.5 1.385 17.323 1.031 16.969C0.677 16.615 0.5 16.1894 0.5 15.6923V2.30775C0.5 1.81058 0.677 1.385 1.031 1.031C1.385 0.677 1.81058 0.5 2.30775 0.5H15.6923C16.1894 0.5 16.615 0.677 16.969 1.031C17.323 1.385 17.5 1.81058 17.5 2.30775V15.6923C17.5 16.1894 17.323 16.615 16.969 16.969C16.615 17.323 16.1894 17.5 15.6923 17.5H2.30775ZM2.30775 16H15.6923C15.7821 16 15.8558 15.9712 15.9135 15.9135C15.9712 15.8558 16 15.7821 16 15.6923V4H2V15.6923C2 15.7821 2.02883 15.8558 2.0865 15.9135C2.14417 15.9712 2.21792 16 2.30775 16ZM9 13.5C7.77433 13.5 6.67533 13.1757 5.703 12.527C4.7305 11.8782 4.01217 11.0358 3.548 10C4.01217 8.96417 4.7305 8.12183 5.703 7.473C6.67533 6.82433 7.77433 6.5 9 6.5C10.2257 6.5 11.3247 6.82433 12.297 7.473C13.2695 8.12183 13.9878 8.96417 14.452 10C13.9878 11.0358 13.2695 11.8782 12.297 12.527C11.3247 13.1757 10.2257 13.5 9 13.5ZM9 12.3077C9.86283 12.3077 10.6568 12.1045 11.3818 11.698C12.1068 11.2917 12.6955 10.7257 13.148 10C12.6955 9.27433 12.1068 8.70833 11.3818 8.302C10.6568 7.8955 9.86283 7.69225 9 7.69225C8.13717 7.69225 7.34325 7.8955 6.61825 8.302C5.89325 8.70833 5.3045 9.27433 4.852 10C5.3045 10.7257 5.89325 11.2917 6.61825 11.698C7.34325 12.1045 8.13717 12.3077 9 12.3077ZM9.00225 11.3077C9.36608 11.3077 9.67458 11.1803 9.92775 10.9255C10.1811 10.6708 10.3077 10.3616 10.3077 9.99775C10.3077 9.63392 10.1803 9.32542 9.9255 9.07225C9.67083 8.81892 9.36158 8.69225 8.99775 8.69225C8.63392 8.69225 8.32542 8.81967 8.07225 9.0745C7.81892 9.32917 7.69225 9.63842 7.69225 10.0022C7.69225 10.3661 7.81967 10.6746 8.0745 10.9277C8.32917 11.1811 8.63842 11.3077 9.00225 11.3077Z\"\n    />\n  </Svg>\n);\n\nexport default SvgComponent;"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/CallFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Chat.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.062 17.85 3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275zm1.5-2.334h6.15a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.229h-6.15a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.557q0 .335.232.564a.77.77 0 0 0 .564.23m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.23H6.895a.77.77 0 0 0-.564.234.76.76 0 0 0-.232.557q0 .335.232.564.231.23.564.229m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.563.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.229H6.895a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.558q0 .334.232.564.231.229.564.229'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Chatfill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.039 17.5-2.002 2.002q-.427.426-.982.192-.555-.235-.555-.84V4.308q0-.758.525-1.283T4.308 2.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zM7 13.75h6q.319 0 .534-.216A.73.73 0 0 0 13.75 13a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 13.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 10a.73.73 0 0 0-.216-.534A.73.73 0 0 0 17 9.25H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 10.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 7a.73.73 0 0 0-.216-.535A.73.73 0 0 0 17 6.25H7a.73.73 0 0 0-.534.216A.73.73 0 0 0 6.25 7q0 .32.216.535A.73.73 0 0 0 7 7.75'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/CheckFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m9.551 15.516 8.639-8.639a.73.73 0 0 1 .522-.228q.299-.005.532.228a.74.74 0 0 1 .232.535q0 .302-.232.534l-9.06 9.075a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.27l-4.175-4.176a.71.71 0 0 1-.22-.53.75.75 0 0 1 .236-.539.74.74 0 0 1 .534-.233q.303 0 .535.233z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Close.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 13.117 6.895 18.22a.763.763 0 0 1-1.117-.004.766.766 0 0 1 .004-1.1l5.1-5.117-5.1-5.117a.74.74 0 0 1-.229-.544q0-.315.23-.556a.74.74 0 0 1 .55-.244.78.78 0 0 1 .562.232l5.104 5.112 5.112-5.112a.75.75 0 0 1 .555-.232.78.78 0 0 1 .562.244.76.76 0 0 1 .223.556.77.77 0 0 1-.235.544L13.116 12l5.1 5.117a.74.74 0 0 1 .229.543q0 .314-.23.557a.74.74 0 0 1-.55.243.74.74 0 0 1-.553-.24z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Delete.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.734 20.833q-.655 0-1.12-.464a1.53 1.53 0 0 1-.464-1.119V5.55h-.204a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.562q0-.33.232-.56a.77.77 0 0 1 .564-.23h3.89v-.012a.76.76 0 0 1 .23-.558.77.77 0 0 1 .564-.23h4.766q.324 0 .556.232.231.232.232.556v.013h3.9q.32 0 .552.232a.76.76 0 0 1 .231.558.76.76 0 0 1-.232.564.76.76 0 0 1-.555.229h-.213v13.7q0 .655-.464 1.119-.465.464-1.12.464zM17.284 5.55H6.734v13.7h10.55zm-7.29 11.62a.77.77 0 0 0 .559-.226.76.76 0 0 0 .23-.56V8.4a.77.77 0 0 0-.232-.565.76.76 0 0 0-.558-.23.76.76 0 0 0-.563.23.77.77 0 0 0-.23.565v7.983q0 .335.234.56a.78.78 0 0 0 .56.228m4.034 0a.77.77 0 0 0 .558-.226.76.76 0 0 0 .231-.56V8.4a.77.77 0 0 0-.233-.565.76.76 0 0 0-.557-.23.76.76 0 0 0-.564.23.77.77 0 0 0-.23.565v7.983q0 .335.235.56a.78.78 0 0 0 .56.228'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Flash.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ width = 24, height = 24, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M18 2H6V8L8 11V22H16V11L18 8V2ZM16 4V5H8V4H16ZM14 10.4V20H10V10.39L8 7.39V7H16V7.39L14 10.4Z\"\n      fill={color || \"#F9F8FD\"}\n    />\n    <Path\n      d=\"M12 15.5C12.8284 15.5 13.5 14.8284 13.5 14C13.5 13.1716 12.8284 12.5 12 12.5C11.1716 12.5 10.5 13.1716 10.5 14C10.5 14.8284 11.1716 15.5 12 15.5Z\"\n      fill={color || \"#F9F8FD\"}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Group.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M1.21 17.381q0-.85.431-1.54.43-.69 1.2-1.04 1.72-.776 3.2-1.137 1.478-.36 3.024-.36t3.013.36q1.468.361 3.186 1.136.771.35 1.211 1.04t.44 1.541v.819q0 .653-.465 1.118t-1.118.465H2.794q-.66 0-1.122-.465a1.53 1.53 0 0 1-.461-1.118zm20 2.402h-3.1q.159-.383.274-.77.114-.386.114-.813v-.817q0-1.41-.654-2.384-.654-.975-1.979-1.628 1.537.191 2.89.535 1.351.344 2.29.831.82.463 1.285 1.121.464.66.464 1.473v.865q0 .66-.465 1.123-.465.464-1.118.464M9.066 12q-1.62 0-2.677-1.056T5.332 8.267 6.388 5.59t2.677-1.057q1.62 0 2.677 1.057t1.056 2.677-1.056 2.677Q10.685 12 9.065 12m9.033-3.743q0 1.61-1.056 2.67t-2.677 1.06q-.255 0-.596-.042a3 3 0 0 1-.604-.13 4.4 4.4 0 0 0 .944-1.565 5.9 5.9 0 0 0 .323-1.986q0-1.08-.323-1.962a5.2 5.2 0 0 0-.944-1.602q.271-.084.596-.126a5 5 0 0 1 .604-.04q1.62 0 2.677 1.059t1.056 2.664M2.794 18.2h12.538v-.815q0-.364-.209-.69a1.3 1.3 0 0 0-.512-.474q-1.655-.755-2.9-1.044-1.246-.29-2.642-.29-1.4 0-2.662.29-1.262.289-2.909 1.044-.312.15-.508.476-.195.327-.196.686zm6.27-7.783q.922 0 1.536-.613.615-.614.615-1.536t-.613-1.537-1.535-.614-1.537.613-.615 1.535q0 .922.613 1.537t1.535.615'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/GroupAdd.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.596 11.642q.638-.694.944-1.594t.306-1.856-.306-1.856a4.4 4.4 0 0 0-.944-1.594q1.317.151 2.187 1.138t.87 2.312q0 1.327-.87 2.313a3.33 3.33 0 0 1-2.187 1.137m4.863 7.666q.188-.345.288-.733.099-.389.099-.787v-.827q0-.817-.333-1.556a3.6 3.6 0 0 0-.944-1.267q1.15.384 2.117 1.033.967.65.967 1.79v.827q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm2.194-8.558h-1.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.25V8q0-.319.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v1.25h1.25q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .319-.215.534a.73.73 0 0 1-.535.216h-1.25V12q0 .318-.215.534a.73.73 0 0 1-.535.216.72.72 0 0 1-.534-.216.73.73 0 0 1-.216-.534zm-11.307.942q-1.444 0-2.472-1.028T4.846 8.192 5.874 5.72t2.472-1.028 2.472 1.028 1.028 2.472-1.028 2.472-2.472 1.028m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.991-1.09 12.95 12.95 0 0 1 6.087 0q1.509.364 2.991 1.09.667.337 1.066.963.4.625.4 1.36v.704q0 .633-.444 1.076a1.47 1.47 0 0 1-1.075.444H2.364q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/GroupFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M1.797 17.085q0-.774.399-1.38a2.7 2.7 0 0 1 1.066-.944q1.426-.697 2.866-1.075t3.169-.378 3.168.377 2.867 1.076q.666.338 1.066.944.399.606.399 1.38v.703q0 .605-.444 1.062a1.44 1.44 0 0 1-1.076.458H3.316q-.633 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076zm16.613 2.223q.189-.345.288-.733t.099-.787v-.826q0-.985-.482-1.877a4.54 4.54 0 0 0-1.368-1.531q1.005.15 1.91.464.903.315 1.724.743.775.414 1.197.975t.422 1.226v.826q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm-9.113-7.616q-1.444 0-2.472-1.028T5.797 8.192q0-1.443 1.028-2.471t2.472-1.029 2.472 1.029 1.028 2.471-1.028 2.472q-1.029 1.028-2.472 1.028m8.634-3.5q0 1.444-1.028 2.472t-2.472 1.028a3 3 0 0 1-.43-.038 4 4 0 0 1-.431-.085q.59-.711.91-1.578a5.2 5.2 0 0 0-.007-3.593 5.8 5.8 0 0 0-.903-1.582q.215-.078.43-.1.216-.024.431-.024 1.444 0 2.472 1.029t1.028 2.471'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Info.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/InfoIcon.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Logout.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h5.952q.318 0 .534.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.952q.318 0 .534.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215zm12.31-7.75H9.845a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h7.771l-1.923-1.923a.7.7 0 0 1-.212-.507.74.74 0 0 1 .212-.531.72.72 0 0 1 .527-.241.72.72 0 0 1 .543.225l3.094 3.094q.27.271.27.633 0 .361-.27.633l-3.094 3.094a.71.71 0 0 1-.53.22.75.75 0 0 1-.54-.236.73.73 0 0 1-.21-.534.74.74 0 0 1 .226-.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Person.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06q1.628 0 2.689 1.06 1.06 1.06 1.06 2.69t-1.06 2.69-2.69 1.06m-7.85 6.2v-.817q0-.9.451-1.568a2.9 2.9 0 0 1 1.185-1.018 18 18 0 0 1 3.14-1.116 12.8 12.8 0 0 1 3.073-.377q1.545 0 3.065.381a18.4 18.4 0 0 1 3.134 1.118q.75.348 1.201 1.012.45.664.45 1.568v.821q0 .648-.465 1.114-.465.465-1.118.465H5.732q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.533v-.814q0-.366-.208-.686a1.36 1.36 0 0 0-.513-.48q-1.512-.728-2.831-1.03A12 12 0 0 0 12 14.887q-1.403 0-2.732.303-1.328.302-2.825 1.03-.312.16-.512.481-.2.322-.2.686zm6.266-7.783q.925 0 1.546-.621t.621-1.546-.62-1.546-1.547-.62-1.545.62-.621 1.546.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/PersonAdd.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.13 10.817h-2.345a.75.75 0 0 1-.56-.234.78.78 0 0 1-.228-.561q0-.327.227-.558a.76.76 0 0 1 .56-.23h2.347V6.887q0-.325.227-.556a.76.76 0 0 1 .563-.232q.334 0 .564.232t.229.556v2.345h2.337q.333 0 .565.233a.76.76 0 0 1 .231.558q0 .334-.231.564a.77.77 0 0 1-.565.229h-2.337v2.337q0 .333-.232.565a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.565zM9.019 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06 2.69 1.06 1.06 2.69-1.06 2.69T9.018 12m-7.85 6.2v-.818q0-.85.427-1.539a2.67 2.67 0 0 1 1.21-1.04q1.746-.795 3.22-1.147a12.8 12.8 0 0 1 5.973 0q1.467.352 3.22 1.144.78.363 1.215 1.046a2.8 2.8 0 0 1 .435 1.537v.821q0 .648-.465 1.114-.465.465-1.118.465H2.75q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.534v-.813q0-.364-.196-.686a1.23 1.23 0 0 0-.525-.48q-1.63-.763-2.882-1.048a12 12 0 0 0-2.662-.286q-1.419 0-2.677.286t-2.88 1.048q-.337.158-.524.479a1.33 1.33 0 0 0-.188.683zm6.267-7.783q.925 0 1.546-.621.62-.621.62-1.546t-.62-1.546q-.622-.62-1.546-.62-.925 0-1.546.62-.62.621-.62 1.546t.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/PersonFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 11.692q-1.448 0-2.474-1.026A3.37 3.37 0 0 1 8.5 8.192q0-1.448 1.026-2.474A3.37 3.37 0 0 1 12 4.692q1.448 0 2.474 1.026A3.37 3.37 0 0 1 15.5 8.192q0 1.449-1.026 2.474A3.37 3.37 0 0 1 12 11.692m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.992-1.09 12.95 12.95 0 0 1 6.086 0q1.509.364 2.992 1.09.667.337 1.066.963t.399 1.36v.704q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444H6.019q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/PersonOff.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill={color ?? \"#none\"}\n    viewBox=\"0 0 24 24\"\n  >\n    <Path\n    fill={color ?? \"#000\"}\n      d=\"m18.777 20.6-2.108-2.092H6.622q-.552 0-.937-.385a1.27 1.27 0 0 1-.385-.935v-.404q0-.529.274-.982a2.1 2.1 0 0 1 .766-.74q1.171-.68 2.449-1.07 1.275-.39 2.61-.448h.158q.081 0 .159.01L3.385 5.223a.6.6 0 0 1-.195-.45.66.66 0 0 1 .21-.47.64.64 0 0 1 .464-.207q.255 0 .455.208l15.375 15.39a.65.65 0 0 1 .205.454.6.6 0 0 1-.203.452.62.62 0 0 1-.455.208.64.64 0 0 1-.464-.208M6.6 17.208h8.77l-2.328-2.352q-.27-.02-.52-.034a9.93 9.93 0 0 0-3.109.326 9.5 9.5 0 0 0-2.384 1.002.9.9 0 0 0-.315.278.6.6 0 0 0-.114.356zm11.296-2.04q.222.14.358.34.136.202.177.46l-1.187-1.187.33.18q.165.09.322.206m-4.18-4.009-.975-.95q.481-.22.77-.662.29-.44.289-.964 0-.741-.526-1.266a1.73 1.73 0 0 0-1.264-.525q-.522 0-.963.29-.442.288-.664.77l-.95-.975a2.85 2.85 0 0 1 1.112-1.027q.69-.358 1.455-.358 1.298 0 2.199.901.9.9.901 2.2 0 .764-.358 1.454-.357.69-1.027 1.112\"\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/Reset.tsx",
    "content": "\nimport React from \"react\";\nimport Svg, { Path, SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 90 90\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M75.702 53.014c-2.142 7.995-7.27 14.678-14.439 18.816-7.168 4.138-15.519 5.239-23.514 3.095-16.505-4.423-26.335-21.448-21.913-37.953C20.258 20.467 37.286 10.64 53.79 15.06c4.213 1.129 8.076 3.118 11.413 5.809l-8.349 8.35h26.654V2.565l-8.354 8.354c-5.1-4.405-11.133-7.61-17.74-9.381C33.451-4.882 8.735 9.389 2.314 33.35c-6.42 23.961 7.851 48.678 31.811 55.098C38.001 89.486 41.934 90 45.842 90c7.795 0 15.488-2.044 22.42-6.046 10.407-6.008 17.851-15.709 20.962-27.317L75.702 53.014z\"\n      fill={color || \"#000\"}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/UserEmptyIcon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Mask, Path, Rect } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 120 120'>\n    <Mask\n      id='mask0_5082_129886'\n      style={{ maskType: \"alpha\" }}\n      maskUnits='userSpaceOnUse'\n      x='-20'\n      y='-20'\n      width='160'\n      height='160'\n    >\n      <Rect x='-20' y='-20' width='160' height='160' fill='#D9D9D9' />\n    </Mask>\n    <G mask='url(#mask0_5082_129886)'>\n      <Path\n        d='M60 57.9479C53.5644 57.9479 48.0667 55.6679 43.5067 51.1079C38.9467 46.549 36.6667 41.0512 36.6667 34.6146C36.6667 28.179 38.9467 22.6825 43.5067 18.1235C48.0667 13.5644 53.5644 11.2844 60 11.2844C66.4356 11.2844 71.9333 13.5644 76.4933 18.1235C81.0533 22.6825 83.3333 28.179 83.3333 34.6146C83.3333 41.0512 81.0533 46.549 76.4933 51.1079C71.9333 55.6679 66.4356 57.9479 60 57.9479ZM19.3166 105.007C16.1811 105.007 13.4568 103.856 11.1437 101.555C8.8306 99.2539 7.67344 96.5196 7.67344 93.3513V91.0288C7.67344 86.7545 8.70044 82.9018 10.7544 79.4706C12.8084 76.0385 15.6466 73.4095 19.268 71.5826C24.3797 69.1016 29.6071 67.2153 34.95 65.9235C40.2929 64.6317 46.0113 63.9858 52.105 63.9858H67.8949C73.987 63.9858 79.7055 64.6317 85.0491 65.9235C90.3928 67.2153 95.6215 69.1016 100.735 71.5826C104.357 73.4095 107.195 76.0385 109.249 79.4706C111.303 82.9018 112.33 86.7545 112.33 91.0288V93.3513C112.33 96.5196 111.173 99.2539 108.86 101.555C106.547 103.856 103.823 105.007 100.687 105.007H19.3166Z'\n        fill={color}\n      />\n    </G>\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/assets/icons/VideoCam.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.779 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h12.537q.627 0 1.104.471.475.471.475 1.112v5.1L21.13 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.104.47zm0-1.583h12.533V5.733H3.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/AIAgent/AIAgents.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUsers, useTheme} from '@cometchat/chat-uikit-react-native';\nimport React, {useCallback, useLayoutEffect} from 'react';\nimport {SafeAreaView} from 'react-native';\nimport {useFocusEffect, useNavigation} from '@react-navigation/native';\nimport {RootStackParamList} from '../../navigation/types';\nimport {StackNavigationProp} from '@react-navigation/stack';\n\ntype AIAgentNavigationProp = StackNavigationProp<RootStackParamList, 'AIAgents'>;\n\nconst AIAgents: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<AIAgentNavigationProp>();\n  const [shouldHide, setShouldHide] = React.useState(false);\n\n  // Focus effect to manage component visibility\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  // Configure header with back button\n  useLayoutEffect(() => {\n    navigation.setOptions({\n      headerShown: false,\n      title: 'AI Assistants',\n      headerStyle: {\n        backgroundColor: theme.color.background1,\n      },\n      headerTitleStyle: {\n        color: theme.color.textPrimary,\n      },\n    });\n  }, [navigation, theme]);\n\n  const handleUserPress = (user: CometChat.User) => {\n    navigation.navigate('Messages', { user });\n  };\n\n  return shouldHide ? null : (\n    <SafeAreaView style={{flex: 1, backgroundColor: theme.color.background1}}>\n      <CometChatUsers\n        onItemPress={handleUserPress}\n        title='Assistants'\n        showBackButton={true}\n        onBack={() => navigation.goBack()}\n        usersRequestBuilder={new CometChat.UsersRequestBuilder()\n          .setLimit(30)\n          .hideBlockedUsers(false)\n          .setRoles(['@agentic'])\n          .friendsOnly(false)\n          .setStatus('')\n          .setTags([])\n          .sortBy('name')\n          .setUIDs([])}\n      />\n    </SafeAreaView>\n  );\n};\n\nexport default AIAgents;"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/CallDetailHelper.tsx",
    "content": "import {CometChatTheme, CometChatUIKit} from '@cometchat/chat-uikit-react-native';\nimport {CallMade, CallMissedOutgoingFill, CallReceived} from './icons';\nimport {JSX} from 'react';\nimport { getCometChatTranslation } from '@cometchat/chat-uikit-react-native';\nconst t = getCometChatTranslation();\n\ntype CallDirection = 'incoming' | 'outgoing';\n\nexport type CallStatus =\n  | 'incoming'\n  | 'outgoing'\n  | 'incomingCallEnded'\n  | 'outgoingCallEnded'\n  | 'cancelledByMe'\n  | 'cancelledByThem'\n  | 'incomingRejected'\n  | 'outgoingRejected'\n  | 'incomingBusy'\n  | 'outgoingBusy'\n  | 'unansweredByMe'\n  | 'unansweredByThem';\n\nexport class CallDetailHelper {\n  static getFormattedInitiatedAt = (call: any): string => {\n    const date = new Date(call.getInitiatedAt() * 1000);\n    const now = new Date();\n\n    // Extracting parts\n    const day = date.getDate();\n    const month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(\n      date,\n    );\n    const year = date.getFullYear();\n    const time = date.toLocaleTimeString('en-US', {\n      hour: 'numeric',\n      minute: '2-digit',\n      hour12: true,\n    });\n\n    // Determine if the year should be included\n    const includeYear = now.getFullYear() !== year;\n\n    return `${day} ${month}${includeYear ? `, ${year}` : ''}, ${time}`;\n  };\n\n  /** Returns the UI-facing callStatus plus the direction */\n  static getCallType = (\n    call: any,\n  ): {type: CallDirection; callStatus: CallStatus} => {\n    const myUid = CometChatUIKit.loggedInUser?.getUid();\n    const type: CallDirection =\n      call.getInitiator().getUid() === myUid ? 'outgoing' : 'incoming';\n\n    const statusMap: Record<\n      string,\n      {incoming: CallStatus; outgoing: CallStatus}\n    > = {\n      ended: {\n        incoming: 'incomingCallEnded',\n        outgoing: 'outgoingCallEnded',\n      },\n      rejected: {\n        incoming: 'incomingRejected',\n        outgoing: 'outgoingRejected',\n      },\n      cancelled: {\n        incoming: 'unansweredByMe',\n        outgoing: 'cancelledByMe',\n      },\n      unanswered: {\n        incoming: 'unansweredByMe',\n        outgoing: 'unansweredByThem',\n      },\n      initiated: {\n        incoming: 'incoming',\n        outgoing: 'outgoing',\n      },\n      busy: {\n        incoming: 'incomingBusy',\n        outgoing: 'outgoingBusy',\n      },\n    };\n\n    return {\n      type,\n      callStatus:\n        statusMap[call.getStatus() as keyof typeof statusMap]?.[type] ??\n        (type === 'incoming' ? 'incoming' : 'outgoing'),\n    };\n  };\n\n  /** Which SVG to render for a given callStatus */\n  static getCallStatusDisplayIcon = (\n    callStatus: CallStatus,\n    theme: CometChatTheme,\n  ): JSX.Element | undefined => {\n    const icons: Record<CallStatus, JSX.Element> = {\n      outgoing: <CallMade height={24} width={24} color={theme.color.success} />,\n      outgoingCallEnded: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      cancelledByMe: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      outgoingRejected: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      outgoingBusy: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      unansweredByThem: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n\n      incoming: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      incomingCallEnded: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      cancelledByThem: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n      incomingRejected: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      incomingBusy: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n      unansweredByMe: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n    };\n\n    return icons[callStatus];\n  };\n\n  static getCallStatusDisplayText = (callStatus: CallStatus): string => {\n    const labels: Record<CallStatus, string> = {\n      outgoing: t('OUTGOING_CALL'),\n      outgoingCallEnded: t('OUTGOING_CALL'),\n      cancelledByMe: t('OUTGOING_CALL'),\n      outgoingRejected: t('OUTGOING_CALL'),\n      outgoingBusy: t('OUTGOING_CALL'),\n      unansweredByThem: t('OUTGOING_CALL'),\n\n      incoming: t('INCOMING_CALL'),\n      incomingCallEnded: t('INCOMING_CALL'),\n      cancelledByThem: t('MISSED_CALL'),\n      incomingRejected: t('INCOMING_CALL'),\n      incomingBusy: t('MISSED_CALL'),\n      unansweredByMe: t('MISSED_CALL'),\n    };\n\n    return labels[callStatus];\n  };\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/CallDetails.tsx",
    "content": "import React, {\n  JSX,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {View, TouchableOpacity, Text, TextStyle, ViewStyle} from 'react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatListItem,\n  useCometChatTranslation,\n  useTheme,\n  useLocalizedDate,\n  LocalizedDateHelper\n} from '@cometchat/chat-uikit-react-native';\nimport {CallHistory} from './CallHistory';\nimport {CallLogDetailHeader} from './CallLogDetailHeader';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CallParticipants} from './CallParticipants';\nimport {CallDetailHelper, CallStatus} from './CallDetailHelper';\nimport {CallRecordings} from './CallRecordings';\nimport {StackScreenProps} from '@react-navigation/stack';\nimport {ICONS} from '@cometchat/chat-uikit-react-native/src/shared/icons/icon-mapping';\nimport {RootStackParamList} from '../../navigation/types';\n\n\nconst listenerId = 'userListener_' + new Date().getTime();\nconst TABS = {\n  DETAILS: 'Details',\n  PARTICIPANTS: 'Participants',\n  RECORDINGS: 'Recordings',\n  HISTORY: 'History',\n};\n\ntype Props = StackScreenProps<RootStackParamList, 'CallDetails'>;\n\nexport const CallDetails: React.FC<Props> = ({route, navigation}) => {\n  const {call} = route.params;\n\n  const theme = useTheme();\n  const {t, language} = useCometChatTranslation()\n  const {formatDate}= useLocalizedDate()\n  const [group, setGroup] = useState<CometChat.Group | null>(null);\n  const [user, setUser] = useState<CometChat.User | null>(null);\n  const loggedInUser = useRef<CometChat.User | any>(null);\n  const [selectedTab, setSelectedTab] = useState(TABS.PARTICIPANTS);\n  const [tabStyle, setTableStyle] = useState<{\n    containerStyle: ViewStyle;\n    itemStyle: ViewStyle;\n    selectedItemStyle: ViewStyle;\n    itemEmojiStyle: TextStyle;\n    selectedItemEmojiStyle: TextStyle;\n    itemTextStyle: TextStyle;\n    selectedItemTextStyle: TextStyle;\n  }>();\n  const BackIcon = ICONS['arrow-back'];\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  useEffect(() => {\n    console.log('CALL RECEIVER: ', call);\n    CometChat.getLoggedinUser().then((loggedUser: CometChat.User | any) => {\n      loggedInUser.current = loggedUser;\n      let user =\n        call?.getReceiverType() == 'user'\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      let group =\n        call?.getReceiverType() == 'group'\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      if (user) {\n        CometChat.getUser(user.getUid()).then((userObject: CometChat.User) => {\n          setUser(userObject);\n        });\n        return;\n      }\n\n      CometChat.getGroup(group.getGuid()).then(\n        (groupObject: CometChat.Group) => {\n          setGroup(groupObject);\n        },\n      );\n    });\n  }, [call]);\n\n  useEffect(() => {\n    CometChat.addUserListener(\n      listenerId,\n      new CometChat.UserListener({\n        onUserOnline: (userDetails: any) => {\n          if (user?.getUid() === userDetails?.getUid()) {\n            setUser(userDetails);\n          }\n        },\n        onUserOffline: (userDetails: any) => {\n          if (user?.getUid() === userDetails?.getUid()) {\n            setUser(userDetails);\n          }\n        },\n      }),\n    );\n    return () => {\n      CometChat.removeUserListener(listenerId);\n    };\n  }, [user]);\n\n  useEffect(() => {\n    setTableStyle({\n      containerStyle: {\n        //flex: 1,\n        backgroundColor: theme.color.background1,\n        flexDirection: 'row',\n        //justifyContent: 'space-evenly', // ✅ Ensures even spacing\n        alignItems: 'center', // Optional, ensures vertical alignment\n        width: '100%', // ✅ Ensures full width for proper spacing\n        borderBottomWidth: 1,\n        borderColor: theme.color.borderDefault,\n        justifyContent: 'space-evenly',\n      },\n      itemStyle: {\n        // paddingHorizontal: theme.spacing.padding.p4,\n        paddingVertical: theme.spacing.padding.p2,\n        flexDirection: 'row',\n        borderBottomWidth: theme.spacing.spacing.s0_5,\n        borderBottomColor: 'transparent',\n        alignItems: 'center',\n        justifyContent: 'center',\n        flex: 1,\n      },\n      selectedItemStyle: {\n        // paddingHorizontal: theme.spacing.padding.p4,\n        paddingVertical: theme.spacing.padding.p2,\n        flexDirection: 'row',\n        borderBottomWidth: theme.spacing.spacing.s0_5,\n        borderBottomColor: theme.color.primary,\n        alignItems: 'center',\n        justifyContent: 'center',\n        flex: 1,\n      },\n      itemEmojiStyle: {\n        color: theme.color.textSecondary,\n        borderColor: 'transparent',\n        ...theme.typography.body.medium,\n      },\n      selectedItemEmojiStyle: {\n        color: theme.color.textSecondary,\n        borderColor: 'transparent',\n        ...theme.typography.body.medium,\n      },\n      itemTextStyle: {\n        color: theme.color.textSecondary,\n        marginLeft: theme.spacing.margin.m1,\n        ...theme.typography.body.medium,\n      },\n      selectedItemTextStyle: {\n        color: theme.color.primary,\n        marginLeft: theme.spacing.margin.m1,\n        ...theme.typography.body.medium,\n      },\n    });\n  }, [theme]);\n\n  const getUserToFetchHistory = useCallback(() => {\n    if (call?.getInitiator().getUid() === loggedInUser.current.getUid()) {\n      return call?.getReceiver();\n    }\n    call?.getInitiator();\n  }, [call]);\n\n \n  const callTypeAndStatus = useMemo(\n    (): {\n      type: 'incoming' | 'outgoing';\n      callStatus: CallStatus;\n    } => CallDetailHelper.getCallType(call),\n    [call],\n  );\n\n  /** Busy call toast: only when attempting a call and target is busy */\n  useEffect(() => {\n    const callListener = new CometChat.CallListener({\n      onOutgoingCallRejected: (rejectedCall: any) => {\n        try {\n          const status = rejectedCall?.getStatus?.() || rejectedCall?.status;\n          if (\n            status &&\n            status.toLowerCase() === CometChat.CALL_STATUS.BUSY.toLowerCase()\n          ) {\n            setToastMessage(t('CALL_BUSY'));\n            setTimeout(() => setToastMessage(null), 3000);\n          }\n        } catch {}\n      },\n    });\n    CometChat.addCallListener(listenerId, callListener);\n    return () => {\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [t]);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background2,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingRight: theme.spacing.padding.p4,\n          paddingLeft: theme.spacing.padding.p2,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          flex: 1,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.heading4.bold,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.caption1.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.bold,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n  const getFormattedInitiatedAt = useCallback(() => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [call]);\n\n  const callStatusDisplayString = useMemo(() => {\n    return CallDetailHelper.getCallStatusDisplayText(\n      callTypeAndStatus.callStatus,\n    );\n  }, [callTypeAndStatus]);\n\n  const CallStatusIcon = useMemo<JSX.Element | undefined>(\n    () =>\n      CallDetailHelper.getCallStatusDisplayIcon(\n        callTypeAndStatus.callStatus,\n        theme,\n      ),\n    [callTypeAndStatus, theme],\n  );\n\n  /** Local toast view component (shows only when toastMessage set; auto hides after 3s from effect) */\n  const ToastView = () => {\n    if (!toastMessage) return null;\n    return (\n      <View\n        style={{\n          alignSelf: 'center',\n          flexDirection: 'row',\n          alignItems: 'center',\n          borderRadius: 24,\n          backgroundColor: 'rgba(255,59,48,0.10)',\n          paddingVertical: theme.spacing.padding.p1,\n          paddingHorizontal: theme.spacing.padding.p2,\n          marginBottom: theme.spacing.margin.m3,\n          maxWidth: '80%',\n        }}>\n        <Icon name=\"info\" color={theme.color.error} size={18} />\n        <Text\n          style={[\n            theme.typography.caption1.bold,\n            {color: theme.color.error, marginLeft: theme.spacing.margin.m1},\n          ]}\n          accessibilityRole=\"alert\">\n          {toastMessage}\n        </Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      <View style={{flex: 1, paddingBottom: theme.spacing.padding.p3}}>\n        <View\n          style={{\n            flexDirection: 'row',\n            justifyContent: 'flex-start',\n            alignItems: 'center',\n            gap: theme.spacing.padding.p2,\n            paddingVertical: theme.spacing.padding.p2,\n            paddingHorizontal: theme.spacing.padding.p5,\n            minHeight: 64,\n          }}>\n          <TouchableOpacity onPress={() => navigation.goBack()}>\n            <Icon\n              imageStyle={{\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconPrimary,\n              }}\n              icon={\n                <BackIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.iconPrimary}></BackIcon>\n              }\n            />\n          </TouchableOpacity>\n          <Text\n            style={{\n              color: theme.color.textPrimary,\n              ...theme.typography.heading1.bold,\n              paddingTop: 35 - (35 * 0.75),\n            }}>\n            {t('CALL_DETAILS')}\n          </Text>\n        </View>\n\n        {(user || group) && (\n          <View style={{flexDirection: 'column'}}>\n            <CallLogDetailHeader\n              {...(user && {user})}\n              {...(group && {group})}></CallLogDetailHeader>\n            <View\n              style={{\n                flexDirection: 'row',\n                width: '100%',\n                alignItems: 'center',\n                backgroundColor: theme.color.background2,\n              }}>\n              <Icon\n                icon={CallStatusIcon}\n                containerStyle={{marginLeft: theme.spacing.margin.m4}}></Icon>\n              <CometChatListItem\n                id={call.sessionId}\n                avatarStyle={_style.itemStyle.avatarStyle}\n                containerStyle={_style.itemStyle.containerStyle}\n                headViewContainerStyle={{flexDirection: 'row'}}\n                titleStyle={_style.itemStyle.titleStyle}\n                title={callStatusDisplayString}\n                trailingViewContainerStyle={{\n                  alignSelf: 'center',\n                }}\n                SubtitleView={\n                  <Text style={_style.itemStyle.subtitleStyle}>\n                    {getFormattedInitiatedAt()}\n                  </Text>\n                }\n                TrailingView={\n                  <Text style={_style.itemStyle.tailViewTextStyle}>\n                    {convertMinutesToTime(call.getTotalDurationInMinutes())}\n                  </Text>\n                }\n              />\n            </View>\n\n            <View\n              style={{\n                flexDirection: 'row',\n                alignItems: 'center',\n                width: '100%',\n                borderBottomWidth: 1,\n                borderColor: theme.color.borderDefault,\n                justifyContent: 'space-evenly',\n                paddingHorizontal: 0,\n              }}>\n              {[\n                { key: TABS.PARTICIPANTS, title: t('PARTICIPANT') },\n                { key: TABS.RECORDINGS, title: t('RECORDING') },\n                { key: TABS.HISTORY, title: t('HISTORY') }\n              ].map((tab) => (\n                <TouchableOpacity\n                  key={tab.key}\n                  style={\n                    selectedTab === tab.key\n                      ? tabStyle!.selectedItemStyle\n                      : tabStyle!.itemStyle\n                  }\n                  onPress={() => setSelectedTab(tab.key)}>\n                  <Text\n                    style={\n                      selectedTab === tab.key\n                        ? tabStyle!.selectedItemTextStyle\n                        : tabStyle!.itemTextStyle\n                    }>\n                    {tab.title}\n                  </Text>\n                </TouchableOpacity>\n              ))}\n            </View>\n            {selectedTab === TABS.PARTICIPANTS && (\n              <CallParticipants\n                call={call}\n                data={call?.getParticipants()}></CallParticipants>\n            )}\n            {selectedTab === TABS.HISTORY && (\n              <CallHistory user={getUserToFetchHistory()}></CallHistory>\n            )}\n            {selectedTab === TABS.RECORDINGS &&\n              call.getRecordings()?.length && (\n                <CallRecordings call={call}></CallRecordings>\n              )}\n          </View>\n        )}\n      </View>\n      <View style={{width: '100%', paddingBottom: theme.spacing.padding.p3}}>\n        <ToastView />\n      </View>\n    </View>\n  );\n};"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/CallHistory.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CallingPackage,\n  CometChatListItem,\n  useTheme,\n  useCometChatTranslation,\n  useLocalizedDate,\n  LocalizedDateHelper\n} from '@cometchat/chat-uikit-react-native';\nimport React, { JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, FlatList, Text, View } from 'react-native';\nimport { CallDetailHelper } from './CallDetailHelper';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\nexport const CallHistory = (props: { user?: any; group?: any }) => {\n  const { user, group } = props;\n\n  const theme = useTheme();\n  const { language, t } = useCometChatTranslation();\n  const { formatDate } = useLocalizedDate();\n\n  const [list, setList] = useState<any[]>([]);\n  const [loading, setLoading] = useState<boolean>(true);\n  const [isFetchingMore, setIsFetchingMore] = useState<boolean>(false);\n\n  const loggedInUser = useRef<CometChat.User | any>(null);\n  const callRequestBuilderRef = useRef<any>(null);\n\n  function setRequestBuilder() {\n    callRequestBuilderRef.current;\n    let builder = new CometChatCalls.CallLogRequestBuilder()\n      .setLimit(30)\n      .setAuthToken(loggedInUser.current?.getAuthToken() || '')\n      .setCallCategory('call');\n    if (user) {\n      builder = builder.setUid(user?.getUid());\n    } else if (group) {\n      builder = builder.setGuid(group?.getGuid());\n    }\n    callRequestBuilderRef.current = builder.build();\n  }\n\n  const fetchCallLogHistory = () => {\n    if (!callRequestBuilderRef.current || isFetchingMore) {\n      return;\n    }\n    setIsFetchingMore(true);\n    callRequestBuilderRef.current\n      .fetchNext()\n      .then((CallLogHistory: any) => {\n        if (CallLogHistory.length > 0) {\n          setList(prev => [...prev, ...CallLogHistory]);\n        }\n      })\n      .catch((err: any) => {\n        // onError && onError(err);\n      })\n      .finally(() => {\n        setLoading(false);\n        setIsFetchingMore(false);\n      });\n  };\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: any) => {\n        loggedInUser.current = u;\n        setRequestBuilder();\n        fetchCallLogHistory();\n      })\n      .catch((e: any) => {\n        // onError && onError(e);\n        setLoading(false);\n      });\n  }, []);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          marginHorizontal: theme.spacing.margin.m4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          flex: 1,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.bold,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.caption1.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.bold,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  // Updated to use proper localization\n  const getFormattedInitiatedAt = useCallback((call: any) => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    // Use localizedDateHelper to format the date with proper localization\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [formatDate]);\n\n  const getCallType = useCallback((call: any) => {\n    return CallDetailHelper.getCallType(call);\n  }, []);\n\n  const getCallStatusIcon = useCallback((item: any): JSX.Element => {\n    const CallStatusIcon = CallDetailHelper.getCallStatusDisplayIcon(\n      getCallType(item).callStatus,\n      theme,\n    );\n    return CallStatusIcon || <View />;\n  }, []);\n\n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n  \n\n  // Added to get localized call status text\n  const getCallStatusText = useCallback((callStatus: any) => {\n    // Get translation key for this status\n    const translationKeys: Record<string, string> = {\n      outgoing: 'OUTGOING_CALL',\n      outgoingCallEnded: 'OUTGOING_CALL',\n      cancelledByMe: 'OUTGOING_CALL',\n      outgoingRejected: 'OUTGOING_CALL',\n      outgoingBusy: 'OUTGOING_CALL',\n      unansweredByThem: 'OUTGOING_CALL',\n      incoming: 'INCOMING_CALL',\n      incomingCallEnded: 'INCOMING_CALL',\n      cancelledByThem: 'MISSED_CALL',\n      incomingRejected: 'INCOMING_CALL',\n      incomingBusy: 'MISSED_CALL',\n      unansweredByMe: 'MISSED_CALL',\n    };\n\n    const key = translationKeys[callStatus] || 'UNKNOWN_CALL';\n    return t(key);\n  }, [t]);\n\n  const _render = ({ item, index }: any) => {\n    return (\n      <React.Fragment key={index}>\n        <View\n          style={{\n            flexDirection: 'row',\n            width: '100%',\n            alignItems: 'center',\n          }}>\n          <Icon\n            icon={getCallStatusIcon(item)}\n            containerStyle={{ marginLeft: theme.spacing.margin.m4 }}></Icon>\n          <CometChatListItem\n            id={item.sessionId}\n            containerStyle={_style.itemStyle.containerStyle}\n            headViewContainerStyle={{ flexDirection: 'row' }}\n            trailingViewContainerStyle={{\n              alignSelf: 'center',\n            }}\n            titleStyle={_style.itemStyle.titleStyle}\n           title={CallDetailHelper.getCallStatusDisplayText(\n              getCallType(item).callStatus,\n            )}\n            SubtitleView={\n              <Text\n                style={{\n                  ...theme.typography.body.regular,\n                  color: theme.color.textSecondary,\n                }}>\n                {getFormattedInitiatedAt(item)}\n              </Text>\n            }\n            TrailingView={\n              <Text style={_style.itemStyle.tailViewTextStyle}>\n              {convertMinutesToTime(item.getTotalDurationInMinutes())}\n              </Text>\n            }\n          />\n        </View>\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <FlatList\n      data={list}\n      keyExtractor={(item, index) => item.sessionId + '_' + index}\n      renderItem={_render}\n      onEndReached={fetchCallLogHistory}\n      onEndReachedThreshold={0.5}\n      ListEmptyComponent={\n        loading ? (\n          <View\n            style={{\n              flex: 1,\n              height: 300,\n              justifyContent: 'center',\n              alignItems: 'center',\n            }}\n          >\n            <ActivityIndicator size=\"large\" color={theme.color.primary} />\n          </View>\n        ) : null\n      }\n    />\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/CallLogDetailHeader.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  useTheme,\n  CometChatAvatar,\n  CometChatCallButtons,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  GroupTypeConstants,\n  UserStatusConstants,\n} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport {CometChatCompThemeProvider} from '@cometchat/chat-uikit-react-native/src/theme/provider';\nimport React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';\nimport {Text, View} from 'react-native';\nimport { useConfig } from '../../config/store';\nexport type CallLogDetailHeaderInterface = {\n  user?: CometChat.User;\n  /**\n   *\n   * @type {CometChat.Group}\n   *   To pass group object\n   */\n  group?: CometChat.Group;\n};\n\nexport const CallLogDetailHeader = (props: CallLogDetailHeaderInterface) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const {user, group} = props;\n\n  const [groupObj, setGroupObj] = useState(group);\n  const [userStatus, setUserStatus] = useState(\n    user &&\n      !(user.getBlockedByMe() || user.getHasBlockedMe()) &&\n      user?.getStatus\n      ? user?.getStatus()\n      : '',\n  );\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n  const receiverTypeRef = useRef(\n    user\n      ? CometChat.RECEIVER_TYPE.USER\n      : group\n        ? CometChat.RECEIVER_TYPE.GROUP\n        : null,\n  );\n\n  useEffect(() => {\n    setGroupObj(group);\n  }, [group]);\n\n  useEffect(() => {\n    setUserStatus(\n      user && !(user.getBlockedByMe() || user.getHasBlockedMe())\n        ? user?.getStatus()\n        : '',\n    );\n  }, [user]);\n\n  const messageHeaderStyles = useMemo(() => {\n    return theme.messageHeaderStyles;\n  }, [theme.messageHeaderStyles]);\n\n  const statusIndicatorType = useMemo(() => {\n    if (groupObj?.getType() === GroupTypeConstants.password) {\n      return 'protected';\n    } else if (groupObj?.getType() === GroupTypeConstants.private) {\n      return 'private';\n    } else if (userStatus === 'online') {\n      return 'online';\n    }\n    return '';\n  }, [userStatus, groupObj]);\n\n  const AvatarWithStatusView = useCallback(() => {\n    return (\n      <View>\n        <CometChatAvatar\n          image={\n            user\n              ? user.getAvatar()\n                ? {uri: user.getAvatar()}\n                : undefined\n              : groupObj\n                ? groupObj.getIcon()\n                  ? {uri: groupObj.getIcon()}\n                  : undefined\n                : undefined\n          }\n          name={user?.getName() || groupObj?.getName() || ''}\n        />\n      </View>\n    );\n  }, [user, groupObj, statusIndicatorType]);\n\n  const SubtitleViewFnc = () => {\n    const statusTytle =\n      receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&\n      (groupObj?.['membersCount'] || groupObj?.['membersCount'] === 0)\n        ? `${groupObj['membersCount']} ${t('MEMBERS')}`\n        : receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER\n          ? userStatus === UserStatusConstants.online\n            ? t('ONLINE')\n            : userStatus === UserStatusConstants.offline\n              ? t('OFFLINE')\n              : ''\n          : '';\n\n    if(!statusTytle) return <></>;\n    return (\n      <Text\n        style={{\n          ...theme.typography.caption1.regular,\n          color: theme.color.textSecondary,\n        }}>\n        {statusTytle}\n      </Text>\n    );\n  };\n\n  return (\n    <CometChatCompThemeProvider\n      theme={{\n        callButtonStyles: messageHeaderStyles.callButtonStyle,\n        avatarStyle: messageHeaderStyles.avatarStyle,\n        statusIndicatorStyle: messageHeaderStyles.statusIndicatorStyle,\n      }}>\n      <View\n        style={{\n          paddingVertical: theme.spacing.padding.p5,\n          paddingHorizontal: theme.spacing.padding.p4,\n          backgroundColor: theme.color.background1,\n          flexDirection: 'row',\n          gap: theme.spacing.padding.p3,\n          alignItems: 'center',\n          borderTopWidth: 1,\n          borderBottomWidth: 1,\n          borderColor: theme.color.borderLight,\n        }}>\n        <AvatarWithStatusView />\n        <View style={{flex: 1}}>\n          <Text\n            numberOfLines={1}\n            ellipsizeMode=\"tail\"\n            style={{\n              ...theme.typography.heading4.medium,\n              color: theme.color.textPrimary,\n            }}>\n            {user ? user.getName() : groupObj ? groupObj.getName() : ''}\n          </Text>\n          {<SubtitleViewFnc />}\n        </View>\n        <View style={{marginLeft: 'auto'}}>\n          <CometChatCallButtons\n            user={user}\n            group={group}\n            hideVoiceCallButton={\n              (user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)\n            }\n            hideVideoCallButton={\n              (user && !oneOnOneVideoCalling) || (group && !groupVideoConference)\n            }\n            style={{\n              containerStyle: {\n                gap: 10,\n              },\n              audioCallButtonIconStyle: {\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconHighlight,\n              },\n              videoCallButtonIconStyle: {\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconHighlight,\n              },\n              audioCallButtonIconContainerStyle: {\n                height: 40,\n                width: 64,\n                paddingVertical: theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p5,\n                borderWidth: 1,\n                borderRadius: theme.spacing.radius.r2,\n                borderColor: theme.color.borderDefault,\n              },\n              videoCallButtonIconContainerStyle: {\n                height: 40,\n                width: 64,\n                paddingVertical: theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p5,\n                borderWidth: 1,\n                borderRadius: theme.spacing.radius.r2,\n                borderColor: theme.color.borderDefault,\n              },\n            }}\n          />\n        </View>\n      </View>\n    </CometChatCompThemeProvider>\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/CallParticipants.tsx",
    "content": "import { CometChatListItem, useTheme, useCometChatTranslation, localizedDateHelperInstance, useLocalizedDate, LocalizedDateHelper } from '@cometchat/chat-uikit-react-native';\nimport React, { useCallback, useMemo } from 'react';\nimport { View, FlatList, Text } from 'react-native';\nimport { CallDetailHelper } from './CallDetailHelper';\n\nexport const CallParticipants = (props: {\n  /**\n   * Participant list\n   */\n  data: any[];\n  call: any;\n}) => {\n  const { call, data } = props;\n\n  const theme = useTheme();\n  const { language, t } = useCometChatTranslation();\n  const { formatDate } = useLocalizedDate();\n\n  const getCallDetails = (item: any) => {\n    return {\n      title: item['name'],\n      avatarUrl: item['avatar'],\n    };\n  };\n\n  // Updated to use proper localization\n  const formattedInitiatedAt = useMemo(() => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    // Use formatDate for proper localization of date/time\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [call, formatDate]);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingHorizontal: theme.spacing.padding.p4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.body.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.medium,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n  \n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n \n\n  const _render = ({ item, index }: any) => {\n    const { title, avatarUrl } = getCallDetails(item);\n\n    return (\n      <React.Fragment key={index}>\n        <CometChatListItem\n          id={item.sessionId}\n          avatarStyle={_style.itemStyle.avatarStyle}\n          containerStyle={_style.itemStyle.containerStyle}\n          headViewContainerStyle={{ flexDirection: 'row' }}\n          titleStyle={_style.itemStyle.titleStyle}\n          trailingViewContainerStyle={{\n            alignSelf: 'center',\n          }}\n          SubtitleView={\n            <Text\n              style={{\n                ...theme.typography.body.regular,\n                color: theme.color.textSecondary,\n              }}>\n              {formattedInitiatedAt}\n            </Text>\n          }\n          // avatarName={title}\n          title={title}\n          // subtitle={'8 August, 8:14 pm'}\n          avatarURL={avatarUrl}\n          TrailingView={\n            <Text style={_style.itemStyle.tailViewTextStyle}>\n              {convertMinutesToTime(item.getTotalDurationInMinutes())}\n            </Text>\n          }\n        />\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <View style={{ backgroundColor: theme.color.background1 }}>\n      {data.length && (\n        <FlatList\n          data={data}\n          keyExtractor={(item, index) => item.sessionId + '_' + index}\n          renderItem={_render}\n        />\n      )}\n    </View>\n  );\n};"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/CallRecordings.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {useTheme, CometChatListItem} from '@cometchat/chat-uikit-react-native';\nimport React, {useRef, useEffect, useMemo, useState} from 'react';\nimport {\n  View,\n  FlatList,\n  Text,\n  Pressable,\n  NativeEventEmitter,\n  NativeModules,\n  Platform,\n} from 'react-native';\nimport {PlayArrow} from './icons';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CallDetailHelper} from './CallDetailHelper';\n\nconst {FileManager} = NativeModules;\nconst eventEmitter = new NativeEventEmitter(FileManager);\n\nexport const CallRecordings = (props: {call: any}) => {\n  const {call} = props;\n  const [data, setData] = useState(call.getRecordings());\n\n  const theme = useTheme();\n\n  const loggedInUser = useRef(null);\n  const downloadIdRef = useRef(0);\n  const [processing, setProcessing] = React.useState(false);\n  const [fileExists, setFileExists] = React.useState(false); // State to track if the file exists\n  let listener: any = useRef(undefined);\n  const [fileUrl, setFileUrl] = useState();\n  const [reOpenCount, setReopenCount] = useState(0);\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: any) => {\n        loggedInUser.current = u;\n      })\n      .catch((e: any) => {\n        // onError && onError(e);\n      });\n  }, []);\n\n  useEffect(() => {\n    if (Platform.OS == 'android') {\n      listener.current = eventEmitter.addListener(\n        'downloadComplete',\n        (data: {downloadId: number}) => {\n          if (\n            data.downloadId &&\n            downloadIdRef.current &&\n            data.downloadId == downloadIdRef.current\n          ) {\n            setProcessing(false);\n            setFileExists(true);\n            openFile(fileUrl);\n          }\n        },\n      );\n    }\n    return () => {\n      if (Platform.OS == 'android') {\n        listener.current.remove();\n      }\n    };\n  }, []);\n\n  useEffect(() => {\n    if (!fileUrl) return;\n\n    const fileName = getFileName(fileUrl);\n    setProcessing(true);\n    FileManager.doesFileExist(fileName, (result: string) => {\n      setProcessing(false);\n      if (JSON.parse(result).exists) {\n        setFileExists(true);\n        openFile(fileUrl);\n      } else {\n        downloadFile(fileUrl);\n      }\n    });\n  }, [fileUrl, reOpenCount]);\n\n  const downloadFile = (fileUrl: any) => {\n    if (processing || fileExists) return; // Do not process if file already exists\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.checkAndDownload(\n      fileUrl,\n      getFileName(fileUrl),\n      (result: string) => {\n        if (Platform.OS == 'ios') {\n          let parsedResult = JSON.parse(result);\n          if (parsedResult.success == true) {\n            setProcessing(false);\n            setFileExists(true);\n          }\n          openFile(fileUrl);\n        } else if (Platform.OS == 'android') {\n          downloadIdRef.current = JSON.parse(result).downloadId;\n        }\n      },\n    );\n  };\n\n  const openFile = (fileUrl: any) => {\n    if (processing) return;\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.openFileWithOption(getFileName(fileUrl), (isOpened: string) => {\n      setProcessing(false);\n    });\n  };\n\n  const getFileName = (fileUrl: any) => {\n    return fileUrl.substring(fileUrl.lastIndexOf('/') + 1).replace(' ', '_');\n  };\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingHorizontal: theme.spacing.padding.p4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          minHeight: 72,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.body.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.medium,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  const formattedInitiatedAt = useMemo(() => {\n    return CallDetailHelper.getFormattedInitiatedAt(call);\n  }, [call]);\n\n  const _render = ({item, index}: any) => {\n    const title = item['rid'];\n\n    return (\n      <React.Fragment key={index}>\n        <CometChatListItem\n          id={item.sessionId}\n          title={title}\n          SubtitleView={\n            <Text\n              style={{\n                ...theme.typography.body.regular,\n                color: theme.color.textSecondary,\n              }}>\n              {formattedInitiatedAt}\n            </Text>\n          }\n          titleStyle={_style.itemStyle.titleStyle}\n          containerStyle={_style.itemStyle.containerStyle}\n          trailingViewContainerStyle={{\n            alignSelf: 'center',\n          }}\n          TrailingView={\n            <Pressable\n              onPress={() => {\n                setFileUrl(item.getRecordingURL());\n                setReopenCount(reOpenCount + 1);\n              }}>\n              <Icon\n                icon={\n                  <PlayArrow\n                    height={24}\n                    width={24}\n                    color={theme.color.iconHighlight}></PlayArrow>\n                }\n                containerStyle={{marginLeft: theme.spacing.margin.m4}}></Icon>\n            </Pressable>\n          }\n        />\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <View style={{backgroundColor: theme.color.background1}}>\n      {data.length && (\n        <FlatList\n          data={data}\n          keyExtractor={(item, index) => item.sessionId + '_' + index}\n          renderItem={_render}\n        />\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/Calls.tsx",
    "content": "import {CometChatCallLogs, useTheme, Icon} from '@cometchat/chat-uikit-react-native';\nimport {useFocusEffect, useNavigation} from '@react-navigation/native';\nimport React, {useCallback} from 'react';\nimport {View, TouchableOpacity} from 'react-native';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../navigation/types';\nimport { useConfig } from '../../config/store';\n\ntype CallNavigationProp = StackNavigationProp<RootStackParamList, 'CallLogs'>;\n\nconst Calls: React.FC = () => {\n  const [shouldHide, setShouldHide] = React.useState(false);\n  const navigation = useNavigation<CallNavigationProp>();\n\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  const theme = useTheme();\n  const onItemPress = (item: any) => {\n    navigation.navigate('CallDetails', {\n      call: item,\n    });\n  };\n  // Create TrailingView that respects visibility configuration\n  const TrailingView = useCallback((call?: any, onPress?: (call: any) => void) => {\n    if (!call || !onPress) return (<></>);\n    const receiverType = call?.getReceiverType?.();\n    const callType = call?.getType?.();\n    const isUser = receiverType === 'user';\n    const isAudio = callType === 'audio';\n\n    // Check if button should be hidden based on configuration\n    const shouldHide =\n      (isUser && isAudio && !oneOnOneVoiceCalling) ||\n      (isUser && !isAudio && !oneOnOneVideoCalling) ||\n      (!isUser && isAudio && !groupVoiceConference) ||\n      (!isUser && !isAudio && !groupVideoConference);\n\n    if (shouldHide) {\n      return <View />;\n    }\n\n    // Return call button - styling matches CometChatCallLogs default button\n    return (\n      <TouchableOpacity\n        onPress={() => onPress(call)}\n        style={{\n          marginLeft: \"auto\",\n        }}\n      >\n        <Icon\n          name={call.type === \"audio\" ? \"call\" : \"videocam\"}\n          size={24}\n          color={theme.callLogsStyles.itemStyle.callIconStyle.tintColor}\n          imageStyle={theme.callLogsStyles.itemStyle.callIconStyle}\n        />\n      </TouchableOpacity>\n    );\n  }, [oneOnOneVoiceCalling, oneOnOneVideoCalling, groupVoiceConference, groupVideoConference, theme]);\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {!shouldHide && (\n        <CometChatCallLogs \n          onItemPress={onItemPress}\n          TrailingView={TrailingView}\n        />\n      )}\n    </View>\n  );\n};\n\nexport default Calls;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-end-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 8.25q2.864 0 5.649 1.149a14.15 14.15 0 0 1 4.876 3.332q.308.31.314.724a.95.95 0 0 1-.305.714l-1.905 1.856q-.305.294-.681.329a1 1 0 0 1-.696-.2l-2.516-1.912a1.3 1.3 0 0 1-.367-.417 1.1 1.1 0 0 1-.12-.517v-2.822a15 15 0 0 0-2.113-.552A12 12 0 0 0 12 9.75q-1.107 0-2.137.184-1.029.186-2.113.553v2.82q0 .289-.12.518-.118.229-.367.417l-2.515 1.912a1 1 0 0 1-.696.2 1.1 1.1 0 0 1-.681-.329l-1.906-1.856a.95.95 0 0 1-.305-.714 1 1 0 0 1 .315-.724 14 14 0 0 1 4.868-3.332Q9.136 8.25 12 8.25'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-end.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.517 16.3 1.2 14.001a.74.74 0 0 1-.234-.551.84.84 0 0 1 .233-.567 13.8 13.8 0 0 1 4.917-3.587A14.7 14.7 0 0 1 12 8.066q3.045 0 5.88 1.23 2.833 1.23 4.92 3.587.229.255.233.567a.74.74 0 0 1-.235.551L20.5 16.3a.86.86 0 0 1-.573.24.86.86 0 0 1-.595-.158L16.5 14.254a.8.8 0 0 1-.237-.275.8.8 0 0 1-.08-.358v-3.27a11.3 11.3 0 0 0-2.077-.534 13.4 13.4 0 0 0-2.09-.167q-1.05 0-2.11.167-1.059.165-2.073.533v3.267a.8.8 0 0 1-.077.352.74.74 0 0 1-.24.281l-2.852 2.137q-.286.21-.598.183a.84.84 0 0 1-.55-.27m2.65-5.3q-.887.442-1.715 1.063-.826.62-1.602 1.32l1.317 1.35 2-1.5zm11.6-.05v2.183l2.066 1.584 1.334-1.317a12 12 0 0 0-1.619-1.367 17 17 0 0 0-1.781-1.083'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-log-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.44 21.5q-2.827 0-5.68-1.314t-5.242-3.709-3.703-5.242T2.5 5.56A1.03 1.03 0 0 1 3.55 4.5h3.262q.378 0 .668.247.289.247.368.61L8.421 8.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M12.75 4a.73.73 0 0 1-.534-.216A.73.73 0 0 1 12 3.25q0-.32.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 20.75 4zm0 3.692a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.319.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215zm0 3.692a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .319-.216.534a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-log.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.75 21.833q-2.893 0-5.863-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.457.31-.768.31-.31.773-.31h3.567q.35 0 .598.235T7.749 5l.666 3.194q.043.335-.026.616a1 1 0 0 1-.26.477L5.704 11.75q.603 1.041 1.304 1.953.702.91 1.546 1.735a17 17 0 0 0 1.818 1.626A16 16 0 0 0 12.4 18.4l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M4.92 10.283l1.9-1.916-.537-2.617H3.77q.059 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521zm-.842-15.4a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.234.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-made-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17 8.054 6.083 18.973a.72.72 0 0 1-.522.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.217-.527L15.947 7H9.751a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.535a.73.73 0 0 1 .535-.215h7.846q.383 0 .644.26.26.26.26.644v7.846q0 .319-.216.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-made.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.245 7.854 5.83 19.284a.73.73 0 0 1-.554.235.8.8 0 0 1-.559-.244.75.75 0 0 1 .002-1.106L16.133 6.75H9.766a.75.75 0 0 1-.56-.234.78.78 0 0 1-.227-.56q0-.327.227-.558a.76.76 0 0 1 .56-.231h8.267q.333 0 .565.232a.76.76 0 0 1 .23.555v8.267q0 .333-.231.565a.77.77 0 0 1-.567.23.75.75 0 0 1-.56-.23.78.78 0 0 1-.225-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-missed-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5 10.021v4.181a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V8.356q0-.387.259-.645a.88.88 0 0 1 .645-.259h5.846q.32 0 .534.215a.73.73 0 0 1 .216.535.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215H6.054l5.773 5.773a.3.3 0 0 0 .221.087.3.3 0 0 0 .221-.087l6.66-6.66a.7.7 0 0 1 .522-.22q.3.003.532.236.217.232.225.527a.7.7 0 0 1-.225.527l-6.654 6.654q-.27.27-.608.401a1.9 1.9 0 0 1-.673.131q-.337 0-.673-.13a1.75 1.75 0 0 1-.608-.402z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-missed-outgoing-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m19 10.021-5.767 5.768q-.272.27-.608.401a1.8 1.8 0 0 1-.673.131q-.336 0-.673-.13a1.75 1.75 0 0 1-.608-.402L4.017 9.135a.72.72 0 0 1-.212-.515.75.75 0 0 1 .212-.54.74.74 0 0 1 .535-.232q.302 0 .535.233l6.644 6.644a.3.3 0 0 0 .221.087q.134 0 .221-.087l5.773-5.773H13.75a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.32.216-.535a.73.73 0 0 1 .534-.215h5.846q.387 0 .645.259a.88.88 0 0 1 .259.645v5.846a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-missed-outgoing.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m19.25 9.942-6.224 6.224a1.5 1.5 0 0 1-.52.345q-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.522-.346L3.702 9.06a.78.78 0 0 1-.237-.552.78.78 0 0 1 .222-.565.82.82 0 0 1 1.151.003l7.08 7.087L18.2 8.75H13.93a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.562q0-.33.231-.56a.77.77 0 0 1 .565-.23h6.116q.324 0 .556.233.231.232.232.555v6.234q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.23-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-missed.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.751 9.942v4.246q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.229-.565V7.954a.76.76 0 0 1 .231-.555.77.77 0 0 1 .565-.232h6.117q.323 0 .555.233.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.555.23H5.8l6.284 6.282 7.11-7.11a.72.72 0 0 1 .557-.227.86.86 0 0 1 .568.25.8.8 0 0 1 .219.562.77.77 0 0 1-.238.55l-7.107 7.107q-.238.24-.52.346-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.521-.345z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-received-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.404 18.5a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V9.75q0-.318.216-.534A.73.73 0 0 1 6.25 9q.32 0 .535.216A.73.73 0 0 1 7 9.75v6.196L17.92 5.027a.72.72 0 0 1 .521-.212.7.7 0 0 1 .532.212q.217.217.217.527a.72.72 0 0 1-.217.527L8.053 17h6.197q.319 0 .534.215a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call-received.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.967 18.833a.76.76 0 0 1-.56-.231.78.78 0 0 1-.227-.565V9.771q0-.325.227-.557a.76.76 0 0 1 .563-.23q.334 0 .564.23.23.232.229.557v6.366L18.18 4.717a.7.7 0 0 1 .545-.236.8.8 0 0 1 .559.24.76.76 0 0 1 .241.554q0 .312-.241.546L7.867 17.25h6.367q.333 0 .564.232a.76.76 0 0 1 .232.558q0 .334-.232.564a.77.77 0 0 1-.564.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/cancel-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 13.054 3.073 3.073q.208.208.522.213a.7.7 0 0 0 .532-.213.72.72 0 0 0 .217-.527.72.72 0 0 0-.217-.527L13.054 12l3.073-3.073a.73.73 0 0 0 .213-.522.7.7 0 0 0-.213-.532.72.72 0 0 0-.527-.217.72.72 0 0 0-.527.217L12 10.946 8.927 7.873a.73.73 0 0 0-.522-.213.7.7 0 0 0-.532.213.72.72 0 0 0-.217.527q0 .31.217.527L10.946 12l-3.073 3.073a.73.73 0 0 0-.213.522.7.7 0 0 0 .213.532q.217.217.527.217a.72.72 0 0 0 .527-.217zm.002 8.446a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/index.tsx",
    "content": "export {default as Call} from './call';\nexport {default as CallEnd} from './call-end';\nexport {default as CallEndFill} from './call-end-fill';\nexport {default as CallFill} from './call-fill';\nexport {default as CallLog} from './call-log';\nexport {default as CallLogFill} from './call-log-fill';\nexport {default as CallMade} from './call-made';\nexport {default as CallMadeFill} from './call-made-fill';\nexport {default as CallMissed} from './call-missed';\nexport {default as CallMissedFill} from './call-missed-fill';\nexport {default as CallMissedOutgoing} from './call-missed-outgoing';\nexport {default as CallMissedOutgoingFill} from './call-missed-outgoing-fill';\nexport {default as CallReceived} from './call-received';\nexport {default as CallReceivedFill} from './call-received-fill';\nexport {default as CancelFill} from './cancel-fill';\nexport {default as PlayArrow} from './play-arrow';\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/calls/icons/play-arrow.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M8.117 17.313V6.578a.73.73 0 0 1 .24-.57.8.8 0 0 1 .56-.216q.096 0 .206.022a.7.7 0 0 1 .21.08l8.438 5.393q.18.133.273.298t.094.365a.7.7 0 0 1-.094.365.9.9 0 0 1-.273.29l-8.437 5.393a.6.6 0 0 1-.212.087 1 1 0 0 1-.201.023.81.81 0 0 1-.564-.216.74.74 0 0 1-.24-.579m1.583-1.43 6.184-3.933L9.7 8.017z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/helper/GroupListeners.ts",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUIKit} from '@cometchat/chat-uikit-react-native';\n\nexport const listners = {\n  addListener: {\n    groupListener: ({groupListenerId, handleGroupListener}: any) =>\n      CometChat.addGroupListener(\n        groupListenerId,\n        new CometChat.GroupListener({\n          onGroupMemberKicked: (\n            message: any,\n            kickedUser: any,\n            kickedBy: any,\n            kickedFrom: any,\n          ) => {\n            handleGroupListener(kickedFrom);\n          },\n          onGroupMemberBanned: (\n            message: any,\n            bannedUser: any,\n            bannedBy: any,\n            bannedFrom: any,\n          ) => {\n            handleGroupListener(bannedFrom);\n          },\n          onMemberAddedToGroup: (\n            message: any,\n            userAdded: any,\n            userAddedBy: any,\n            userAddedIn: any,\n          ) => {\n            handleGroupListener(userAddedIn);\n          },\n          onGroupMemberLeft: (message: any, leavingUser: any, group: any) => {\n            handleGroupListener(group);\n          },\n          onGroupMemberScopeChanged: (\n            message: any,\n            changedUser: CometChat.User,\n            newScope: any,\n            oldScope: any,\n            changedGroup: CometChat.Group,\n          ) => {\n            console.log('changedGroup: ', message, changedGroup);\n            if (changedUser.getUid() == CometChatUIKit.loggedInUser!.getUid()) {\n              changedGroup.setScope(newScope);\n              handleGroupListener(changedGroup);\n            }\n          },\n        }),\n      ),\n  },\n  removeListner: {\n    removeUserListener: ({userStatusListenerId}: any) =>\n      CometChat.removeUserListener(userStatusListenerId),\n\n    removeGroupListener: ({groupListenerId}: any) =>\n      CometChat.removeGroupListener(groupListenerId),\n  },\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/AddMember.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  NativeModules,\n  Platform,\n  KeyboardAvoidingView,\n  BackHandler,\n} from 'react-native';\nimport {\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  CometChatUsers,\n  CometChatUsersActionsInterface,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport {\n  useRoute,\n  useNavigation,\n  RouteProp,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport { styles } from './AddMemberStyles';\nimport { commonVars } from '@cometchat/chat-uikit-react-native/src/shared/base/vars';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { useConfig } from '../../../config/store';\nconst { CommonUtil } = NativeModules;\n\nconst AddMember: React.FC = () => {\n  const route = useRoute<RouteProp<RootStackParamList, 'AddMember'>>();\n  const navigation = useNavigation();\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const userRef = useRef<CometChatUsersActionsInterface>(null);\n  const [selectedUsers, setSelectedUsers] = useState<CometChat.User[]>([]);\n  const [errorToastVisible, setErrorToastVisible] = useState(false);\n  const [errorToastMessage, setErrorToastMessage] = useState('');\n  const errorTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const [kbOffset, setKbOffset] = React.useState(900);\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n    );\n\n  useEffect(() => {\n    return () => {\n      if (errorTimeoutRef.current) {\n        clearTimeout(errorTimeoutRef.current);\n      }\n    };\n  }, []);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  useLayoutEffect(() => {\n    if (Platform.OS === 'ios') {\n      if (Number.isInteger(commonVars.safeAreaInsets.top)) {\n        setKbOffset(commonVars.safeAreaInsets.top ?? 0);\n        return;\n      }\n\n      CommonUtil.getSafeAreaInsets().then((res: any) => {\n        if (Number.isInteger(res.top)) {\n          commonVars.safeAreaInsets.top = res.top;\n          commonVars.safeAreaInsets.bottom = res.bottom;\n          setKbOffset(res.top);\n        }\n      });\n    }\n  }, []);\n\n  const addMembersToGroup = useCallback(\n    async (users: Array<CometChat.User>) => {\n      try {\n        const membersList = users.map((item: CometChat.User) => {\n          const groupMember = new CometChat.GroupMember(\n            item.getUid(),\n            CometChat.GROUP_MEMBER_SCOPE.PARTICIPANT,\n          );\n          groupMember.setName(item.getName());\n          return groupMember;\n        });\n\n        const guid = group.getGuid();\n        const response = await CometChat.addMembersToGroup(\n          guid,\n          membersList,\n          [],\n        );\n\n        const addedUIDs = Object.entries(response)\n          .filter(([_, status]) => status === 'success')\n          .map(([uid]) => uid);\n\n        const addedMembers = membersList.filter(member =>\n          addedUIDs.includes(member.getUid()),\n        );\n\n        if (addedUIDs.length > 0) {\n          navigation.goBack();\n        } else {\n          setErrorToastMessage('Error, Unable to add members');\n          setErrorToastVisible(true);\n          errorTimeoutRef.current = setTimeout(() => {\n            setErrorToastVisible(false);\n          }, 3000);\n        }\n\n        // If all succeeded, emit individual events for each member\n        if (addedMembers.length > 0) {\n          const groupInfo  = await CometChat.getGroup(group.getGuid());\n          group.setMembersCount(groupInfo.getMembersCount());\n          // Create separate action for each added member\n          addedMembers.forEach(member => {\n            const action: CometChat.Action = new CometChat.Action(\n              guid,\n              CometChatUiKitConstants.MessageTypeConstants.groupMember,\n              CometChat.RECEIVER_TYPE.GROUP,\n              CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n            );\n            action.setConversationId(guid);\n            action.setActionBy(CometChatUIKit.loggedInUser!);\n            action.setActionFor(group);\n            action.setSender(CometChatUIKit.loggedInUser!);\n            // Initialize data to prevent crash when SDK accesses getData().metadata during render\n            action.setData({ metadata: {} });\n\n            // Emit individual event for each member added\n            CometChatUIEventHandler.emitGroupEvent(\n              CometChatGroupsEvents.ccGroupMemberAdded,\n              {\n                addedBy: CometChatUIKit.loggedInUser,\n                message: action,\n                usersAdded: [member],\n                userAddedIn: group,\n              },\n            );\n          });\n        }\n      } catch (error) {\n        console.error('Something went wrong', error);\n        setErrorToastMessage('Error, Unable to add members');\n        setErrorToastVisible(true);\n        errorTimeoutRef.current = setTimeout(() => {\n          setErrorToastVisible(false);\n        }, 2000);\n      }\n    },\n    [group, navigation],\n  );\n\n  const handleUserSelection = useCallback((users: CometChat.User[]) => {\n    setSelectedUsers(users);\n  }, []);\n\n  return (\n    <KeyboardAvoidingView\n      behavior=\"padding\"\n      enabled={Platform.OS === 'ios' ? true : false}\n      keyboardVerticalOffset={kbOffset}\n      style={{ flex: 1, backgroundColor: theme.color.background1 }}\n    >\n      {/* Header */}\n      <View style={{ flex: 1 }}>\n        <View\n          style={[\n            styles.addMemberContainer,\n            { borderBottomColor: theme.color.borderLight },\n          ]}\n        >\n          <TouchableOpacity\n            style={styles.iconContainer}\n            onPress={() => navigation.goBack()}\n          >\n            <Icon\n              icon={\n                <ArrowBack\n                  color={theme.color.iconPrimary}\n                  height={24}\n                  width={24}\n                />\n              }\n            />\n          </TouchableOpacity>\n          <Text\n            style={[\n              theme.typography.heading1.bold,\n              styles.addMemberText,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            {t('ADD_MEMBERS')}\n          </Text>\n        </View>\n\n        {/* Users List */}\n        <CometChatUsers\n          hideHeader={true}\n          ref={userRef}\n          usersRequestBuilder={new CometChat.UsersRequestBuilder()\n            .setLimit(30)\n            .hideBlockedUsers(false)\n            .setRoles([])\n            .friendsOnly(false)\n            .setStatus('')\n            .setTags([])\n            .sortBy('name')\n            .setUIDs([])}\n          selectionMode=\"multiple\"\n          onSelection={handleUserSelection}\n          showBackButton={true}\n          onBack={() => navigation.goBack()}\n          usersStatusVisibility={userAndFriendsPresence}\n        />\n\n        {/* Add Members Button */}\n        <TouchableOpacity\n          onPress={() => addMembersToGroup(selectedUsers)}\n          style={styles.addMembersButton}\n        >\n          <View\n            style={[\n              styles.addMembersButtonContainer,\n              { backgroundColor: theme.color.primaryButtonBackground },\n            ]}\n          >\n            <Text\n              style={[\n                theme.typography.heading4.medium,\n                { color: theme.color.primaryButtonText, alignSelf: 'center' },\n              ]}\n            >\n              {t('ADD_MEMBERS')}\n            </Text>\n          </View>\n        </TouchableOpacity>\n\n        {/* Error Toast */}\n        {errorToastVisible && (\n          <View\n            style={[\n              styles.toastContainer,\n              { backgroundColor: theme.color.error },\n            ]}\n          >\n            <Text style={styles.toastTextStyle}>{errorToastMessage}</Text>\n          </View>\n        )}\n      </View>\n    </KeyboardAvoidingView>\n  );\n};\n\nexport default AddMember;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/AddMemberStyles.tsx",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n    toastTextStyle: {\n      color: '#fff',\n      fontSize: 16,\n    },\n    toastContainer: {\n      position: 'absolute',\n      bottom: 20,\n      left: 20,\n      right: 20,\n      paddingVertical: 10,\n      borderRadius: 5,\n      alignItems: 'center',\n    },\n    addMemberContainer: {\n      paddingTop: 15,\n      paddingLeft: 10,\n      flexDirection: 'row',\n      paddingBottom: 15,\n      borderBottomWidth: 1,\n    },\n    addMemberText: {\n      paddingLeft: 10,\n    },\n    iconContainer: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    addMembersButton: {\n      alignContent: 'center',\n      justifyContent: 'center',\n      paddingVertical: 1,\n      height: 50,\n      width: '100%',\n      alignSelf: 'center',\n    },\n    addMembersButtonContainer: {\n      marginHorizontal: 20,\n      alignSelf: 'center',\n      justifyContent: 'center',\n      borderRadius: 6,\n      height: '75%',\n      width: '95%',\n    },\n  });\n  "
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/BannedMember.tsx",
    "content": "import React, { useCallback, useRef, useState } from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  Modal,\n  TouchableWithoutFeedback,\n  BackHandler,\n} from 'react-native';\nimport {\n  CometChatConfirmDialog,\n  CometChatList,\n  CometChatListActionsInterface,\n} from '@cometchat/chat-uikit-react-native/src/shared';\nimport { Skeleton } from '@cometchat/chat-uikit-react-native/src/CometChatUsers/Skeleton';\nimport {\n  Icon,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { ErrorEmptyView } from '@cometchat/chat-uikit-react-native/src/shared/views/ErrorEmptyView/ErrorEmptyView';\nimport { RouteProp, useFocusEffect } from '@react-navigation/native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  getBannedMemberStyleLight,\n  styles,\n  getBannedMemberStyleDark,\n} from './BannedMemberStyles';\nimport UserEmptyIcon from '../../../assets/icons/UserEmptyIcon';\nimport Close from '../../../assets/icons/Close';\nimport Block from '../../../assets/icons/Block';\n\ntype BannedMembersRouteProp = {\n  route: RouteProp<RootStackParamList, 'BannedMember'>;\n  navigation: StackNavigationProp<RootStackParamList, 'BannedMember'>;\n};\n\nconst BannedMember: React.FC<BannedMembersRouteProp> = ({\n  route,\n  navigation,\n}) => {\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const bannedListRef = useRef<CometChatListActionsInterface>(null);\n\n  const [isModalVisible, setModalVisible] = useState(false);\n  const [selectedUser, setSelectedUser] = useState<CometChat.User | null>(null);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  /**\n   * --- Callbacks / Handlers ---\n   */\n\n  const openUnbanModal = (user: CometChat.User) => {\n    setSelectedUser(user);\n    setModalVisible(true);\n  };\n\n  const closeUnbanModal = () => {\n    setModalVisible(false);\n    setSelectedUser(null);\n  };\n\n  const handleUnbanUser = async () => {\n    if (!group || !selectedUser) return;\n    try {\n      const guid = group.getGuid();\n      const uid = selectedUser.getUid();\n\n      // Unban the user\n      await CometChat.unbanGroupMember(guid, uid);\n\n      // Create and dispatch an Action message for the unban event\n      const actionMessage = new CometChat.Action(\n        guid,\n        CometChatUiKitConstants.MessageTypeConstants.groupMember,\n        CometChat.RECEIVER_TYPE.GROUP,\n        CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n      );\n      actionMessage.setConversationId(guid);\n      actionMessage.setActionFor(group);\n      actionMessage.setActionOn(selectedUser);\n      actionMessage.setActionBy(CometChatUIKit.loggedInUser!);\n      actionMessage.setSender(CometChatUIKit.loggedInUser!);\n      // Initialize data to prevent crash when SDK accesses getData().metadata during render\n      actionMessage.setData({ metadata: {} });\n      actionMessage.setMessage(\n        `${CometChatUIKit.loggedInUser?.getName()} ${t(\n          'UNBANNED',\n        )} ${selectedUser.getName()}`,\n      );\n      CometChatUIEventHandler.emitGroupEvent(\n        CometChatGroupsEvents.ccGroupMemberUnBanned,\n        {\n          unbannedBy: CometChatUIKit.loggedInUser,\n          userUnbanned: selectedUser,\n          group,\n          message: actionMessage,\n        },\n      );\n\n      // Remove from the banned list\n      bannedListRef.current?.removeItemFromList(uid);\n\n      // Close modal\n      closeUnbanModal();\n    } catch (error) {\n      console.error('Error unbanning user:', error);\n    }\n  };\n\n  /**\n   * --- Render Helpers ---\n   */\n\n  const renderEmptyView = useCallback(() => {\n    return (\n      <View style={styles.flexContainer}>\n        <ErrorEmptyView\n          title={t('NO_BANNED_MEMBERS_FOUND')}\n          Icon={\n            <Icon\n              icon={\n                <UserEmptyIcon\n                  color={theme.color.neutral300}\n                  height={100}\n                  width={100}\n                />\n              }\n              size={theme.spacing.spacing.s20}\n              containerStyle={{ marginBottom: theme.spacing.spacing.s5 }}\n            />\n          }\n          containerStyle={styles.emptyViewContainer}\n          titleStyle={[\n            theme.userStyles.emptyStateStyle.titleStyle,\n            { color: theme.color.textPrimary },\n          ]}\n        />\n      </View>\n    );\n  }, [theme]);\n\n  const renderLoadingView = () => <Skeleton />;\n\n  const renderTailView = (user: CometChat.User) => (\n    <TouchableOpacity onPress={() => openUnbanModal(user)}>\n      <Icon\n        icon={<Close height={24} width={24} color={theme.color.iconTertiary} />}\n      />\n    </TouchableOpacity>\n  );\n\n  const renderUnbanModal = () => {\n    if (!selectedUser) return null;\n\n    return (\n      <CometChatConfirmDialog\n        titleText={`${t('UNBAN')} ${selectedUser.getName()}`}\n        icon={<Block color={theme.color.error} height={40} width={40} />}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('UNBAN')}\n        messageText={t('UNBAN_SURE') + ' ' + selectedUser.getName()}\n        isOpen={true}\n        onCancel={closeUnbanModal}\n        onConfirm={handleUnbanUser}\n      />\n    );\n  };\n\n  /**\n   * --- Main JSX ---\n   */\n  return (\n    <>\n      <View\n        style={[\n          styles.flexContainer,\n          { backgroundColor: theme.color.background1 },\n        ]}\n      >\n        <CometChatList\n          title={t('BANNED_MEMBERS')}\n          hideStickyHeader={true}\n          ref={bannedListRef}\n          onBack={() => navigation.goBack()}\n          listItemKey=\"uid\"\n          hideBackButton={false}\n          LoadingView={renderLoadingView}\n          EmptyView={renderEmptyView}\n          TrailingView={renderTailView}\n          listStyle={\n            theme.mode === 'light'\n              ? getBannedMemberStyleLight(theme)\n              : getBannedMemberStyleDark(theme)\n          }\n          requestBuilder={new CometChat.BannedMembersRequestBuilder(\n            group.getGuid(),\n          ).setLimit(30)}\n        />\n      </View>\n      {renderUnbanModal()}\n    </>\n  );\n};\n\nexport default BannedMember;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/BannedMemberStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\nimport {\n  CometChatListStylesInterface,\n  CometChatTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {deepMerge} from '@cometchat/chat-uikit-react-native/src/shared/helper/helperFunctions';\nimport {DeepPartial} from '@cometchat/chat-uikit-react-native/src/shared/helper/types';\nimport {ColorValue, ViewStyle} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  flexContainer: {\n    flex: 1,\n  },\n  emptyViewContainer: {\n    flex: 1,\n    justifyContent: 'center',\n    alignItems: 'center',\n    paddingHorizontal: '10%',\n  },\n\n  /* Modal Styles */\n  modalOverlay: {\n    flex: 1,\n    backgroundColor: 'rgba(0, 0, 0, 0.5)',\n  },\n  modalContainer: {\n    position: 'absolute',\n    top: '30%',\n    left: '2%',\n    right: '2%',\n    borderRadius: 10,\n    padding: 20,\n    elevation: 5,\n  },\n  modalIconContainer: {\n    alignSelf: 'center',\n    marginBottom: 10,\n    width: 64,\n    height: 64,\n    borderRadius: 32,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  modalContentContainer: {\n    alignItems: 'center',\n  },\n  modalTitle: {\n    marginBottom: 15,\n  },\n  modalDesc: {\n    textAlign: 'center',\n    marginBottom: 20,\n  },\n  buttonContainer: {\n    flexDirection: 'row',\n  },\n  cancelButton: {\n    flex: 1,\n    paddingVertical: 10,\n    marginHorizontal: 5,\n    borderRadius: 5,\n    borderWidth: 1,\n    alignItems: 'center',\n  },\n  unbanButton: {\n    flex: 1,\n    paddingVertical: 10,\n    marginHorizontal: 5,\n    borderRadius: 5,\n    alignItems: 'center',\n  },\n});\n\nexport type BannedMemberStyle = CometChatListStylesInterface & {\n  skeletonStyle: {\n    backgroundColor: ColorValue;\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n  };\n  headerContainerStyle: ViewStyle;\n};\n\nexport const getBannedMemberStyleLight = (\n  theme: CometChatTheme,\n): DeepPartial<BannedMemberStyle> => {\n  const {color, spacing, typography} = theme;\n  return {\n    headerContainerStyle: {\n      alignItems: 'flex-start',\n      justifyContent: 'center',\n      width: '100%',\n      borderRadius: 0,\n      paddingHorizontal: 0,\n    },\n    titleSeparatorStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n      flexDirection: 'row',\n      justifyContent: 'space-between',\n      alignItems: 'center',\n      width: '100%',\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      flex: 1,\n    },\n    itemStyle: {\n      containerStyle: {\n        flexDirection: 'row',\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        gap: spacing.spacing.s3,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.body.regular,\n      },\n      statusIndicatorStyle: {},\n      avatarStyle: {\n        containerStyle: {},\n        textStyle: {},\n        imageStyle: {},\n      },\n      headViewContainerStyle: {},\n      titleSubtitleContainerStyle: {\n        alignSelf: 'center',\n      },\n      trailingViewContainerStyle: {\n        alignSelf: 'center',\n      },\n    },\n    confirmSelectionStyle: {},\n    selectionCancelStyle: {},\n    loadingIconTint: color.primary,\n    sectionHeaderTextStyle: {\n      marginHorizontal: spacing.spacing.s5,\n      color: color.primary,\n      ...typography.heading4.medium,\n    },\n    onlineStatusColor: color.success,\n    titleViewStyle: {\n      paddingVertical: spacing.spacing.s3,\n      paddingLeft: spacing.spacing.s3,\n      margin: spacing.spacing.s0,\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      ...typography.heading1.bold,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    searchStyle: {\n      textStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.regular,\n        textAlignVertical: 'center',\n        paddingVertical: 0,\n        height: spacing.spacing.s7,\n      },\n      containerStyle: {\n        backgroundColor: color.background3,\n        paddingVertical: spacing.spacing.s3,\n        marginTop: spacing.spacing.s3,\n        width: '95%',\n        gap: spacing.spacing.s1,\n        alignContent: 'space-around',\n        alignSelf: 'center',\n        flexDirection: 'row',\n        alignItems: 'center',\n      },\n      icon: undefined,\n      iconStyle: {\n        tintColor: color.iconSecondary,\n      },\n      placehodlerTextStyle: {\n        color: color.textTertiary,\n      },\n    },\n    emptyStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: 'center' as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: 'center',\n        display: 'none',\n        alignItems: 'center',\n        padding: spacing.padding.p3,\n      },\n    },\n    errorStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: 'center' as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: 'center',\n        alignItems: 'center',\n        padding: spacing.padding.p3,\n      },\n    },\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: ['#E8E8E8', '#F5F5F5'] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  };\n};\n\nexport const getBannedMemberStyleDark = (\n  theme: CometChatTheme,\n): DeepPartial<BannedMemberStyle> => {\n  const {color, spacing, typography} = theme;\n  return deepMerge(getBannedMemberStyleLight(theme), {\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: ['#383838', '#272727'] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  });\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/Conversations.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport React, {useCallback, useContext, useRef, useState} from 'react';\nimport {TouchableOpacity, View, Platform} from 'react-native';\nimport {\n  CometChatAvatar,\n  CometChatConversations,\n  CometChatUIKit,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {AuthContext} from '../../../navigation/AuthContext';\nimport {\n  useFocusEffect,\n  useNavigation,\n  CommonActions,\n} from '@react-navigation/native';\nimport {TooltipMenu} from '../../../utils/TooltipMenu';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../../navigation/types';\nimport AccountCircle from '../../../assets/icons/AccountCircle';\nimport AddComment from '../../../assets/icons/AddComment';\nimport InfoIcon from '../../../assets/icons/InfoIcon';\nimport Logout from '../../../assets/icons/Logout';\nimport {navigate, navigationRef} from '../../../navigation/NavigationService';\nimport {AppConstants, SCREEN_CONSTANTS} from '../../../utils/AppConstants';\nimport Builder from '../../../assets/icons/Builder';\nimport { useConfig, useConfigStore } from '../../../config/store'; // adjust import if needed\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport Reset from '../../../assets/icons/Reset';\nimport AiIcon from '../../../assets/icons/AiIcon';\n\ntype ChatNavigationProp = StackNavigationProp<\n  RootStackParamList,\n  'Conversation'\n>;\n\nconst Conversations: React.FC<{}> = ({}) => {\n  const theme = useTheme();\n  const {setIsLoggedIn: setLogout} = useContext(AuthContext);\n  const [isLoggingOut, setIsLoggingOut] = useState(false);\n  const tooltipPositon = React.useRef({pageX: 0, pageY: 0});\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const selectedConversation = useRef<CometChat.Conversation | null>(null);\n  const navigation = useNavigation<ChatNavigationProp>();\n  const avatarContainerRef = useRef<View>(null);\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser,\n  ).current;\n  const { t } = useCometChatTranslation();\n\n  const [isConfigUpdated, setIsConfigUpdated] = useState(false);\n  const messageDeliveryAndReadReceipts = useConfig(\n        (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n\n  useFocusEffect(\n    useCallback(() => {\n      // Check config updated flag\n      AsyncStorage.getItem('@config_updated').then(val => {\n        setIsConfigUpdated(val === 'true');\n      });\n      return () => {\n        setTooltipVisible(false);\n      };\n    }, []),\n  );\n\n  const handleResetConfig = async () => {\n    useConfigStore.getState().resetConfig();\n    await AsyncStorage.removeItem('@config_updated');\n    setIsConfigUpdated(false);\n  };\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n    );\n    \n  const openMessagesFor = (item: CometChat.Conversation) => {\n    // Determine if it's a user or group conversation\n    const isUser = item.getConversationType() === 'user';\n    const isGroup = item.getConversationType() === 'group';\n\n    // Navigate to Messages with appropriate params\n    navigation.navigate('Messages', {\n      user: isUser ? (item.getConversationWith() as CometChat.User) : undefined,\n      group: isGroup\n        ? (item.getConversationWith() as CometChat.Group)\n        : undefined,\n    });\n  };\n\n  const _conversationsConfig = {\n    onItemPress: openMessagesFor,\n    onError: (err: any) => {\n      console.log('ERROR IN CONVO: ', err);\n    },\n  };\n\n  const handleAvatarPress = () => {\n    try {\n      if (avatarContainerRef.current) {\n        avatarContainerRef.current.measureInWindow((x, y, height) => {\n          // Set tooltip position 10px below the avatar\n          tooltipPositon.current = {pageX: x, pageY: y + height};\n        });\n        selectedConversation.current = null;\n        setTooltipVisible(true);\n      }\n    } catch (error) {\n      console.error('Error while handling avatar press:', error);\n    }\n  };\n\n  const handleLogout = async () => {\n    if (isLoggingOut) return;\n    setIsLoggingOut(true);\n\n    // Step 1: Logout from CometChat\n    try {\n      await CometChat.logout();\n    } catch (error) {\n      console.error('CometChat logout failed:', error);\n      setIsLoggingOut(false);\n      return; // Exit if CometChat logout fails\n    }\n\n    // If all operations succeed, navigate to the LoginScreen\n    setIsLoggingOut(false);\n    setLogout(false);\n    navigationRef.dispatch(\n      CommonActions.reset({\n        index: 0,\n        routes: [{ name: 'SampleUser' }],\n      }),\n    );\n  };\n\n  const NewConversation = () => {\n    return (\n      <View ref={avatarContainerRef}>\n        <TouchableOpacity\n          onPress={() => {\n            handleAvatarPress();\n          }}>\n          <CometChatAvatar\n            style={{\n              containerStyle: {\n                height: 40,\n                width: 40,\n                justifyContent: 'center',\n                alignItems: 'center',\n                overflow: 'hidden',\n              },\n              textStyle: {\n                fontSize: 22,\n                lineHeight: 28,\n                textAlign: 'center',\n              },\n            }}\n            image={\n              loggedInUser?.getAvatar()\n                ? {uri: loggedInUser?.getAvatar()}\n                : undefined\n            }\n            name={loggedInUser?.getName() ?? ''}\n          />\n        </TouchableOpacity>\n      </View>\n    );\n  };\n\n  return (\n    <View style={{flex: 1}}>\n      <View style={{flex: 1}}>\n        <CometChatConversations\n          {..._conversationsConfig}\n          AppBarOptions={NewConversation}\n          selectionMode=\"none\"\n          showSearchBar={true}\n          onSearchBarClicked={() => {\n            navigate(SCREEN_CONSTANTS.SEARCH_MESSAGES);\n          }}\n          usersStatusVisibility={userAndFriendsPresence}\n          receiptsVisibility={messageDeliveryAndReadReceipts}\n        />\n      </View>\n\n      <View\n        style={{\n          position: 'absolute',\n          top: tooltipPositon.current.pageY,\n          left: tooltipPositon.current.pageX,\n          zIndex: 9999,\n        }}>\n        <TooltipMenu\n          visible={tooltipVisible}\n          onClose={() => {\n            setTooltipVisible(false);\n          }}\n          onDismiss={() => {\n            setTooltipVisible(false);\n          }}\n          event={{\n            nativeEvent: tooltipPositon.current,\n          }}\n          menuItems={[\n            {\n              text: t('CREATE_CONVERSATION'),\n              onPress: () => {\n                navigation.navigate('CreateConversation');\n              },\n              icon: (\n                <AddComment\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AddComment>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: t('AI_ASSISTANTS'),\n              onPress: () => {\n                navigation.navigate(SCREEN_CONSTANTS.AI_AGENTS);\n              },\n              icon: (\n                <AiIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AiIcon>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: loggedInUser?.getName() || 'User',\n              onPress: () => {\n                setTooltipVisible(false);\n              },\n              icon: (\n                <AccountCircle\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AccountCircle>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: t('LOGOUT'),\n              onPress: () => {\n                handleLogout();\n              },\n              icon: (\n                <Logout\n                  height={24}\n                  width={24}\n                  color={theme.color.error}></Logout>\n              ),\n              textColor: theme.color.error,\n              iconColor: theme.color.error,\n            },\n            {\n              text: AppConstants.versionNumber,\n              onPress: () => {},\n              icon: (\n                <InfoIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></InfoIcon>\n              ),\n            },\n            isConfigUpdated\n              ? {\n                text: 'Reset to Default',\n                onPress: handleResetConfig,\n                icon: (\n                  <Reset\n                    height={24}\n                    width={24}\n                    color={theme.color.textPrimary}\n                  />\n                ),\n                textColor: theme.color.textPrimary,\n                iconColor: theme.color.textPrimary,\n              }\n              : {\n                text: 'Builder Live Preview',\n                onPress: () => {\n                  navigation.navigate(SCREEN_CONSTANTS.QR_SCREEN);\n                },\n                icon: (\n                  <Builder\n                    height={24}\n                    width={24}\n                    color={theme.color.textPrimary}\n                  />\n                ),\n                textColor: theme.color.textPrimary,\n                iconColor: theme.color.textPrimary,\n              },\n          ]}\n        />\n      </View>\n    </View>\n  );\n};\n\nexport default Conversations;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/CreateConversation.tsx",
    "content": "import React, { useState } from 'react';\nimport { View, Text, TouchableOpacity, StyleSheet } from 'react-native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RouteProp } from '@react-navigation/native';\nimport {\n  useTheme,\n  CometChatUsers,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport Groups from '../../groups/Groups';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\n\n// Define prop types for the component using React Navigation's types\ntype Props = {\n  route: RouteProp<RootStackParamList, 'CreateConversation'>;\n  navigation: StackNavigationProp<RootStackParamList, 'CreateConversation'>;\n};\n\nconst CreateConversation: React.FC<Props> = ({ route, navigation }) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const {\n    background1,\n    background3,\n    iconPrimary,\n    textPrimary,\n    textSecondary,\n    primary,\n  } = theme.color;\n  const { heading1 } = theme.typography;\n  const [selectedTab, setSelectedTab] = useState<'Users' | 'Groups'>('Users');\n\n  return (\n    <View style={[styles.container, { backgroundColor: background1 }]}>\n      {/* Header */}\n      <View style={styles.header}>\n        <TouchableOpacity style={styles.rowCenter} onPress={navigation.goBack}>\n          <Icon\n            icon={<ArrowBack color={iconPrimary} height={24} width={24} />}\n          />\n        </TouchableOpacity>\n        <Text\n          style={[heading1.bold, styles.headerText, { color: textPrimary }]}\n        >\n          {t('NEW_CHAT')}\n        </Text>\n      </View>\n\n      {/* Tab Bar */}\n      <View style={[styles.tabContainer, { backgroundColor: background3 }]}>\n        {['Users', 'Groups'].map(tab => (\n          <TouchableOpacity\n            key={tab}\n            style={[\n              styles.tabButton,\n              selectedTab === tab && styles.activeTab,\n              selectedTab === tab && { backgroundColor: background1 },\n            ]}\n            onPress={() => setSelectedTab(tab as 'Users' | 'Groups')}\n          >\n            <Text\n              style={[\n                styles.tabText,\n                { color: selectedTab === tab ? primary : textSecondary },\n              ]}\n            >\n              {t(tab.toUpperCase())}\n            </Text>\n          </TouchableOpacity>\n        ))}\n      </View>\n\n      {/* Content */}\n      <View style={styles.content}>\n        {selectedTab === 'Users' ? (\n          <CometChatUsers\n            usersRequestBuilder={new CometChat.UsersRequestBuilder()\n              .setLimit(30)\n              .hideBlockedUsers(false)\n              .setRoles([])\n              .friendsOnly(false)\n              .setStatus('')\n              .setTags([])\n              .sortBy('name')\n              .setUIDs([])}\n            hideHeader\n            onItemPress={(user: CometChat.User) =>\n              navigation.navigate('Messages', { user })\n            }\n          />\n        ) : (\n          <Groups hideHeader />\n        )}\n      </View>\n    </View>\n  );\n};\n\nexport default CreateConversation;\n\n// Style definitions for the component\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  header: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  rowCenter: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  headerText: {\n    paddingLeft: 5,\n  },\n  tabContainer: {\n    marginTop: 20,\n    flexDirection: 'row',\n    marginHorizontal: 20,\n    borderRadius: 30,\n    padding: 5,\n  },\n  tabButton: {\n    flex: 1,\n    paddingVertical: 10,\n    alignItems: 'center',\n    borderRadius: 30,\n  },\n  activeTab: {\n    borderRadius: 30,\n  },\n  tabText: {\n    fontSize: 16,\n    fontWeight: 'bold',\n  },\n  content: {\n    flex: 1,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/GroupInfo.tsx",
    "content": "import React, { useEffect, useRef, useState } from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  useWindowDimensions,\n  BackHandler,\n} from 'react-native';\nimport { RouteProp, useFocusEffect } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  CometChatAvatar,\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatConfirmDialog,\n  useTheme,\n  CometChatConversationEvents,\n  CometChatUIEvents,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatUIKit,\n  CometChatUiKitConstants,\n} from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { listners } from '../helper/GroupListeners';\nimport { styles } from './GroupInfoStyles';\nimport { leaveGroup } from '../../../utils/helper';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport Group from '../../../assets/icons/Group';\nimport PersonAdd from '../../../assets/icons/PersonAdd';\nimport PersonOff from '../../../assets/icons/PersonOff';\nimport Block from '../../../assets/icons/Block';\nimport Delete from '../../../assets/icons/Delete';\nimport { useConfig } from '../../../config/store';\n\ntype GroupInfoProps = {\n  route: RouteProp<RootStackParamList, 'GroupInfo'>;\n  navigation: StackNavigationProp<RootStackParamList, 'GroupInfo'>;\n};\n\nconst GroupInfo: React.FC<GroupInfoProps> = ({route, navigation}) => {\n  const addMembersToGroups = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.addMembersToGroups\n  );\n  const joinLeaveGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.joinLeaveGroup\n  );\n  const deleteGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.deleteGroup\n  );\n  const viewGroupMembers = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.viewGroupMembers\n  );\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const groupListenerId = useRef('groupListener' + new Date().getTime());\n\n  const [data, setData] = useState({ groupDetails: group });\n  const [userScope, setUserScope] = useState(\n    group?.getOwner() === CometChatUIKit.loggedInUser?.getUid()\n      ? CometChatUiKitConstants.GroupMemberScope.owner\n      : group?.getScope(),\n  );\n\n  // Separate states for each type of modal\n  const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false);\n  const [isOwnerLeaveModalOpen, setIsOwnerLeaveModalOpen] = useState(false);\n  const [isDeleteExitModalOpen, setIsDeleteExitModalOpen] = useState(false);\n  const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);\n\n  const { width } = useWindowDimensions();\n  const isSmallDevice = width < 360;\n\n  useEffect(() => {\n    // Update group details in state whenever group changes\n    const handleGroupListener = (updatedGroup: CometChat.Group) => {\n      if (updatedGroup.getGuid() === route.params.group.getGuid()) {\n        setData({ groupDetails: updatedGroup });\n        setUserScope(\n          updatedGroup?.getOwner() === CometChatUIKit.loggedInUser?.getUid()\n            ? CometChatUiKitConstants.GroupMemberScope.owner\n            : (updatedGroup?.getScope() ?? userScope),\n        );\n      }\n    };\n\n    const handleGroupMemberKicked = ({ kickedFrom }: any) => {\n      handleGroupListener(CommonUtils.clone(kickedFrom));\n    };\n    const handleGroupMemberBanned = ({ kickedFrom }: any) => {\n      handleGroupListener(CommonUtils.clone(kickedFrom));\n    };\n    const handleGroupMemberAdded = ({ userAddedIn }: any) => {\n      handleGroupListener(CommonUtils.clone(userAddedIn));\n    };\n    const handleOwnershipChanged = ({ group }: any) => {\n      handleGroupListener(group);\n    };\n\n    // Add group listeners\n    listners.addListener.groupListener({\n      groupListenerId: groupListenerId.current,\n      handleGroupListener,\n    });\n\n    CometChatUIEventHandler.addGroupListener(groupListenerId.current, {\n      ccGroupMemberKicked: (item: any) => handleGroupMemberKicked(item),\n      ccGroupMemberBanned: (item: any) => handleGroupMemberBanned(item),\n      ccGroupMemberAdded: (item: any) => handleGroupMemberAdded(item),\n      ccOwnershipChanged: (item: any) => handleOwnershipChanged(item),\n    });\n\n    return () => {\n      // Cleanup\n      listners.removeListner.removeGroupListener({\n        groupListenerId: groupListenerId.current,\n      });\n      CometChatUIEventHandler.removeGroupListener(groupListenerId.current);\n      CometChat.removeGroupListener(groupListenerId.current);\n    };\n  }, [group, userScope]);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  const getLabel = (key: string) => {\n    const label = t(key);\n    // Split into two words if device is small\n    if (isSmallDevice && label.split(' ').length === 2) {\n      return label.split(' ').join('\\n');\n    }\n    return label;\n  };\n\n  /**\n   * Handlers for each modal's confirm action\n   */\n\n  // 1) Normal \"Leave Group\" confirm\n  const handleLeaveConfirm = () => {\n    setIsLeaveModalOpen(false);\n    if (data.groupDetails) {\n      leaveGroup(data.groupDetails, navigation, 2);\n    }\n  };\n\n  // 2) \"Owner => Transfer Ownership\" confirm\n  const handleOwnerLeaveConfirm = () => {\n    if (!data.groupDetails) return;\n\n    setIsOwnerLeaveModalOpen(false);\n    navigation.navigate('TransferOwnershipSection', {\n      group: data.groupDetails,\n    });\n  };\n\n  // 3) \"Delete and Exit\" confirm\n  const handleDeleteExitConfirm = () => {\n    setIsDeleteExitModalOpen(false);\n    if (!data.groupDetails) return;\n    // Delete group\n    CometChat.deleteGroup(data.groupDetails.getGuid())\n      .then(() => {\n        navigation.pop(2);\n      })\n      .catch(error => {\n        console.log('Group deletion failed:', error);\n      });\n    // Emit group deleted event\n    CometChatUIEventHandler.emitGroupEvent(\n      CometChatGroupsEvents.ccGroupDeleted,\n      {\n        group: data.groupDetails,\n      },\n    );\n  };\n\n  /** DELETE CONVERSATION LOGIC **/\n  const handleDeleteConversationConfirm = () => {\n    setDeleteModalOpen(false); // close the dialog\n    if (group) {\n      CometChat.getConversation(group.getGuid(), 'group')\n        .then(conversation => {\n          CometChat.deleteConversation(group.getGuid(), 'group')\n            .then(deletedConversation => {\n              console.log(deletedConversation);\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccConversationDeleted,\n                { conversation: conversation },\n              );\n              navigation.pop(2);\n            })\n            .catch(error => {\n              console.log('Error while deleting conversation:', error);\n            });\n        })\n        .catch(error => {\n          console.log('Error while deleting conversation:', error);\n        });\n    }\n  };\n\n  return (\n    <View\n      style={[styles.flexOne, { backgroundColor: theme.color.background1 }]}\n    >\n      {/* Header */}\n      <View style={styles.headerContainer}>\n        <TouchableOpacity\n          style={styles.iconContainer}\n          onPress={() => navigation.goBack()}\n        >\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.pL5,\n            { color: theme.color.textPrimary },\n          ]}\n        >\n          {t('GROUP_INFO')}\n        </Text>\n      </View>\n\n      {/* Main Group Info Container */}\n      <View\n        style={[\n          styles.groupInfoSection,\n          { borderColor: theme.color.borderLight },\n        ]}\n      >\n        <View style={styles.infoTitleContainer}>\n          <CometChatAvatar\n            style={{\n              containerStyle: styles.avatarContainer,\n              textStyle: styles.avatarText,\n              imageStyle: styles.avatarImage,\n            }}\n            image={\n              data.groupDetails?.getIcon()\n                ? { uri: data.groupDetails?.getIcon() }\n                : undefined\n            }\n            name={data.groupDetails?.getName() ?? ''}\n          />\n          <View style={styles.ellipseTail}>\n            <Text\n              style={[\n                theme.typography.heading3.medium,\n                styles.titleName,\n                { color: theme.color.textPrimary },\n              ]}\n              numberOfLines={1}\n              ellipsizeMode=\"tail\"\n            >\n              {data.groupDetails?.getName()}\n            </Text>\n          </View>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.boxLabel,\n              { color: theme.color.textSecondary },\n            ]}\n          >\n            {data.groupDetails?.getMembersCount() +\n              ' ' +\n              t(\n                data.groupDetails?.getMembersCount() === 1\n                  ? 'MEMBER'\n                  : 'MEMBERS',\n              )}\n          </Text>\n        </View>\n\n        {/* Action Boxes: Add Members / View Members / Banned Members */}\n        <View style={styles.boxContainerRow}>\n          {/* Add Members */}\n          {addMembersToGroups &&\n            [CometChatUiKitConstants.GroupMemberScope.owner,\n            CometChatUiKitConstants.GroupMemberScope.admin\n            ].includes(userScope) && (\n              <TouchableOpacity\n                onPress={() => {\n                  navigation.navigate('AddMember', { group });\n                }}\n                style={[\n                  styles.buttonContainer,\n                  { borderColor: theme.color.borderDefault },\n                ]}\n              >\n                <Icon\n                  icon={\n                    <PersonAdd color={theme.color.primary} height={24} width={24} />\n                  }\n                  containerStyle={styles.buttonIcon}\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.boxLabel,\n                    { color: theme.color.textSecondary },\n                  ]}\n                >\n                  {getLabel('ADD_MEMBERS')}\n                </Text>\n              </TouchableOpacity>\n            )}\n\n          {/* View Members */}\n          {viewGroupMembers && (\n          <TouchableOpacity\n            onPress={() => {\n              navigation.navigate('ViewMembers', { group });\n            }}\n            style={[\n              styles.buttonContainer,\n              { borderColor: theme.color.borderDefault },\n            ]}\n          >\n            <Icon\n              icon={\n                <Group color={theme.color.primary} height={24} width={24} />\n              }\n              containerStyle={styles.buttonIcon}\n            />\n            <Text\n              style={[\n                theme.typography.caption1.regular,\n                styles.boxLabel,\n                { color: theme.color.textSecondary },\n              ]}\n            >\n              {getLabel('VIEW_MEMBERS')}\n            </Text>\n          </TouchableOpacity>\n          )}\n\n          {/* Banned Members */}\n          {(userScope === CometChatUiKitConstants.GroupMemberScope.owner ||\n            userScope === CometChatUiKitConstants.GroupMemberScope.admin ||\n            userScope ===\n              CometChatUiKitConstants.GroupMemberScope.moderator) && (\n            <TouchableOpacity\n              onPress={() => {\n                navigation.navigate('BannedMember', { group });\n              }}\n              style={[\n                styles.buttonContainer,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            >\n              <Icon\n                icon={\n                  <PersonOff\n                    color={theme.color.primary}\n                    height={24}\n                    width={24}\n                  />\n                }\n                containerStyle={styles.buttonIcon}\n              />\n              <Text\n                style={[\n                  theme.typography.caption1.regular,\n                  styles.boxLabel,\n                  { color: theme.color.textSecondary },\n                ]}\n              >\n                {getLabel('BANNED_MEMBERS')}\n              </Text>\n            </TouchableOpacity>\n          )}\n        </View>\n      </View>\n\n      {/* Actions */}\n      <View style={styles.actionContainer}>\n        <View style={styles.actionButtons}>\n          <TouchableOpacity\n            onPress={() => setDeleteModalOpen(true)}\n            style={styles.iconContainer}\n          >\n            <Icon\n              icon={<Delete color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.mL5,\n                { color: theme.color.error },\n              ]}\n            >\n              {t('DELETE_CHAT_TEXT')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n        {/* If user is owner but group has multiple members => must TRANSFER ownership.\n           Otherwise => normal leave.  */}\n        {joinLeaveGroup &&\n          (data.groupDetails.getMembersCount() > 1 ||\n            userScope !== CometChatUiKitConstants.GroupMemberScope.owner) && (\n            <View style={styles.actionButtons}>\n              <TouchableOpacity\n                onPress={() => {\n                  if (userScope === CometChatUiKitConstants.GroupMemberScope.owner) {\n                    setIsOwnerLeaveModalOpen(true);\n                  } else {\n                    setIsLeaveModalOpen(true);\n                  }\n                }}\n                style={styles.iconContainer}\n              >\n                <Icon icon={<Block color={theme.color.error} height={24} width={24} />} />\n                <Text\n                  style={[\n                    theme.typography.heading4.regular,\n                    styles.mL5,\n                    { color: theme.color.error },\n                  ]}\n                >\n                  {t('LEAVE')}\n                </Text>\n              </TouchableOpacity>\n            </View>\n          )}\n\n        {/* Delete and Exit (Group owner only) */}\n        {deleteGroup && \n        [\n          CometChatUiKitConstants.GroupMemberScope.owner,\n          CometChatUiKitConstants.GroupMemberScope.admin,\n        ].includes(userScope) && (\n          <View style={styles.actionButtons}>\n            <TouchableOpacity\n              onPress={() => setIsDeleteExitModalOpen(true)}\n              style={styles.iconContainer}\n            >\n              <Icon\n                icon={\n                  <Delete color={theme.color.error} height={24} width={24} />\n                }\n              />\n              <Text\n                style={[\n                  theme.typography.heading4.regular,\n                  styles.mL5,\n                  { color: theme.color.error },\n                ]}\n              >\n                {t('DELETE_AND_EXIT')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        )}\n      </View>\n\n      {/* ============== LEAVE GROUP (Regular) Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isLeaveModalOpen}\n        onCancel={() => setIsLeaveModalOpen(false)}\n        onConfirm={handleLeaveConfirm}\n        titleText={t('LEAVE_GROUP_TEXT')}\n        messageText={t('LEAVE_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('LEAVE')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* ============== TRANSFER OWNERSHIP Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isOwnerLeaveModalOpen}\n        onCancel={() => setIsOwnerLeaveModalOpen(false)}\n        onConfirm={handleOwnerLeaveConfirm}\n        titleText={t('TRANSFER_OWNERSHIP')}\n        messageText={t('TRANSFER_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('TRANSFER')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* ============== DELETE AND EXIT Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteExitModalOpen}\n        onCancel={() => setIsDeleteExitModalOpen(false)}\n        onConfirm={handleDeleteExitConfirm}\n        titleText={`${t('DELETE_AND_EXIT')}?`}\n        messageText={t('DELETE_AND_EXIT_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE_AND_EXIT')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* =============== DELETE CHAT MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteModalOpen}\n        onCancel={() => setDeleteModalOpen(false)}\n        onConfirm={handleDeleteConversationConfirm}\n        onDismiss={() => console.log('Delete Modal dismissed')}\n        titleText={t('DELETE_CHAT')}\n        messageText={t('SURE_TO_DELETE_CHAT')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n    </View>\n  );\n};\n\nexport default GroupInfo;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/GroupInfoStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  headerContainer: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  ellipseTail: {paddingHorizontal: 10, width: '80%'},\n  iconContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  pL5: {\n    paddingLeft: 5,\n  },\n  groupInfoSection: {\n    marginTop: 12,\n    borderTopWidth: 1,\n    borderBottomWidth: 1,\n    paddingVertical: 10,\n  },\n  infoTitleContainer: {\n    alignSelf: 'center',\n    alignItems: 'center',\n    marginTop: 20,\n  },\n  avatarContainer: {\n    height: 120,\n    width: 120,\n  },\n  avatarText: {\n    fontSize: 28,\n    lineHeight: 55,\n  },\n  avatarImage: {\n    height: '100%',\n    width: '100%',\n  },\n  titleName: {\n    marginTop: 10,\n    // width:'20%',\n    alignSelf: 'center',\n  },\n  boxLabel: {\n    marginTop: 5,\n    textAlign: 'center',\n    alignSelf: 'center',\n  },\n  boxContainerRow: {\n    flexDirection: 'row',\n    paddingHorizontal: 20,\n    justifyContent: 'space-between',\n    marginVertical: 20,\n  },\n  buttonContainer: {\n    flex: 1,\n    paddingVertical: 10,\n    borderWidth: 1,\n    borderRadius: 8,\n    justifyContent: 'center',\n    alignItems: 'center',\n    marginHorizontal: 5,\n  },\n  buttonIcon: {\n    marginBottom: 5,\n  },\n  actionContainer: {\n    paddingTop: 10,\n    gap: 4,\n  },\n  actionButtons: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 8,\n    paddingLeft: 20,\n    width: '100%',\n  },\n  mL5: {\n    marginLeft: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/Messages.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  TouchableOpacity,\n  View,\n  StyleSheet,\n  Text,\n  BackHandler,\n  Platform,\n  Modal,\n  Animated,\n  Dimensions,\n  AppState,\n  AppStateStatus,\n} from 'react-native';\nimport {\n  CometChatUIKit,\n  CometChatMessageHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  CometChatMessageComposer,\n  useTheme,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  ChatConfigurator,\n  useCometChatTranslation,\n  Icon,\n  CometChatAIAssistantChatHistory,\n  CometChatAIAssistantTools,\n  CometChatThemeProvider,\n  stopStreamingForRunId,\n} from '@cometchat/chat-uikit-react-native';\nimport { StackScreenProps } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport InfoIcon from '../../../assets/icons/InfoIcon';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport Info from '../../../assets/icons/Info';\nimport {useActiveChat} from '../../../utils/ActiveChatContext';\nimport { useConfig } from '../../../config/store';\nimport { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\n\nconst { width } = Dimensions.get('window');\n\ntype Props = StackScreenProps<RootStackParamList, 'Messages'>;\n\nconst Messages: React.FC<Props> = ({ route, navigation }) => {\n  const {\n    user,\n    group,\n    fromMention = false,\n    fromMessagePrivately = false,\n    parentMessageId: routeParentMessageId,\n    messageId,\n    searchKeyword,\n    navigatedFromSearch\n  } = route.params;\n  const typingIndicator = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.typingIndicator\n  );\n  const threadConversationAndReplies = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.threadConversationAndReplies\n  );\n  const photosSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing\n  );\n  const videoSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing\n  );\n  const audioSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing\n  );\n  const fileSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing\n  );\n  const editMessage = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.editMessage\n  );\n  const deleteMessage = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.deleteMessage\n  );\n  const messageDeliveryAndReadReceipts = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n  const userAndFriendsPresence = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n  );\n  const mentions = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.mentions\n  );\n  const reactions = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.reactions\n  );\n  const messageTranslation = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.messageTranslation\n  );\n  const polls = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.polls\n  );\n  const collaborativeWhiteboard = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeWhiteboard\n  );\n  const collaborativeDocument = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeDocument\n  );\n  const voiceNotes = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes\n  );\n  const stickers = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.stickers\n  );\n  const userInfo = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.userInfo\n  );\n  const groupInfo = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.groupInfo\n  );\n  const sendPrivateMessageToGroupMembers = useConfig(\n    (state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers\n  );\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const themeRef = useRef(theme);\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n  const userListenerId = 'app_messages' + new Date().getTime();\n  // Stable listener id for the lifetime of this component (prevents multiple listeners)\n  const openmessageListenerIdRef = useRef('message_' + new Date().getTime());\n  const lastOpenChatRef = useRef<{ uid: string; time: number } | null>(null);\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(user);\n  const [messageListKey, setMessageListKey] = useState(0);\n  const [messageComposerKey, setMessageComposerKey] = useState(0);\n  const [showHistoryModal, setShowHistoryModal] = useState(false);\n\n  // Manage parentMessageId in parent component\n  const [parentMessageId, setParentMessageId] = useState<string | undefined>(routeParentMessageId);\n\n  const { setActiveChat } = useActiveChat();\n  const insets = useSafeAreaInsets();\n\n  // Add ref to track streaming state\n  const messageComposerRef = useRef<any>(null);\n\n  /** Animation state for drawer */\n  const slideAnim = useRef(new Animated.Value(width)).current;\n\n  useEffect(() => {\n    if (showHistoryModal) {\n      Animated.timing(slideAnim, {\n        toValue: 0,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    } else {\n      Animated.timing(slideAnim, {\n        toValue: width,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    }\n  }, [showHistoryModal, slideAnim]);\n\n  /** Agentic user check */\n  const isAgenticUser = useCallback((): boolean => {\n    if (localUser) {\n      return localUser.getRole?.() === '@agentic';\n    }\n    return false;\n  }, [localUser]);\n  const agentic = isAgenticUser();\n\n  // ============================================\n  // Group Kicked/Banned Detection (real-time)\n  // ============================================\n  const isNoLongerMember = useGroupMemberStatus(group);\n\n  // Stop streaming when app goes to background for agentic users\n  useEffect(() => {\n    if (!agentic) return;\n\n    const handleAppStateChange = (nextAppState: AppStateStatus) => {\n      if (nextAppState === 'background' || nextAppState === 'inactive') {\n        stopStreamingForRunId();\n        if (messageComposerRef.current?.resetStreaming) {\n          messageComposerRef.current.resetStreaming();\n        }\n      }\n    };\n\n    const subscription = AppState.addEventListener('change', handleAppStateChange);\n    return () => subscription.remove();\n  }, [agentic]);\n\n  useEffect(() => {\n    // if it’s a user chat\n    if (user) {\n      setActiveChat({ type: 'user', id: user.getUid() });\n    } else if (group) {\n      setActiveChat({ type: 'group', id: group.getGuid() });\n    }\n\n    // Cleanup on unmount => setActiveChat(null)\n    return () => {\n      setActiveChat(null);\n      // Reset streaming state when leaving chat\n      if (messageComposerRef.current?.resetStreaming) {\n        messageComposerRef.current.resetStreaming();\n      }\n    };\n  }, [user, group, setActiveChat]);\n\n  useEffect(() => {\n    const backAction = () => {\n      if (fromMention || fromMessagePrivately) {\n        navigation.goBack();\n      } else {\n        navigation.popToTop();\n      }\n      return true;\n    };\n    // Add event listener for hardware back press\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n    return () => backHandler.remove();\n  }, [navigation, fromMention, fromMessagePrivately]);\n\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (item: { user: CometChat.User }) =>\n        handleccUserBlocked(item),\n      ccUserUnBlocked: (item: { user: CometChat.User }) =>\n        handleccUserUnBlocked(item),\n    });\n    const statusListenerId = 'user_status_messages_' + new Date().getTime();\n    if (localUser) {\n\n      CometChat.addUserListener(\n        statusListenerId,\n        new CometChat.UserListener({\n          onUserOnline: (onlineUser: CometChat.User) => {\n            if (onlineUser.getUid() === localUser.getUid()) {\n              console.log('🚀 ~ onUserOnline ~ onlineUser:', onlineUser);\n              setLocalUser(onlineUser);\n            }\n          },\n          onUserOffline: (offlineUser: CometChat.User) => {\n            console.log('🚀 ~ onUserOffline ~ offlineUser:', offlineUser);\n            if (offlineUser.getUid() === localUser.getUid()) {\n              setLocalUser(offlineUser);\n            }\n          },\n        }),\n      );\n    }\n\n    // Only attach the openChat listener when we are in a group context.\n    // This prevents stacking duplicate private chat screens because the group\n    // screen remains mounted underneath the user chat.\n    const currentListenerId = openmessageListenerIdRef.current;\n    if (group) {\n      CometChatUIEventHandler.addUIListener(currentListenerId, {\n        openChat: ({ user: chatUser }) => {\n          if (!chatUser) return;\n\n          try {\n            const uid = chatUser.getUid();\n\n            // 1. Debounce rapid duplicate events (within 800ms for the same UID)\n            const now = Date.now();\n            if (\n              lastOpenChatRef.current &&\n              lastOpenChatRef.current.uid === uid &&\n              now - lastOpenChatRef.current.time < 800\n            ) {\n              return; // ignore duplicate\n            }\n\n            const state = navigation.getState();\n            const routes = state?.routes || [];\n            const topRoute = routes[routes.length - 1];\n\n            // If the top route already represents this user chat, skip.\n            if (\n              topRoute?.name === 'Messages' &&\n              (topRoute as any)?.params?.user?.getUid &&\n              (topRoute as any).params.user.getUid() === uid\n            ) {\n              return;\n            }\n\n            // If any existing route (beneath) already has this user chat, pop back to it instead of pushing another.\n            const existingIndex = routes.findIndex(\n              r =>\n                r.name === 'Messages' &&\n                (r as any)?.params?.user?.getUid &&\n                (r as any).params.user.getUid() === uid,\n            );\n            if (existingIndex !== -1) {\n              const popCount = routes.length - existingIndex - 1;\n              if (popCount > 0) {\n                navigation.pop(popCount);\n              }\n              return; // we're now at the existing user chat\n            }\n\n            lastOpenChatRef.current = { uid, time: now };\n            navigation.push('Messages', { user: chatUser, fromMessagePrivately: true });\n          } catch (e) {\n            console.warn('openChat navigation prevented due to error', e);\n          }\n        },\n      });\n    }\n\n    // Close sticker panel when screen loses focus\n    const blurSub = navigation.addListener('blur', () => {\n      CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n        alignment: 'composerBottom',\n        child: () => null,\n        panelId: 'sticker',\n      });\n    });\n    const focusSub = navigation.addListener('focus', () => {\n      // Force re-mount composer so auxiliary options (StickerButton) reset correctly\n      setMessageComposerKey(prev => prev + 1);\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n      CometChat.removeUserListener(statusListenerId);\n      if (group) {\n        CometChatUIEventHandler.removeUIListener(currentListenerId);\n      }\n      blurSub();\n\n      focusSub();\n    };\n    // Listener re-attached only when group context changes\n  }, [navigation, group, userListenerId]);\n\n  const handleccUserBlocked = ({ user: blockedUser }: { user: CometChat.User }) => {\n    setLocalUser(CommonUtils.clone(blockedUser));\n  };\n\n  const handleccUserUnBlocked = ({ user: unblockedUser }: { user: CometChat.User }) => {\n    setLocalUser(CommonUtils.clone(unblockedUser));\n  };\n\n  /** Reset messages */\n  const handleNewChatClick = useCallback(() => {\n    if (messageComposerRef.current?.resetStreaming) {\n      messageComposerRef.current.resetStreaming();\n    }\n    setParentMessageId(undefined);\n    setMessageListKey(prev => prev + 1);\n    setMessageComposerKey(prev => prev + 1);\n    setShowHistoryModal(false);\n    navigation.replace('Messages', {\n      user,\n      group,\n    });\n  }, [navigation, user, group]);\n\n  /** Open chat history modal */\n  const handleChatHistoryClick = useCallback(() => {\n    setShowHistoryModal(true);\n  }, []);\n\n  /** Handle history message click */\n  const handleHistoryMessageClick = useCallback(\n    (message: CometChat.BaseMessage) => {\n      if (messageComposerRef.current && messageComposerRef.current.stopStreaming) {\n        messageComposerRef.current.stopStreaming();\n      }\n      setShowHistoryModal(false);\n      setParentMessageId(message.getId().toString());\n      setMessageListKey(prev => prev + 1);\n      setMessageComposerKey(prev => prev + 1);\n    },\n    [],\n  );\n\n  /** Handle history error */\n  const handleChatHistoryError = useCallback(\n    (_error: CometChat.CometChatException) => {},\n    [],\n  );\n\n  const unblock = async (userToUnblock: CometChat.User) => {\n    let uid = userToUnblock.getUid();\n    try {\n      const response = await CometChat.unblockUsers([uid]);\n      const unBlockedUser = await CometChat.getUser(uid);\n      if (response) {\n        setLocalUser(unBlockedUser);\n\n        // Optionally emit an event or let the server call do the job\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: unBlockedUser,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user:', error);\n    }\n  };\n\n  // Options for the header menu\n  const options = useMemo(() => {\n    return () => {\n      // For agentic users, don't show any options menu\n      if (agentic) {\n        return [];\n      }\n\n      const menuOptions = [];\n\n      // Add info option first\n      if (group && loggedInUser) {\n        menuOptions.push({\n          text: 'Group Info',\n          onPress: () => {\n            navigation.navigate('GroupInfo', { group });\n          },\n          icon: <Icon name=\"info\" width={20} height={20} color={theme.color.iconSecondary} />,\n        });\n      } else if (localUser && !localUser.getBlockedByMe()) {\n        menuOptions.push({\n          text: 'User Info',\n          onPress: () => {\n            navigation.navigate('UserInfo', { user: localUser });\n          },\n          icon: <Icon name=\"info\" width={20} height={20} color={theme.color.iconSecondary} />,\n        });\n      }\n\n      // Then add search option\n      menuOptions.push({\n        text: 'Search',\n        onPress: () => {\n          if (group) {\n            navigation.navigate('SearchMessages', { group });\n          } else if (localUser) {\n            navigation.navigate('SearchMessages', { user: localUser });\n          }\n        },\n        icon: <Icon name=\"search\" width={20} height={20} color={theme.color.iconSecondary} />,\n      });\n\n      return menuOptions;\n    };\n  }, [navigation, group, localUser, theme, agentic, loggedInUser]);\n\n\n  const getMentionsTap = useCallback(() => {\n    const mentionsFormatter =\n      ChatConfigurator.getDataSource().getMentionsFormatter(\n        loggedInUser,\n        theme,\n      );\n    if (user) mentionsFormatter.setUser(user);\n    if (group) mentionsFormatter.setGroup(group);\n\n    mentionsFormatter.setOnMentionClick(\n      (_message: CometChat.BaseMessage, uid: string) => {\n        if (uid !== loggedInUser.getUid()) {\n          // Get the user by UID and navigate to Messages\n          CometChat.getUser(uid)\n            .then((mentionedUser: CometChat.User) => {\n              navigation.push('Messages', {\n                user: mentionedUser,\n                fromMention: true,\n              });\n            })\n            .catch((error: any) => {\n              console.error('Error fetching mentioned user:', error);\n            });\n        }\n      },\n    );\n    return mentionsFormatter;\n  }, [user, group, loggedInUser, navigation, theme]);\n\n  /** Theme override for agentic outgoing bubble */\n  const providerTheme = useMemo(() => {\n    const defaultOutgoingBg =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.containerStyle?.backgroundColor;\n    const defaultTextColor =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.textBubbleStyles?.textStyle?.color;\n\n    const outgoingOverride = {\n      messageComposerStyles: {\n        containerStyle: {\n          backgroundColor: agentic\n            ? theme.color.background3\n            : theme?.messageComposerStyles?.containerStyle?.backgroundColor,\n        },\n      },\n      messageListStyles: {\n        outgoingMessageBubbleStyles: {\n          containerStyle: {\n            backgroundColor: agentic\n              ? theme.color.background4\n              : defaultOutgoingBg,\n          },\n          textBubbleStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textPrimary\n                : defaultTextColor,\n            },\n          },\n          dateStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textSecondary\n                : defaultTextColor,\n            },\n          },\n        },\n      },\n    };\n\n    return {\n      light: outgoingOverride,\n      dark: outgoingOverride,\n      mode: \"auto\" as \"auto\",\n    };\n  }, [agentic, theme]);\n\n  return (\n    <CometChatThemeProvider theme={providerTheme}>\n      <View style={styles.flexOne}>\n        <CometChatMessageHeader\n          user={localUser}\n          group={group}\n          onBack={() => {\n            if (fromMention || fromMessagePrivately) {\n              navigation.goBack();\n            } else {\n              navigation.popToTop();\n            }\n          }}\n          showBackButton={true}\n          usersStatusVisibility={userAndFriendsPresence}\n          hideVoiceCallButton={\n            (user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)\n          }\n          hideVideoCallButton={\n            (user && !oneOnOneVideoCalling) || (group && !groupVideoConference)\n          }\n          hideChatHistoryButton={false}\n          hideNewChatButton={false}\n          onChatHistoryButtonClick={handleChatHistoryClick}\n          onNewChatButtonClick={handleNewChatClick}\n          options={options}\n        />\n        <View style={styles.flexOne}>\n          <CometChatMessageList\n            key={messageListKey}\n            textFormatters={[getMentionsTap()]}\n            user={user}\n            group={group}\n            parentMessageId={parentMessageId}\n            // callback signature expects (messageObject, messageBubbleView)\n            onThreadRepliesPress={(messageObject, _messageBubbleView) => {\n              CometChatUIEventHandler.emitUIEvent?.(\n                CometChatUIEvents.hidePanel,\n                {\n                  alignment: 'composerBottom',\n                  child: () => null,\n                },\n              );\n              navigation.navigate('ThreadView', { message: messageObject, user, group });\n            }}\n            hideReplyInThreadOption={!threadConversationAndReplies}\n            hideEditMessageOption={!editMessage}\n            hideDeleteMessageOption={!deleteMessage}\n            receiptsVisibility={messageDeliveryAndReadReceipts}\n            hideTranslateMessageOption={!messageTranslation}\n            hideReactionOption={!reactions}\n            hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}\n            aiAssistantTools={new CometChatAIAssistantTools({\n              getCurrentWeather: (args: any) => console.log('Weather args', args),\n            })}\n            streamingSpeed={10}\n            goToMessageId={messageId}\n            searchKeyword={searchKeyword}\n            navigatedFromSearch={navigatedFromSearch}\n            showMarkAsUnreadOption={true}\n            startFromUnreadMessages={true}\n          />\n        </View>\n\n        {/* Chat History Drawer */}\n        {agentic && (\n          <Modal visible={showHistoryModal} transparent animationType=\"none\" onRequestClose={() => setShowHistoryModal(false)}>\n            <View style={drawerStyles.backdrop}>\n              <Animated.View\n                style={[\n                  drawerStyles.drawer,\n                  {\n                    backgroundColor: theme.color.background1,\n                    paddingTop: Platform.OS === 'ios' ? insets.top : 0,\n                  },\n                  { transform: [{ translateX: slideAnim }] },\n                ]}\n              >\n                <CometChatAIAssistantChatHistory\n                  user={localUser}\n                  group={group}\n                  onClose={() => setShowHistoryModal(false)}\n                  onMessageClicked={handleHistoryMessageClick}\n                  onError={handleChatHistoryError}\n                  onNewChatButtonClick={handleNewChatClick}\n                />\n              </Animated.View>\n            </View>\n          </Modal>\n        )}\n\n        {isNoLongerMember ? (\n          <View\n            style={{\n              paddingVertical: 12,\n              paddingHorizontal: 16,\n              backgroundColor: theme.color.background1 as string,\n            }}\n          >\n            <Text\n              style={[\n                theme.typography.body.regular,\n                {\n                  color: theme.color.textPrimary as string,\n                  textAlign: 'center',\n                },\n              ]}\n            >\n              {t('GROUP_NO_LONGER_MEMBER')}\n            </Text>\n          </View>\n        ) : localUser?.getBlockedByMe() ? (\n          <View\n            style={[\n              styles.blockedContainer,\n              { backgroundColor: theme.color.background3 },\n            ]}\n          >\n            <Text\n              style={[\n                theme.typography.button.regular,\n                {\n                  color: theme.color.textSecondary,\n                  textAlign: 'center',\n                  paddingBottom: 10,\n                },\n              ]}\n            >\n              {t('BLOCKED_USER_DESC')}\n            </Text>\n            <TouchableOpacity\n              onPress={() => unblock(localUser)}\n              style={[styles.button, { borderColor: theme.color.borderDefault }]}\n            >\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.buttontext,\n                  {\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n              >\n                {t('UNBLOCK')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        ) : (\n          <CometChatCompactMessageComposer\n            key={messageComposerKey}\n            ref={messageComposerRef}\n            parentMessageId={parentMessageId}\n            user={localUser}\n            group={group}\n            keyboardAvoidingViewProps={{\n              ...(Platform.OS === 'android'\n                ? {}\n                : {\n                  behavior: 'padding',\n                }),\n            }}\n            disableTypingEvents={!typingIndicator}\n            hideImageAttachmentOption={!photosSharing}\n            hideVideoAttachmentOption={!videoSharing}\n            hideAudioAttachmentOption={!audioSharing}\n            hideFileAttachmentOption={!fileSharing}\n            hideCameraOption={!photosSharing}\n            disableMentions={!mentions}\n            hideStickersButton={!stickers}\n            hideCollaborativeDocumentOption={!collaborativeDocument}\n            hideCollaborativeWhiteboardOption={!collaborativeWhiteboard}\n            hidePollsAttachmentOption={!polls}\n            hideVoiceRecordingButton={!voiceNotes}\n          />\n        )}\n      </View>\n    </CometChatThemeProvider>\n  );\n};\n\nconst styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n\n});\n\nconst drawerStyles = StyleSheet.create({\n  backdrop: {\n    justifyContent: 'flex-start',\n    alignItems: 'flex-end',\n  },\n  drawer: {\n    width: '100%',\n    height: '100%',\n    overflow: 'hidden',\n  },\n});\n\nexport default Messages;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/OngoingCallScreen.tsx",
    "content": "import React, { useMemo, useRef } from \"react\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { navigate, navigationRef } from \"../../../navigation/NavigationService\";\nimport { CometChatCalls } from \"@cometchat/calls-sdk-react-native\";\nimport { CometChatOngoingCall } from \"@cometchat/chat-uikit-react-native\";\nimport { SCREEN_CONSTANTS } from \"../../../utils/AppConstants\";\nimport type { RouteProp } from \"@react-navigation/native\";\nimport { CallType, RootStackParamList } from \"../../../navigation/types\";\n\ntype Props = {\n  navigation: any;\n  route: RouteProp<RootStackParamList, \"OngoingCallScreen\">;\n};\n\nconst OngoingCallScreen = ({ navigation, route }: Props) => {\n  const params = route.params;\n\n  // 1) Normalize sessionId\n  const sessionID: string | undefined = useMemo(() => {\n    if (\"sessionId\" in params) return params.sessionId;\n    const c = (params as { call: any }).call;\n    return c?.sessionId ?? c?.getSessionId?.();\n  }, [params]);\n\n  // 2) Normalize/derive callType\n  const callType: CallType | undefined = useMemo(() => {\n    if (\"callType\" in params) return params.callType;\n\n    const c = (params as { call: any }).call;\n    if (!c) return undefined;\n\n    // Prefer public getter if available\n    if (typeof c?.getType === \"function\") {\n      const t = c.getType();\n      if (t === CometChat.CALL_TYPE.AUDIO) return \"audio\";\n      if (t === CometChat.CALL_TYPE.VIDEO) return \"video\";\n    }\n\n    // Fallbacks for raw payloads\n    if (c?.callType === \"audio\") return \"audio\";\n    if (c?.data?.type === CometChat.CALL_TYPE.AUDIO) return \"audio\";\n    if (c?.data?.type === CometChat.CALL_TYPE.VIDEO) return \"video\";\n\n    return undefined;\n  }, [params]);\n\n  const isAudioOnly = callType === \"audio\";\n\n  const callListener = useRef<any>(\n    new CometChatCalls.OngoingCallListener({\n      onCallEnded: () => {\n        CometChat.clearActiveCall();\n        CometChatCalls.endSession();\n        navigate(\"BottomTabNavigator\", undefined as any);\n        navigationRef.reset({\n          index: 0,\n          routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n        });\n      },\n      onCallEndButtonPressed: () => {\n        if (sessionID) CometChat.endCall(sessionID);\n        navigate(\"BottomTabNavigator\", undefined as any);\n        navigationRef.reset({\n          index: 0,\n          routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n        });\n      },\n    })\n  );\n\n  const callSettings = useMemo(() => {\n    return new CometChatCalls.CallSettingsBuilder()\n      .enableDefaultLayout(true)\n      .setCallEventListener(callListener.current)\n      .setIsAudioOnlyCall(!!isAudioOnly);\n  }, [isAudioOnly]);\n\n  if (!sessionID) {\n    // Belt & suspenders: try to recover from active call if param missing\n    const active: any = CometChat.getActiveCall?.();\n    const recovered =\n      typeof active?.getSessionId === \"function\"\n        ? active.getSessionId()\n        : undefined;\n    if (!recovered) return null; // or render a fallback/loading\n  }\n\n  return (\n    <CometChatOngoingCall\n      sessionID={sessionID!}\n      callSettingsBuilder={callSettings}\n    />\n  );\n};\n\nexport default OngoingCallScreen;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/SearchMessages.tsx",
    "content": "import React, { useCallback, useEffect, useRef } from 'react';\nimport {\n  View,\n  StyleSheet,\n  BackHandler,\n  Platform,\n  StatusBar,\n} from 'react-native';\nimport {\n  CometChatSearch,\n  useTheme,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { StackScreenProps } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { navigate } from '../../../navigation/NavigationService';\nimport { SCREEN_CONSTANTS } from '../../../utils/AppConstants';\n\ntype Props = StackScreenProps<RootStackParamList, 'SearchMessages'>;\n\nconst SearchMessages: React.FC<Props> = ({ route, navigation }) => {\n  const { user, group } = route.params || {};\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const insets = useSafeAreaInsets();\n\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n\n  // Update refs when navigation/route changes\n  useEffect(() => {\n    navigationRef.current = navigation;\n    routeRef.current = route;\n  }, [navigation, route]);\n\n  // Handle back button on Android\n  useEffect(() => {\n    const backAction = () => {\n      navigation.goBack();\n      return true;\n    };\n\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n\n    return () => backHandler.remove();\n  }, [navigation]);\n\n  const handleBack = useCallback(() => {\n    navigation.goBack();\n  }, [navigation]);\n\n  const handleConversationClicked = useCallback((conversation: CometChat.Conversation, searchKeyword?: string) => {\n    // Navigate to messages screen with the selected conversation\n    const conversationWith = conversation.getConversationWith();\n\n    if (conversationWith instanceof CometChat.User) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        user: conversationWith,\n        searchKeyword,\n      });\n    } else if (conversationWith instanceof CometChat.Group) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        group: conversationWith,\n        searchKeyword,\n      });\n    }\n  }, []);\n\n  const handleMessageClicked = useCallback(async (message: CometChat.BaseMessage, searchKeyword?: string) => {\n    // Navigate to messages screen and highlight the specific message\n    const messageReceiver = message.getReceiver();\n    const parentMessageId = message.getParentMessageId();\n\n    let targetUser: CometChat.User | undefined;\n    let targetGroup: CometChat.Group | undefined;\n\n    if (messageReceiver instanceof CometChat.User) {\n      // For user messages, determine if it's a direct message to/from the user\n      const sender = message.getSender();\n      const loggedInUser = await CometChat.getLoggedinUser();\n\n      if (sender.getUid() === loggedInUser?.getUid()) {\n        // Message sent by logged-in user, target is receiver\n        targetUser = messageReceiver;\n      } else {\n        // Message received by logged-in user, target is sender\n        targetUser = sender;\n      }\n    } else if (messageReceiver instanceof CometChat.Group) {\n      targetGroup = messageReceiver;\n    }\n\n    if (parentMessageId) {\n      try {\n        const parentMessage = await CometChat.getMessageDetails(parentMessageId);\n        if (parentMessage) {\n          navigate(SCREEN_CONSTANTS.THREAD_VIEW, {\n            message: parentMessage,\n            user: targetUser,\n            group: targetGroup,\n            highlightMessageId: String(message.getId()),\n          });\n          return;\n        }\n      } catch (e) {\n        console.error(\"Failed to fetch parent message\", e);\n      }\n    }\n\n    if (targetUser) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        user: targetUser,\n        messageId: String(message.getId()),\n        searchKeyword,\n        navigatedFromSearch: true,\n      });\n    } else if (targetGroup) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        group: targetGroup,\n        messageId: String(message.getId()),\n        searchKeyword,\n        navigatedFromSearch: true,\n      });\n    }\n  }, []);\n\n\n  // Determine placeholder text\n  let searchPlaceholder = \"Search\";\n  if (user && user.getName()) {\n    searchPlaceholder = `Search in ${user.getName()}`;\n  } else if (group && group.getName()) {\n    searchPlaceholder = `Search in ${group.getName()}`;\n  }\n\n  return (\n    <View style={[styles.container, Platform.OS === 'android' && { paddingTop: insets.top }]}>\n      {Platform.OS === 'ios' && (\n        <StatusBar\n          barStyle={\n            theme.mode === 'light' ? 'dark-content' : 'light-content'\n          }\n          backgroundColor={theme.color.background1}\n        />\n      )}\n\n      <CometChatSearch\n        onBack={handleBack}\n        hideBackButton={false}\n        onConversationClicked={handleConversationClicked}\n        onMessageClicked={handleMessageClicked}\n        uid={user?.getUid()}\n        guid={group?.getGuid()}\n        searchPlaceholder={searchPlaceholder}\n        messagesRequestBuilder={\n          new CometChat.MessagesRequestBuilder().setLimit(30)\n        }\n        conversationsRequestBuilder={\n          new CometChat.ConversationsRequestBuilder().setLimit(30)\n        }\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\nexport default SearchMessages;"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/ThreadView.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useMemo,\n  useState,\n} from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  StyleSheet,\n  BackHandler,\n  Platform,\n} from 'react-native';\nimport {\n  RouteProp,\n  useRoute,\n  useNavigation,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport {\n  CometChatThreadHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  CometChatMessageComposer,\n  useCometChatTranslation,\n  CometChatUIKit,\n  ChatConfigurator,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { useTheme } from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport { useConfig } from '../../../config/store';\nimport { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';\n\ntype ThreadViewRouteProp = RouteProp<RootStackParamList, 'ThreadView'>;\ntype ThreadViewNavProp = StackNavigationProp<RootStackParamList>;\n\nconst ThreadView = () => {\n  const { params } = useRoute<ThreadViewRouteProp>();\n  const navigation = useNavigation<ThreadViewNavProp>(); // <-- added navigation\n  const { goBack } = navigation;\n  const theme = useTheme();\n  const { message, user, group } = params || {};\n  const { t } = useCometChatTranslation();\n\n  const messageDeliveryAndReadReceipts = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n  const hideReactionOption = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.reactions\n  );\n  const photosSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing\n  );\n  const videoSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing\n  );\n  const audioSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing\n  );\n  const fileSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing\n  );\n  const mentions = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.mentions\n  );\n  const voiceNotes = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes\n  );\n  const stickers = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.stickers\n  );\n  const sendPrivateMessageToGroupMembers = useConfig(\n      (state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers\n  );\n  const compactMessageComposer = useConfig(\n      (state) => state.settings.layout.compactMessageComposer\n  );\n\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(\n    params?.user,\n  );\n\n  // ============================================\n  // Group Kicked/Banned Detection (real-time)\n  // ============================================\n  const isNoLongerMember = useGroupMemberStatus(group);\n\n  // keep listener ids unique\n  const userListenerId = 'thread_user_' + new Date().getTime();\n\n  // Fetch latest user on mount (if user present)\n  useEffect(() => {\n    let mounted = true;\n    const init = async () => {\n      try {\n        if (params?.user) {\n          const uid = params.user.getUid();\n          const fresh = await CometChat.getUser(uid);\n          if (mounted) setLocalUser(CommonUtils.clone(fresh));\n        }\n      } catch (err) {\n        console.error('Error fetching user in ThreadView:', err);\n      }\n    };\n    init();\n    return () => {\n      mounted = false;\n    };\n  }, [params?.user]);\n\n  // add UI listeners for block/unblock to update localUser\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (payload: { user: CometChat.User }) => {\n        setLocalUser(CommonUtils.clone(payload.user));\n      },\n      ccUserUnBlocked: (payload: { user: CometChat.User }) => {\n        setLocalUser(CommonUtils.clone(payload.user));\n      },\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n    };\n  }, []);\n\n  useEffect(() => {\n    CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n      alignment: 'composerBottom',\n      child: () => null,\n      panelId: 'sticker',\n    });\n  }, []);\n\n// B) Ensure back also asks to close (already using goBack())\nconst handleBack = useCallback(() => {\n  CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n    alignment: 'composerBottom',\n    child: () => null,\n    panelId: 'sticker',\n  });\n  navigation.goBack();\n  return true;\n}, [navigation]);\n\n  useFocusEffect(\n    useCallback(() => {\n      // Android hardware back -> just pop\n      const sub = BackHandler.addEventListener('hardwareBackPress', handleBack);\n\n      // iOS gesture/back button -> let default POP happen, but run side-effects\n      const unsub = navigation.addListener('beforeRemove', e => {\n        const type = e?.data?.action?.type;\n        if (type === 'GO_BACK' || type === 'POP') {\n          // IMPORTANT: do NOT e.preventDefault()\n          CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n            alignment: 'composerBottom',\n            child: () => null,\n            panelId: 'sticker',\n          });\n        }\n      });\n\n      return () => {\n        sub.remove();\n        unsub();\n      };\n    }, [navigation, handleBack]),\n  );\n\n  // Header back\n  <TouchableOpacity onPress={handleBack}>…</TouchableOpacity>;\n\n  // Keep gesture enabled\n  useLayoutEffect(() => {\n    navigation.setOptions?.({ gestureEnabled: true } as any);\n  }, [navigation]);\n  \n  const unblock = async (userToUnblock: CometChat.User) => {\n    try {\n      const uid = userToUnblock.getUid();\n      const response = await CometChat.unblockUsers([uid]);\n      if (response) {\n        const fresh = await CometChat.getUser(uid);\n        setLocalUser(CommonUtils.clone(fresh));\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: fresh,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user from ThreadView:', error);\n    }\n  };\n\n  const getMentionsTap = useCallback(() => {\n    const mentionsFormatter =\n      ChatConfigurator.getDataSource().getMentionsFormatter(\n        loggedInUser,\n        theme,\n      );\n    if (user) mentionsFormatter.setUser(user);\n    if (group) mentionsFormatter.setGroup(group);\n\n    mentionsFormatter.setOnMentionClick(\n      (_message: CometChat.BaseMessage, uid: string) => {\n        if (uid !== loggedInUser.getUid()) {\n          CometChat.getUser(uid)\n            .then((mentionedUser: CometChat.User) => {\n              navigation.push('Messages', {\n                user: mentionedUser,\n                fromMention: true,\n              });\n            })\n            .catch((error: any) => {\n              console.error('Error fetching mentioned user:', error);\n            });\n        }\n      },\n    );\n    return mentionsFormatter;\n  }, [user, group, loggedInUser, navigation, theme]);\n\n  const threadHeaderMentionsFormatter = useMemo(\n    () => getMentionsTap(),\n    [getMentionsTap],\n  );\n\n  return (\n    <View style={{ backgroundColor: theme.color.background1, flex: 1 }}>\n      {/* Custom Header */}\n      <View style={styles.headerStyle}>\n        <TouchableOpacity style={styles.iconStyle} onPress={handleBack}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <View style={styles.textStyle}>\n          <Text\n            style={[\n              theme.typography.heading1.bold,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            {t('THREAD')}\n          </Text>\n          <Text\n            style={[\n              theme.typography.caption1.regular,\n              {\n                color: theme.color.textSecondary,\n                maxWidth: '90%',\n              },\n            ]}\n            numberOfLines={1}\n            ellipsizeMode=\"tail\"\n          >\n            {user ? user?.getName() : group?.getName()}\n          </Text>\n        </View>\n      </View>\n\n      {/* Thread Header */}\n      <CometChatThreadHeader\n        parentMessage={message}\n        receiptsVisibility={messageDeliveryAndReadReceipts}\n        textFormatters={[threadHeaderMentionsFormatter]}\n      />\n\n      {/* Threaded Message List */}\n      <View style={{ flex: 1 }}>\n        <CometChatMessageList\n          user={user}\n          group={group}\n          parentMessageId={message.getId().toString()}\n          textFormatters={[getMentionsTap()]}\n          receiptsVisibility={messageDeliveryAndReadReceipts}\n          hideReactionOption={!hideReactionOption}\n          hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}\n          goToMessageId={params?.highlightMessageId}\n        />\n      </View>\n\n      {/* Message Composer for Thread */}\n      {isNoLongerMember ? (\n        <View\n          style={{\n            paddingVertical: 12,\n            paddingHorizontal: 16,\n            backgroundColor: theme.color.background1 as string,\n          }}\n        >\n          <Text\n            style={[\n              theme.typography.body.regular,\n              {\n                color: theme.color.textPrimary as string,\n                textAlign: 'center',\n              },\n            ]}\n          >\n            {t('GROUP_NO_LONGER_MEMBER')}\n          </Text>\n        </View>\n      ) : localUser?.getBlockedByMe() ? (\n        <View\n          style={[\n            styles.blockedContainer,\n            { backgroundColor: theme.color.background3 },\n          ]}\n        >\n          <Text\n            style={[\n              theme.typography.button.regular,\n              {\n                color: theme.color.textSecondary,\n                textAlign: 'center',\n                paddingBottom: 10,\n              },\n            ]}\n          >\n            {t('BLOCKED_USER_DESC')}\n          </Text>\n          <TouchableOpacity\n            onPress={() => unblock(localUser)}\n            style={[styles.button, { borderColor: theme.color.borderDefault }]}\n          >\n            <Text\n              style={[\n                theme.typography.button.medium,\n                styles.buttontext,\n                {\n                  color: theme.color.textPrimary,\n                },\n              ]}\n            >\n              {t('UNBLOCK')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      ) : (\n        <CometChatCompactMessageComposer\n          user={localUser}\n          group={group}\n          parentMessageId={message.getId()}\n          onError={(error: any) => console.error('Composer Error:', error)}\n          keyboardAvoidingViewProps={\n            Platform.OS === 'android' ? {} : { behavior: 'padding' }\n          }\n          hideImageAttachmentOption={!photosSharing}\n          hideVideoAttachmentOption={!videoSharing}\n          hideAudioAttachmentOption={!audioSharing}\n          hideFileAttachmentOption={!fileSharing}\n          hideCameraOption={!photosSharing}\n          disableMentions={!mentions}\n          hideVoiceRecordingButton={!voiceNotes}\n          hideStickersButton={!stickers}\n        />\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  headerStyle: {\n    paddingVertical: 10,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  iconStyle: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  textStyle: {\n    paddingLeft: 10,\n    alignItems: 'flex-start',\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n});\n\nexport default ThreadView;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/TransferOwnership.tsx",
    "content": "import React, {useState} from 'react';\nimport {View, Text, TouchableOpacity} from 'react-native';\nimport {RouteProp} from '@react-navigation/native';\nimport {\n  CometChatGroupMembers,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../../navigation/types';\nimport {styles} from './TransferOwnershipStyles';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport {CommonUtils} from '../../../utils/CommonUtils';\n\ntype TransferOwnershipScreenProps = {\n  route: RouteProp<RootStackParamList, 'TransferOwnershipSection'>;\n  navigation: StackNavigationProp<\n    RootStackParamList,\n    'TransferOwnershipSection'\n  >;\n};\n\nconst TransferOwnership: React.FC<TransferOwnershipScreenProps> = ({\n  route,\n  navigation,\n}) => {\n  const {group} = route.params;\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [selectedOwnershipMember, setSelectedOwnershipMember] =\n    useState<CometChat.User | null>(null);\n\n  const handleBack = () => {\n    navigation.goBack();\n  };\n\n  // Function to leave the group after ownership transfer\n  const leaveGroup = (group: CometChat.Group) => {\n    CometChat.leaveGroup(group.getGuid())\n      .then(() => {\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          leftGroup: CommonUtils.clone(group),\n        });\n        navigation.pop(3);\n      })\n      .catch(error => {\n        console.log('Group leaving failed with exception:', error);\n      });\n  };\n\n  const handleTransferOwnership = async () => {\n    if (!selectedOwnershipMember || !group) return;\n    try {\n      await CometChat.transferGroupOwnership(\n        group.getGuid(),\n        selectedOwnershipMember.getUid(),\n      );\n      leaveGroup(group);\n    } catch (error) {\n      console.error('Ownership transfer failed:', error);\n    }\n  };\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {/* Header */}\n      <View style={styles.headerSection}>\n        <TouchableOpacity\n          style={styles.backButtonContainer}\n          onPress={handleBack}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.leftPaddingSmall,\n            {color: theme.color.textPrimary},\n          ]}>\n          {t('TRANSFER_OWNERSHIP')}\n        </Text>\n      </View>\n\n      {/* Group Members List */}\n      {group && (\n        <CometChatGroupMembers\n          group={group}\n          excludeOwner={true}\n          onBack={handleBack}\n          hideHeader={true}\n          selectionMode=\"single\"\n          onSelection={members => {\n            setSelectedOwnershipMember(\n              members && members.length > 0 ? members[0] : null,\n            );\n          }}\n        />\n      )}\n\n      {/* Transfer Ownership Button */}\n      <TouchableOpacity\n        onPress={handleTransferOwnership}\n        style={styles.transferButtonWrapper}>\n        <View\n          style={[\n            styles.transferButtonContent,\n            {backgroundColor: theme.color.primaryButtonBackground},\n          ]}>\n          <Text\n            style={[\n              theme.typography.heading4.medium,\n              styles.centerAligned,\n              {color: theme.color.primaryButtonText},\n            ]}>\n            {t('TRANSFER_OWNERSHIP')}\n          </Text>\n        </View>\n      </TouchableOpacity>\n    </View>\n  );\n};\n\nexport default TransferOwnership;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/TransferOwnershipStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  headerSection: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  backButtonContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  leftPaddingSmall: {\n    paddingLeft: 5,\n  },\n  transferButtonWrapper: {\n    alignContent: 'center',\n    justifyContent: 'center',\n    paddingVertical: 1,\n    height: '7%',\n    width: '100%',\n    alignSelf: 'center',\n  },\n  transferButtonContent: {\n    marginHorizontal: 20,\n    alignSelf: 'center',\n    justifyContent: 'center',\n    borderRadius: 6,\n    height: '75%',\n    width: '95%',\n  },\n  centerAligned: {\n    alignSelf: 'center',\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/UserInfo.tsx",
    "content": "import React, {useState, useEffect, useRef, FC} from 'react';\nimport {View, Text, TouchableOpacity, BackHandler} from 'react-native';\nimport {\n  CometChatAvatar,\n  useTheme,\n  CometChatUIEventHandler,\n  CallUIEvents,\n  CometChatConversationEvents,\n  CometChatConfirmDialog,\n  CometChatOutgoingCall,\n  useCometChatTranslation,\n  getLastSeenTime,\n} from '@cometchat/chat-uikit-react-native';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {permissionUtil} from '@cometchat/chat-uikit-react-native/src/shared/utils/PermissionUtil';\nimport {CallTypeConstants, UserStatusConstants} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport { blockUser, unblock } from '../../../utils/helper';\nimport {styles} from './UserInfoStyles';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport Block from '../../../assets/icons/Block';\nimport Delete from '../../../assets/icons/Delete';\nimport Videocam from '../../../assets/icons/VideoCam';\nimport Call from '../../../assets/icons/Call';\nimport {StackNavigationProp, StackScreenProps} from '@react-navigation/stack';\nimport {\n  RootStackParamList,\n} from '../../../navigation/types';\nimport {useFocusEffect} from '@react-navigation/native';\nimport { useConfig } from '../../../config/store';\n\n\ntype ScreenProps = StackScreenProps<RootStackParamList, 'UserInfo'>;\ntype NavigationProps = StackNavigationProp<RootStackParamList, 'UserInfo'>;\ntype Props = ScreenProps & {navigation: NavigationProps};\n\nconst UserInfo: FC<Props> = ({route, navigation}) => {\n  const {user} = route.params;\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [userObj, setUserObj] = useState<CometChat.User>(user);\n  /** STATES **/\n  const [disableButton, setDisableButton] = useState<boolean>(false);\n  const [blocked, setBlocked] = useState<boolean>(false);\n\n  // separate modal states\n  const [isBlockModalOpen, setBlockModalOpen] = useState(false);\n  const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);\n\n  const [userStatus, setUserStatus] = useState(userObj.getStatus());\n  const listenerId = useRef<string>('CallListener_' + Date.now());\n  const userStatusListenerId = 'user_status_' + new Date().getTime();\n\n  const [callObj, setCallObj] = useState<CometChat.Call>();\n\n  const callType = useRef<string | undefined>(undefined);\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n\n  useEffect(() => {\n    setUserStatus(userObj.getStatus());\n    setBlocked(userObj.getBlockedByMe());\n\n    CometChat.addCallListener(\n      listenerId.current,\n      new CometChat.CallListener({\n        onIncomingCallReceived: () => {\n          setDisableButton(true);\n        },\n        onOutgoingCallAccepted: () => {\n          console.log('call accepted');\n        },\n        onOutgoingCallRejected: () => {\n          setDisableButton(false);\n          setCallObj(undefined);\n        },\n        onIncomingCallCancelled: () => {\n          setDisableButton(false);\n        },\n      }),\n    );\n    CometChatUIEventHandler.addCallListener(listenerId.current, {\n      ccCallRejected: () => {\n        setDisableButton(false);\n        setCallObj(undefined);\n      },\n      ccCallEnded: () => {\n        setDisableButton(false);\n        setCallObj(undefined);\n      },\n    });\n\n    CometChat.addUserListener(\n      userStatusListenerId,\n      new CometChat.UserListener({\n        onUserOnline: (onlineUser: CometChat.User) => {\n          if (onlineUser.getUid() === userObj.getUid()) {\n            setUserObj(onlineUser);\n            setUserStatus(onlineUser.getStatus());\n          }\n        },\n        onUserOffline: (offlineUser: CometChat.User) => {\n          if (offlineUser.getUid() === userObj.getUid()) {\n            setUserObj(offlineUser);\n            setUserStatus(offlineUser.getStatus());\n          }\n        },\n      }),\n    );\n\n    return () => {\n      CometChat.removeCallListener(listenerId.current);\n      CometChatUIEventHandler.removeCallListener(listenerId.current);\n      CometChat.removeUserListener(userStatusListenerId);\n    };\n  }, [userObj]);\n\n  const translations = {\n    lastSeen: 'Last seen',\n    minutesAgo: (minutes: number) =>\n      `${minutes} minute${minutes === 1 ? '' : 's'} ago`,\n    hoursAgo: (hours: number) => `${hours} hour${hours === 1 ? '' : 's'} ago`,\n  };\n\n  const makeVoiceCall = async (): Promise<void> => {\n    if (disableButton) return;\n    if (!(await permissionUtil.startResourceBasedTask(['mic']))) return;\n    callType.current = CallTypeConstants.audio;\n    makeCall(CallTypeConstants.audio);\n  };\n\n  const makeVideoCall = async (): Promise<void> => {\n    if (disableButton) return;\n    if (!(await permissionUtil.startResourceBasedTask(['mic', 'camera'])))\n      return;\n    callType.current = CallTypeConstants.video;\n    makeCall(CallTypeConstants.video);\n  };\n\n  const makeCall = (type: string): void => {\n    if (type === CallTypeConstants.audio || type === CallTypeConstants.video) {\n      const receiverID = userObj.getUid();\n      const callTypeValue = type;\n      const receiverType = CometChat.RECEIVER_TYPE.USER;\n      if (!receiverID || !receiverType) return;\n\n      const call = new CometChat.Call(\n        receiverID,\n        callTypeValue,\n        receiverType,\n        CometChat.CATEGORY_CALL,\n      );\n\n      CometChat.initiateCall(call).then(\n        initiatedCall => {\n          setCallObj(initiatedCall);\n          setDisableButton(true);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, {\n            call: initiatedCall,\n          });\n        },\n        error => {\n          console.log('Call initialization failed with exception:', error);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, {\n            call,\n          });\n        },\n      );\n    }\n  };\n\n  /** BLOCK/UNBLOCK LOGIC **/\n  const handleBlockUnblockConfirm = () => {\n    setBlockModalOpen(false); // close the dialog\n    if (blocked) {\n      // user is already blocked by me -> now unblocking\n      unblock(userObj.getUid(), userObj, setBlocked, setUserObj);\n    } else {\n      // user is not blocked -> blocking user\n      blockUser(userObj.getUid(), userObj, setBlocked);\n    }\n  };\n\n  /** DELETE CONVERSATION LOGIC **/\n  const handleDeleteConversationConfirm = () => {\n    setDeleteModalOpen(false); // close the dialog\n    if (userObj) {\n      CometChat.getConversation(userObj.getUid(), 'user')\n        .then(conversation => {\n          CometChat.deleteConversation(userObj.getUid(), 'user')\n            .then(deletedConversation => {\n              console.log(deletedConversation);\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccConversationDeleted,\n                {conversation: conversation},\n              );\n              navigation.pop(2);\n            })\n            .catch(error => {\n              console.log('Error while deleting conversation:', error);\n            });\n        })\n        .catch(error => {\n          console.log('Error while deleting conversation:', error);\n        });\n    }\n  };\n\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {/* Header */}\n      <View style={styles.headerContainer}>\n        <TouchableOpacity\n          style={styles.backButtonContainer}\n          onPress={() => navigation.goBack()}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.smallPaddingLeft,\n            {color: theme.color.textPrimary},\n          ]}>\n          {t('USER_INFO')}\n        </Text>\n      </View>\n\n      {/* User Information Section */}\n      <View\n        style={[styles.profileCard, {borderColor: theme.color.borderLight}]}>\n        <View style={styles.profileInfo}>\n          <CometChatAvatar\n            style={{\n              containerStyle: styles.avatarContainer,\n              textStyle: styles.avatarText,\n              imageStyle: styles.avatarImage,\n            }}\n            image={\n              userObj?.getAvatar() ? {uri: userObj.getAvatar()} : undefined\n            }\n            name={userObj?.getName() ?? ''}\n          />\n          <Text\n            style={[\n              theme.typography.heading3.medium,\n              styles.mt10Centered,\n              {color: theme.color.textPrimary},\n            ]}>\n            {userObj?.getName()}\n          </Text>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.mt5Centered,\n              {color: theme.color.textSecondary},\n            ]}>\n            {userObj &&\n              !(userObj.getBlockedByMe() || userObj.getHasBlockedMe()) &&\n              (userStatus === UserStatusConstants.online\n                ? t('ONLINE')\n                : getLastSeenTime(userObj.getLastActiveAt()))}\n          </Text>\n        </View>\n\n        {/* Action Boxes */}\n        {(oneOnOneVoiceCalling || oneOnOneVideoCalling) && (\n          <View style={styles.actionsRow}>\n            {oneOnOneVoiceCalling && (\n              <TouchableOpacity\n                style={[\n                  styles.callActionButton,\n                  {borderColor: theme.color.borderDefault},\n                ]}\n                onPress={makeVoiceCall}>\n                <Icon\n                  icon={<Call color={theme.color.primary} height={24} width={24} />}\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.mt5Centered,\n                    { color: theme.color.textSecondary },\n                  ]}>\n                  {t('AUDIO_CALL')}\n                </Text>\n              </TouchableOpacity>\n            )}\n\n            {oneOnOneVideoCalling && (\n              <TouchableOpacity\n                style={[\n                  styles.callActionButton,\n                  {borderColor: theme.color.borderDefault},\n                ]}\n                onPress={makeVideoCall}>\n                <Icon\n                  icon={\n                    <Videocam color={theme.color.primary} height={24} width={24} />\n                  }\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.mt5Centered,\n                    { color: theme.color.textSecondary },\n                  ]}>\n                  {t('VIDEO_CALL')}\n                </Text>\n              </TouchableOpacity>\n            )}\n          </View>\n        )}\n      </View>\n\n      {/* Block/Unblock and Delete Chat Buttons */}\n      <View style={styles.optionsContainer}>\n        <View style={styles.optionRow}>\n          <TouchableOpacity\n            onPress={() => setBlockModalOpen(true)}\n            style={styles.backButtonContainer}>\n            <Icon\n              icon={<Block color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.ml5,\n                {color: theme.color.error},\n              ]}>\n              {blocked ? t('UNBLOCK') : t('BLOCK')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n\n        {/* DELETE CHAT */}\n        <View style={styles.optionRow}>\n          <TouchableOpacity\n            onPress={() => setDeleteModalOpen(true)}\n            style={styles.backButtonContainer}>\n            <Icon\n              icon={<Delete color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.ml5,\n                { color: theme.color.error },\n              ]}>\n              {t('DELETE_CHAT_TEXT')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      </View>\n\n      {/* =============== BLOCK/UNBLOCK MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isBlockModalOpen}\n        onCancel={() => setBlockModalOpen(false)}\n        onConfirm={handleBlockUnblockConfirm}\n        onDismiss={() => console.log('Block/Unblock Modal dismissed')}\n        titleText={\n          blocked ? t('UNBLOCK_CONTACT') : t('BLOCK_USER')\n        }\n        messageText={\n          blocked ? t('UNBLOCK_SURE') : t('BLOCK_SURE')\n        }\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={blocked ? t('UNBLOCK') : t('BLOCK')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* =============== DELETE CHAT MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteModalOpen}\n        onCancel={() => setDeleteModalOpen(false)}\n        onConfirm={handleDeleteConversationConfirm}\n        onDismiss={() => console.log('Delete Modal dismissed')}\n        titleText={t('DELETE_CHAT')}\n        messageText={t('SURE_TO_DELETE_CHAT')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n\n      {callObj && (\n        <CometChatOutgoingCall\n          call={callObj}\n          onEndCallButtonPressed={call => {\n            CometChat.rejectCall(\n              call?.getSessionId(),\n              CometChat.CALL_STATUS.CANCELLED,\n            ).then(\n              rejectedCall => {\n                console.log('🚀 ~ rejectedCall:', rejectedCall);\n                CometChatUIEventHandler.emitCallEvent(\n                  CallUIEvents.ccCallRejected,\n                  {\n                    call: rejectedCall,\n                  },\n                );\n                setCallObj(undefined);\n              },\n              err => {\n                console.log('🚀 ~ err:', err);\n                setCallObj(undefined);\n                // onError && onError(err);\n              },\n            );\n          }}\n        />\n      )}\n    </View>\n  );\n};\n\nexport default UserInfo;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/UserInfoStyles.tsx",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n    headerContainer: {\n      paddingTop: 15,\n      paddingLeft: 10,\n      flexDirection: 'row',\n    },\n    backButtonContainer: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    smallPaddingLeft: {\n      paddingLeft: 5,\n    },\n    profileCard: {\n      marginTop: 12,\n      borderTopWidth: 1,\n      borderBottomWidth: 1,\n      paddingVertical: 10,\n    },\n    profileInfo: {\n      alignSelf: 'center',\n      alignItems: 'center',\n      marginTop: 20,\n    },\n    avatarContainer: {\n      height: 120,\n      width: 120,\n    },\n    avatarText: {\n      fontSize: 28,\n      lineHeight: 55,\n    },\n    avatarImage: {\n      height: '100%',\n      width: '100%',\n    },\n    mt10Centered: {\n      marginTop: 10,\n      alignSelf: 'center',\n    },\n    mt5Centered: {\n      marginTop: 5,\n      textAlign: 'center',\n      alignSelf: 'center',\n    },\n    actionsRow: {\n      flexDirection: 'row',\n      paddingHorizontal: 20,\n      justifyContent: 'space-between',\n      marginVertical: 20,\n    },\n    callActionButton: {\n      flex: 1,\n      paddingVertical: 10,\n      borderWidth: 1,\n      borderRadius: 8,\n      justifyContent: 'center',\n      alignItems: 'center',\n      marginHorizontal: 5,\n    },\n    optionsContainer: {\n      paddingTop: 10,\n      gap: 4,\n    },\n    optionRow: {\n      flexDirection: 'row',\n      alignItems: 'center',\n      paddingVertical: 8,\n      paddingLeft: 20,\n      width: '100%',\n    },\n    ml5: {\n      marginLeft: 5,\n    },\n  });\n  "
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/ViewMembers.tsx",
    "content": "import React from 'react';\nimport { BackHandler, View } from 'react-native';\nimport {\n  CometChatGroupMembers,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  useRoute,\n  useNavigation,\n  RouteProp,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { useConfig } from '../../../config/store';\n\nconst ViewMembers: React.FC = () => {\n  const route = useRoute<RouteProp<RootStackParamList, 'ViewMembers'>>();\n  const navigation = useNavigation();\n  const { group } = route.params;\n  const theme = useTheme();\n\n  const kickUsers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.kickUsers\n  );\n  const banUsers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.banUsers\n  );\n  const promoteDemoteMembers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.promoteDemoteMembers\n  );\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n   );\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  return (\n    <View style={[{ flex: 1, backgroundColor: theme.color.background1 }]}>\n      <CometChatGroupMembers\n        group={group}\n        onBack={() => {\n          navigation.goBack();\n        }}\n        selectionMode=\"none\"\n        showBackButton={true}\n        hideKickMemberOption={!kickUsers}\n        hideBanMemberOption={!banUsers}\n        hideScopeChangeOption={!promoteDemoteMembers}\n        usersStatusVisibility={userAndFriendsPresence}\n      />\n    </View>\n  );\n};\n\nexport default ViewMembers;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/conversations/screens/qr_screen.tsx",
    "content": "import React, { useEffect, useState, useRef } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  Alert,\n  TouchableOpacity,\n  Image,\n} from 'react-native';\nimport { Camera, useCameraDevice, useCodeScanner } from 'react-native-vision-camera';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { useNavigation } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { Animated, Easing } from 'react-native';\nimport Flash from '../../../assets/icons/Flash';\nimport sync from '../../../assets/icons/cometchat_sync_img.png';\n\ninterface ConfigData {\n  data: {\n    builderId: string;\n    settings: any;\n    name: string;\n    type: string;\n    createdAt: number;\n    updatedAt: number;\n    expiresAt: number;\n  };\n}\n\nconst CONFIG_STORAGE_KEY = '@app_config';\nconst MINIMUM_LOADING_TIME = 5000;\n\nconst QRScreen: React.FC = () => {\n  const device = useCameraDevice('back');\n  const [hasPermission, setHasPermission] = useState(false);\n  const [scannedCode, setScannedCode] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const [lineAnim] = useState(new Animated.Value(0));\n  const [isFlashOn, setIsFlashOn] = useState(false);\n  const [loadingProgress] = useState(new Animated.Value(0));\n  \n  const apiResponseReceived = useRef(false);\n  const loadingStartTime = useRef<number>(0);\n  \n  type ChatNavigationProp = StackNavigationProp<\n    RootStackParamList,\n    'Conversation'\n  >;\n  const navigation = useNavigation<ChatNavigationProp>();\n  \n  const codeScanner = useCodeScanner({\n    codeTypes: ['qr', 'ean-13'],\n    onCodeScanned: (codes) => {\n      if (codes.length > 0 && !isLoading && !scannedCode) {\n        const qrData = codes[0].value ?? '';\n        setScannedCode(qrData);\n        fetchDataFromAPI(qrData);\n      }\n    },\n  });\n\n  useEffect(() => {\n    Animated.loop(\n      Animated.sequence([\n        Animated.timing(lineAnim, {\n          toValue: 1,\n          duration: 2000,\n          easing: Easing.linear,\n          useNativeDriver: true,\n        }),\n        Animated.timing(lineAnim, {\n          toValue: 0,\n          duration: 2000,\n          easing: Easing.linear,\n          useNativeDriver: true,\n        }),\n      ])\n    ).start();\n  }, [lineAnim]);\n\n  const toggleFlash = () => {\n    setIsFlashOn(prev => !prev);\n  };\n\n  useEffect(() => {\n    const requestPermissions = async () => {\n      let cameraStatus = await Camera.getCameraPermissionStatus();\n\n      if (cameraStatus !== 'granted') {\n        cameraStatus = await Camera.requestCameraPermission();\n      }\n\n      if (cameraStatus === 'granted') {\n        setHasPermission(true);\n      } else {\n        Alert.alert(\n          'Camera permission denied',\n          'Please enable camera permission in your settings to scan QR codes'\n        );\n      }\n    };\n\n    requestPermissions();\n  }, []);\n\n  const saveConfigToStorage = async (configData: ConfigData) => {\n    try {\n      // Save to AsyncStorage\n      await AsyncStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(configData));\n      try {\n        const { useConfigStore } = await import('../../../config/store');\n        // Use the data property which contains the actual config structure\n        useConfigStore.setState({ config: configData.data });        \n        await AsyncStorage.setItem('@config_updated', 'true');\n      } catch (importError) {\n        console.log('Config store not available or failed to update:', importError);\n      }\n\n      return true;\n    } catch (error) {\n      console.error('Error saving config to AsyncStorage:', error);\n      throw error;\n    }\n  };\n\n  const finishLoading = () => {\n    setIsLoading(false);\n    navigation.goBack();\n  };\n\n  const fetchDataFromAPI = async (qrCodeData: string) => {\n    try {\n      setIsLoading(true);\n      apiResponseReceived.current = false;\n      loadingStartTime.current = Date.now();\n      \n      // Start progress bar animation (5 seconds to match minimum loading time)\n      loadingProgress.setValue(0);\n      Animated.timing(loadingProgress, {\n        toValue: 1,\n        duration: MINIMUM_LOADING_TIME,\n        easing: Easing.ease,\n        useNativeDriver: false,\n      }).start();\n      \n      // QR code contains the builder ID, construct the API URL\n      const builderId = qrCodeData.trim();\n      const apiUrl = `https://apivcb.cometchat.io/v1/builders/${builderId}`;\n      \n      const response = await fetch(apiUrl, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      });\n\n      if (!response.ok) {\n        throw new Error(`API request failed with status ${response.status}`);\n      }\n\n      const data: ConfigData = await response.json();\n      await saveConfigToStorage(data);\n      \n      apiResponseReceived.current = true;\n      \n      const elapsedTime = Date.now() - loadingStartTime.current;\n      const remainingTime = Math.max(0, MINIMUM_LOADING_TIME - elapsedTime);\n      \n      setTimeout(() => {\n        finishLoading();\n      }, remainingTime);\n      \n      return data;\n    } catch (error) {\n      console.error('Error fetching data from API:', error);\n      Alert.alert(\n        'Error',\n        `Failed to update configuration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n        [\n          {\n            text: 'OK',\n            onPress: () => {\n              setIsLoading(false);\n              setScannedCode(null);\n              apiResponseReceived.current = false;\n            }\n          }\n        ]\n      );\n    }\n  };\n\n\n\n  if (!device) {\n    return (\n      <View style={styles.center}>\n        <Text style={styles.centerText}>Loading camera...</Text>\n      </View>\n    );\n  }\n\n  if (!hasPermission) {\n    return (\n      <View style={styles.center}>\n        <Text style={styles.centerText}>No camera permission</Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Camera\n        style={StyleSheet.absoluteFill}\n        device={device}\n        isActive={!isLoading}\n        codeScanner={codeScanner}\n        torch={isFlashOn ? 'on' : 'off'}\n      />\n      \n      {/* Top Overlay */}\n      <View style={styles.topOverlay}>\n        {/* Header */}\n        <View style={styles.header}>\n          <TouchableOpacity \n            style={styles.closeButton} \n            onPress={() => navigation.goBack()}\n          >\n            <Text style={styles.closeIcon}>✕</Text>\n          </TouchableOpacity>\n          \n         <TouchableOpacity style={styles.flashButton} onPress={toggleFlash}>\n            <Flash color={'#FFFFFF'} width={24} height={24} />\n        </TouchableOpacity>\n\n        </View>\n\n        {/* Title Section */}\n        <View style={styles.titleSection}>\n          <Text style={styles.title}>Scan to Preview Your Chat UI</Text>\n          <Text style={styles.subtitle}>\n            Instantly load and test your configuration{'\\n'}from the Visual builder.\n          </Text>\n        </View>\n      </View>\n\n      {/* Simple full overlay approach */}\n      <View style={styles.overlayTop} />\n      <View style={styles.overlayLeft} />\n      <View style={styles.overlayRight} />\n      <View style={styles.overlayBottom} />\n\n      {/* Bottom Overlay */}\n      <View style={styles.bottomOverlay}>\n        {/* Instructions Section */}\n        <View style={styles.instructionsSection}>\n          <Text style={styles.instructionsTitle}>How to Use:</Text>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>1.</Text>\n            <Text style={styles.stepText}>Go to the Visual builder & generate QR code.</Text>\n          </View>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>2.</Text>\n            <Text style={styles.stepText}>Point your camera at the QR code to scan.</Text>\n          </View>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>3.</Text>\n            <Text style={styles.stepText}>Preview your design live on this device instantly.</Text>\n          </View>\n\n          {/* Note Section */}\n          <View style={styles.noteSection}>\n            <Text style={styles.noteTitle}>Note:</Text>\n            <Text style={styles.noteText}>\n              Make sure to save changes on the builder before scanning again to view updates.\n            </Text>\n          </View>\n        </View>\n      </View>\n\n      {/* Camera Container Border */}\n      \n      <View style={styles.cameraBorder}>\n        <Animated.View\n          style={[\n            styles.animatedLine,\n            {\n              transform: [\n                {\n                  translateY: lineAnim.interpolate({\n                    inputRange: [0, 1],\n                    outputRange: [0, 370], \n                  }),\n                },\n              ],\n            },\n          ]}\n        />\n      </View>\n\n      {/* Loading Overlay */}\n      {isLoading && (\n        <View style={styles.loadingOverlay}>\n          <View style={styles.loadingContent}>\n            <Image \n              source={sync} \n              style={styles.syncImage}\n              resizeMode=\"contain\"\n            />\n            <Text style={styles.loadingText}>Syncing with Visual Builder...</Text>\n            <View style={styles.progressBarContainer}>\n              <Animated.View \n                style={[\n                  styles.progressBarFill,\n                  {\n                    width: loadingProgress.interpolate({\n                      inputRange: [0, 1],\n                      outputRange: ['0%', '100%'],\n                    }),\n                  },\n                ]}\n              />\n            </View>\n          </View>\n        </View>\n      )}\n\n      {/* Scanned Code Display */}\n      {scannedCode && !isLoading && (\n        <View style={styles.resultBox}>\n          <Text style={styles.resultLabel}>Scanned:</Text>\n          <Text style={styles.resultText} numberOfLines={2}>\n            {scannedCode}\n          </Text>\n        </View>\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: { \n    flex: 1, \n    backgroundColor: 'black' ,\n  },\n  center: { \n    flex: 1, \n    alignItems: 'center', \n    justifyContent: 'center', \n    backgroundColor: 'black'\n  },\n  centerText: {\n    color: 'white',\n    fontSize: 16,\n  },\n  topOverlay: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    height: 180,\n    zIndex: 3,\n  },\n  overlayTop: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    height: 180,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayLeft: {\n    position: 'absolute',\n    top: 180,\n    left: 0,\n    width: 20,\n    height: 370,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayRight: {\n    position: 'absolute',\n    top: 180,\n    right: 0,\n    width: 20,\n    height: 370,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayBottom: {\n    position: 'absolute',\n    top: 550,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  animatedLine: {\n    position: 'absolute',\n    left: 0,\n    right: 0,\n    height: 3,\n    backgroundColor: '#6366f1',\n    borderRadius: 2,\n  },\n  bottomOverlay: {\n    position: 'absolute',\n    bottom: 0,\n    left: 0,\n    right: 0,\n    top: 560,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 3,\n  },\n  cameraBorder: {\n    position: 'absolute',\n    top: 180,\n    left: 20,\n    right: 20,\n    height: 370,\n    zIndex: 4,\n  },\n  header: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    paddingTop: 20,\n    paddingBottom: 20,\n  },\n  closeButton: {\n    width: 40,\n    height: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  closeIcon: {\n    color: '#FFFFFF',\n    fontSize: 24,\n    fontWeight: 'bold',\n  },\n  flashButton: {\n    width: 40,\n    height: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  flashIcon: {\n    fontSize: 24,\n  },\n  titleSection: {\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    marginBottom: 30,\n  },\n  title: {\n    color: '#FFFFFF',\n    fontSize: 24,\n    fontWeight: 'bold',\n    textAlign: 'center',\n    marginBottom: 12,\n  },\n  subtitle: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    textAlign: 'center',\n    lineHeight: 22,\n  },\n  instructionsSection: {\n    flex: 1,\n    paddingHorizontal: 20,\n    paddingBottom: 40,\n  },\n  instructionsTitle: {\n    color: '#FFFFFF',\n    fontSize: 18,\n    fontWeight: 'bold',\n    marginBottom: 16,\n  },\n  stepContainer: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n    marginBottom: 12,\n  },\n  stepNumber: {\n    color: '#FFFFFF',\n    fontSize: 16,\n    fontWeight: '600',\n    marginRight: 8,\n    minWidth: 20,\n  },\n  stepText: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    lineHeight: 24,\n    flex: 1,\n  },\n  noteSection: {\n    backgroundColor: '#0B7BEA33',\n    borderRadius: 8,\n    padding: 16,\n    marginTop: 20,\n    borderLeftWidth: 4,\n  },\n  noteTitle: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    fontWeight: 'bold',\n    marginBottom: 8,\n  },\n  noteText: {\n    color: '#CCCCCC',\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  resultBox: {\n    position: 'absolute',\n    bottom: 130,\n    alignSelf: 'center',\n    padding: 12,\n    backgroundColor: 'rgba(0,0,0,0.8)',\n    borderRadius: 8,\n    maxWidth: '80%',\n    borderWidth: 1,\n    borderColor: '#6366f1',\n    zIndex: 2,\n  },\n  resultLabel: {\n    color: '#6366f1',\n    fontSize: 12,\n    fontWeight: 'bold',\n    marginBottom: 4,\n  },\n  resultText: { \n    color: 'white', \n    fontSize: 14 \n  },\n  loadingOverlay: {\n    ...StyleSheet.absoluteFillObject,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    justifyContent: 'center',\n    alignItems: 'center',\n    zIndex: 999,\n  },\n  loadingContent: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    width: '100%',\n  },\n  syncImage: {\n    width: 300,\n    height: 455,\n    marginBottom: 24,\n  },\n  loadingText: {\n    color: '#000000',\n    fontSize: 18,\n    fontWeight: '600',\n    marginBottom: 24,\n  },\n  progressBarContainer: {\n    width: 280,\n    height: 8,\n    backgroundColor: '#E5E7EB',\n    borderRadius: 4,\n    overflow: 'hidden',\n  },\n  progressBarFill: {\n    height: '100%',\n    backgroundColor: '#6366f1',\n    borderRadius: 4,\n  },\n});\n\nexport default QRScreen;"
  },
  {
    "path": "examples/SampleAppExpo/src/components/groups/GroupHelper.tsx",
    "content": "import React, {useEffect, useState} from 'react';\nimport {\n  Dimensions,\n  View,\n  Text,\n  TouchableOpacity,\n  TextInput,\n  TouchableWithoutFeedback,\n} from 'react-native';\nimport {\n  CometChatAvatar,\n  CometChatBottomSheet,\n  CometChatUIKitHelper,\n  Icon,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {styles} from './styles';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport GroupAdd from '../../assets/icons/GroupAdd';\nimport Group from '../../assets/icons/Group';\n\n/**\n * Subcomponent for the default AppBar Options\n * (e.g., the \"Add Group\" button/icon on the header).\n */\nexport const GroupScreenAppBarOptions: React.FC<{\n  onPress: () => void;\n}> = ({onPress}) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n\n  return (\n    <View style={{paddingRight: 10}}>\n      <TouchableOpacity onPress={onPress}>\n        <Icon\n          icon={<GroupAdd color={theme.color.primary} height={28} width={28} />}\n          size={28}\n        />\n      </TouchableOpacity>\n    </View>\n  );\n};\n\n/**\n * Bottom sheet for creating a new group.\n */\ninterface CreateGroupBottomSheetProps {\n  visible: boolean;\n  onClose: () => void;\n  onGroupCreated: (group: CometChat.Group) => void;\n}\n\nexport const CreateGroupBottomSheet: React.FC<CreateGroupBottomSheetProps> = ({\n  visible,\n  onClose,\n  onGroupCreated,\n}) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n\n  // Group state\n  const [groupName, setGroupName] = useState('');\n  const [groupPassword, setGroupPassword] = useState('');\n  const [selectedOption, setSelectedOption] = useState('Public');\n  const [showPasswordField, setShowPasswordField] = useState(false);\n  const [showError, setShowError] = useState('');\n  const groupTypes = [\n    { display: t('PUBLIC'), value: 'Public' },\n    { display: t('PRIVATE'), value: 'Private' },\n    { display: t('PASSWORD'), value: 'Password' }\n  ];\n\n  const resetFields = () => {\n    setGroupName('');\n    setGroupPassword('');\n    setSelectedOption('Public');\n    setShowPasswordField(false);\n    setShowError('');\n  };\n\n  const handleDismiss = () => {\n    resetFields(); // clear everything the user typed/selected\n    onClose(); // let the parent know the sheet closed\n  };\n\n  useEffect(() => {\n    if (!showError) return;\n    const timer = setTimeout(() => setShowError(''), 3000);\n    return () => clearTimeout(timer);\n  }, [showError]);\n\n  const handleOptionPress = (option: { display: string, value: string }) => {\n    setSelectedOption(option.value);\n    setShowPasswordField(option.value === 'Password');\n  };\n\n  const handleCreateGroup = async () => {\n    if (!groupName.trim()) {\n      setShowError('Group name cannot be empty');\n      return;\n    }\n    const GUID = 'group_' + Date.now();\n    let groupType = CometChat.GROUP_TYPE.PUBLIC;\n    let password = '';\n\n    switch (selectedOption) {\n      case 'Private':\n        groupType = CometChat.GROUP_TYPE.PRIVATE;\n        break;\n      case 'Password':\n        groupType = CometChat.GROUP_TYPE.PASSWORD;\n        break;\n      default:\n        groupType = CometChat.GROUP_TYPE.PUBLIC;\n    }\n\n    if (groupType === CometChat.GROUP_TYPE.PASSWORD) {\n      if (!groupPassword.trim()) {\n        setShowError('Password is mandatory for password-protected groups');\n        return;\n      }\n      password = groupPassword;\n    }\n\n    const newGroup = new CometChat.Group(GUID, groupName, groupType, password);\n\n    try {\n      const createdGroup = await CometChat.createGroup(newGroup);\n      CometChatUIKitHelper.onGroupCreated(createdGroup);\n      onGroupCreated(createdGroup);\n\n      // Reset fields after creation\n      resetFields();\n      onClose();\n    } catch (error) {\n      console.log('Group creation failed with exception:', error);\n    }\n  };\n\n  return (\n    <CometChatBottomSheet\n      isOpen={visible}\n      onClose={handleDismiss}\n      doNotOccupyEntireHeight={true}\n      scrollEnabled={true}\n      style={{maxHeight: Dimensions.get('window').height * 0.8}}>\n      <View style={styles.bottomSheetContainer}>\n        {/* Header / Icon */}\n        <View style={{alignItems: 'center'}}>\n          <View\n            style={[\n              styles.avatarIconContainer,\n              {backgroundColor: theme.color.background3},\n            ]}>\n            <Icon\n              icon={\n                <Group color={theme.color.primary} height={44} width={44} />\n              }\n              size={44}\n            />\n          </View>\n          <Text\n            style={[\n              {color: theme.color.textPrimary, marginTop: 15},\n              theme.typography.heading2.regular,\n            ]}>\n            {t('NEW__GROUP')}\n          </Text>\n        </View>\n\n        {/* Group Type Tabs */}\n        <View style={{marginVertical: 20}}>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              {marginBottom: 10, color: theme.color.textPrimary},\n            ]}>\n            {t('TYPE')}\n          </Text>\n          <View\n            style={[\n              styles.optionTabsContainer,\n              {\n                borderColor: theme.color.borderLight,\n                backgroundColor: theme.color.background3,\n              },\n            ]}>\n            {groupTypes.map(option => {\n              const isSelected = selectedOption === option.value;\n              return (\n                <TouchableOpacity\n                  key={option.value}\n                  onPress={() => handleOptionPress(option)}\n                  style={{\n                    flex: 1,\n                    alignItems: 'center',\n                    paddingVertical: 8,\n                    borderRadius: 12,\n                    backgroundColor: isSelected\n                      ? theme.color.background1\n                      : 'transparent',\n                  }}>\n                  <Text\n                    style={[\n                      theme.typography.body.medium,\n                      {\n                        color: isSelected\n                          ? theme.color.textHighlight\n                          : theme.color.textSecondary,\n                        fontWeight: isSelected ? 'bold' : 'normal',\n                      },\n                    ]}>\n                    {option.display}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n\n        {/* Group Name Input */}\n        <View style={styles.marginBottom20}>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              {marginBottom: 10, color: theme.color.textPrimary},\n            ]}>\n            {t('NAME')}\n          </Text>\n          <View\n            style={[\n              styles.passwordInputContainer,\n              {\n                borderColor: theme.color.borderLight,\n                backgroundColor: theme.color.background3,\n              },\n            ]}>\n            <TextInput\n              value={groupName}\n              onChangeText={setGroupName}\n              style={[\n                theme.typography.body.regular,\n                {\n                  flex: 1,\n                  color: theme.color.textPrimary,\n                },\n              ]}\n              placeholder={t(\"ENTER_GROUP_NAME\")}\n              placeholderTextColor={theme.color.textTertiary}\n            />\n          </View>\n        </View>\n\n        {/* Group Password (if needed) */}\n        {showPasswordField && (\n          <View style={styles.marginBottom20}>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginBottom: 10, color: theme.color.textPrimary},\n              ]}>\n              {t('PASSWORD')}\n            </Text>\n            <View\n              style={[\n                styles.passwordInputContainer,\n                {\n                  borderColor: theme.color.borderLight,\n                  backgroundColor: theme.color.background3,\n                },\n              ]}>\n              <TextInput\n                value={groupPassword}\n                onChangeText={setGroupPassword}\n                style={[\n                  theme.typography.body.regular,\n                  {flex: 1, color: theme.color.textPrimary},\n                ]}\n                placeholder={t(\"ENTER_PASSWORD\")}\n                placeholderTextColor={theme.color.textTertiary}\n                secureTextEntry\n              />\n            </View>\n          </View>\n        )}\n\n        {showError !== '' && (\n          <View style={styles.toastContainer}>\n            <Text style={[styles.toastMessage, {color: theme.color.error}]}>\n              {showError}\n            </Text>\n          </View>\n        )}\n\n        {/* Create Group Button */}\n        <TouchableWithoutFeedback onPress={handleCreateGroup}>\n          <View\n            style={[\n              styles.createButton,\n              {backgroundColor: theme.color.primaryButtonBackground},\n            ]}>\n            <Text\n              style={[\n                theme.typography.button.medium,\n                {color: theme.color.primaryButtonText},\n              ]}>\n              {t('CREATE_GROUP')}\n            </Text>\n          </View>\n        </TouchableWithoutFeedback>\n      </View>\n    </CometChatBottomSheet>\n  );\n};\n\n/**\n * Bottom sheet for joining a password-protected group.\n */\ninterface JoinGroupBottomSheetProps {\n  visible: boolean;\n  groupToJoin: CometChat.Group | null;\n  onClose: () => void;\n  onJoinSuccess: (joinedGroup: CometChat.Group) => void;\n}\n\nexport const JoinGroupBottomSheet: React.FC<JoinGroupBottomSheetProps> = ({\n  visible,\n  groupToJoin,\n  onClose,\n  onJoinSuccess,\n}) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n  const [enteredPassword, setEnteredPassword] = useState('');\n  const [isPasswordErrorVisible, setIsPasswordErrorVisible] = useState(false);\n\n  useEffect(() => {\n    if (!isPasswordErrorVisible) return;\n    const timer = setTimeout(() => setIsPasswordErrorVisible(false), 3000);\n    return () => clearTimeout(timer);\n  }, [isPasswordErrorVisible]);\n\n  const joinPasswordGroup = async () => {\n    if (!groupToJoin || !enteredPassword.trim()) return;\n\n    try {\n      const joinedGroup = await CometChat.joinGroup(\n        groupToJoin.getGuid(),\n        groupToJoin.getType() as CometChat.GroupType,\n        enteredPassword,\n      );\n      onJoinSuccess(joinedGroup);\n      handleClose();\n    } catch (error) {\n      setIsPasswordErrorVisible(true);\n      console.log('Error joining password group:', error);\n    }\n  };\n\n  const handleClose = () => {\n    setEnteredPassword('');\n    setIsPasswordErrorVisible(false);\n    onClose();\n  };\n\n  return (\n    <CometChatBottomSheet\n      isOpen={visible}\n      onClose={handleClose}\n      scrollEnabled={true}\n      doNotOccupyEntireHeight={true}>\n      {groupToJoin && (\n        <View style={styles.joiningGroup}>\n          {/* Header */}\n          <Text\n            style={[\n              theme.typography.heading3.bold,\n              {\n                marginBottom: 20,\n                textAlign: 'center',\n                color: theme.color.textPrimary,\n              },\n            ]}>\n            {t('JOIN_GROUP')}\n          </Text>\n\n          {/* Group Info */}\n          <View style={{alignItems: 'center', marginBottom: 20}}>\n            <CometChatAvatar\n              name={groupToJoin.getName()}\n              image={{uri: groupToJoin.getIcon()}}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                {marginTop: 10, color: theme.color.textPrimary},\n              ]}>\n              {groupToJoin.getName()}\n            </Text>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginTop: 5, color: theme.color.textSecondary},\n              ]}>\n              {groupToJoin.getMembersCount()} {t('MEMBERS')}\n            </Text>\n          </View>\n\n          {/* Password Input */}\n          <View style={styles.marginBottom20}>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginBottom: 10, color: theme.color.textPrimary},\n              ]}>\n              {t('ENTER_PASSWORD')}\n            </Text>\n            <View\n              style={[\n                styles.passwordInputContainer,\n                {\n                  borderColor: theme.color.borderLight,\n                  backgroundColor: theme.color.background3,\n                },\n              ]}>\n              <TextInput\n                value={enteredPassword}\n                onChangeText={setEnteredPassword}\n                style={[\n                  theme.typography.body.regular,\n                  {flex: 1, color: theme.color.textPrimary},\n                ]}\n                placeholder={t('ENTER_PASSWORD')}\n                placeholderTextColor={theme.color.textTertiary}\n                secureTextEntry\n              />\n            </View>\n          </View>\n\n          {/* Incorrect Password Toast */}\n          {isPasswordErrorVisible && (\n            <View style={styles.toastContainer}>\n              <Text style={[styles.toastMessage, {color: theme.color.error}]}>\n                {t('PASSWORD_INCORRECT_GROUP')}\n              </Text>\n            </View>\n          )}\n\n          {/* Join Button */}\n          <TouchableWithoutFeedback onPress={joinPasswordGroup}>\n            <View\n              style={[\n                styles.joinGroupButton,\n                {backgroundColor: theme.color.primaryButtonBackground},\n              ]}>\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  {color: theme.color.primaryButtonText},\n                ]}>\n                {t('JOIN_GROUP')}\n              </Text>\n            </View>\n          </TouchableWithoutFeedback>\n        </View>\n      )}\n    </CometChatBottomSheet>\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/groups/Groups.tsx",
    "content": "import React, { useCallback, useEffect, useState } from 'react';\nimport { View } from 'react-native';\nimport { useFocusEffect, useNavigation } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  CometChatGroups,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { RootStackParamList } from '../../navigation/types';\nimport { styles } from './styles';\n\nimport {\n  GroupScreenAppBarOptions,\n  CreateGroupBottomSheet,\n  JoinGroupBottomSheet,\n} from './GroupHelper';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { useConfig } from '../../config/store';\n\ntype GroupNavigationProp = StackNavigationProp<RootStackParamList, 'Groups'>;\n\ninterface GroupsProps {\n  hideHeader?: boolean;\n}\n\nconst Groups: React.FC<GroupsProps> = ({hideHeader = false}) => {\n  const createGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.createGroup\n  );\n  const theme = useTheme();\n  const navigation = useNavigation<GroupNavigationProp>();\n  const [pendingChat, setPendingChat] = useState<CometChat.Group | null>(null);\n\n  // State to handle showing/hiding bottom sheets\n  const [isCreateGroupSheetVisible, setCreateGroupSheetVisible] =\n    useState(false);\n  const [isJoinGroupSheetVisible, setJoinGroupSheetVisible] = useState(false);\n\n  // State for the group that user wants to join\n  const [groupToJoin, setGroupToJoin] = useState<CometChat.Group | null>(null);\n\n  // Condition to hide the entire screen if needed\n  const [shouldHide, setShouldHide] = useState(false);\n\n  useEffect(() => {\n    if (!isCreateGroupSheetVisible && !isJoinGroupSheetVisible && pendingChat) {\n      const timer = setTimeout(() => {\n        navigation.navigate(SCREEN_CONSTANTS.MESSAGES, { group: pendingChat });\n        setPendingChat(null);\n      }, 300);\n      return () => clearTimeout(timer);\n    }\n  }, [isCreateGroupSheetVisible, isJoinGroupSheetVisible, pendingChat]);\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, [navigation]),\n  );\n\n  /**\n   * Navigates to the Messages screen after group creation or join.\n   */\n  const handleNavigateToMessages = (group: CometChat.Group) => {\n    // close any open sheet first\n    setCreateGroupSheetVisible(false);\n    setJoinGroupSheetVisible(false);\n\n    // save the group – navigation will happen in a useEffect below\n    setPendingChat(group);\n  };\n\n  /**\n   * Handle group item press:\n   * - If joined, open chat\n   * - If public, join automatically\n   * - If password, show the join modal\n   */\n  const handleGroupItemPress = (group: CometChat.Group) => {\n    if (group.getHasJoined()) {\n      handleNavigateToMessages(group);\n      return;\n    }\n\n    if (group.getType() === CometChat.GROUP_TYPE.PUBLIC) {\n      joinPublicGroup(group);\n    } else if (group.getType() === CometChat.GROUP_TYPE.PASSWORD) {\n      setGroupToJoin(group);\n      setJoinGroupSheetVisible(true);\n    }\n    // For private group, you'd have a different flow.\n  };\n\n  const joinPublicGroup = async (group: CometChat.Group) => {\n    try {\n      const joinedGroup = await CometChat.joinGroup(\n        group.getGuid(),\n        group.getType() as CometChat.GroupType,\n        '',\n      );\n\n      handleNavigateToMessages(joinedGroup);\n      CometChatUIEventHandler.emitGroupEvent(\n        CometChatUIEvents.ccGroupMemberJoined,\n        {\n          joinedUser: CometChatUIKit.loggedInUser,\n          joinedGroup: joinedGroup,\n        },\n      );\n    } catch (error) {\n      console.log('Error joining public group:', error);\n    }\n  };\n\n  if (shouldHide) return null;\n\n  return (\n    <View\n      style={[\n        styles.safeAreaContainer,\n        { backgroundColor: theme.color.background1 },\n      ]}\n    >\n      {/* CometChatGroups list component */}\n      <CometChatGroups\n        AppBarOptions={\n          createGroup\n            ? () => (\n              <GroupScreenAppBarOptions\n                onPress={() => setCreateGroupSheetVisible(true)}\n              />\n            )\n            : undefined\n        }\n        onItemPress={handleGroupItemPress}\n        hideHeader={hideHeader}\n      />\n\n      {/* Create Group Bottom Sheet */}\n      <CreateGroupBottomSheet\n        visible={isCreateGroupSheetVisible}\n        onClose={() => setCreateGroupSheetVisible(false)}\n        onGroupCreated={handleNavigateToMessages}\n      />\n\n      {/* Join Group Bottom Sheet */}\n      <JoinGroupBottomSheet\n        visible={isJoinGroupSheetVisible}\n        groupToJoin={groupToJoin}\n        onClose={() => {\n          setJoinGroupSheetVisible(false);\n          setGroupToJoin(null);\n        }}\n        onJoinSuccess={handleNavigateToMessages}\n      />\n    </View>\n  );\n};\n\nexport default Groups;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/groups/styles.ts",
    "content": "import {Platform, StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  safeAreaContainer: {\n    flex: 1,\n  },\n  bottomSheetContainer: {\n    paddingHorizontal: 20,\n    paddingBottom: Platform.OS==='android'  ? 10: 30,\n    flex: 1,\n  },\n  marginBottom20: {\n    marginBottom: 20,\n  },\n  toastContainer: {\n    alignItems: 'flex-start',\n  },\n  toastMessage: {\n    paddingVertical: 10,\n  },\n  passwordInputContainer: {\n    borderWidth: 1,\n    flexDirection: 'row',\n    borderRadius: 12,\n    paddingLeft: 10,\n    paddingTop: Platform.select({android: 0, ios: 8}),\n    paddingBottom: Platform.select({android: 0, ios: 12}),\n  },\n  avatarIconContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    width: 80,\n    height: 80,\n    borderRadius: 50,\n  },\n  optionTabsContainer: {\n    borderWidth: 1,\n    flexDirection: 'row',\n    borderRadius: 12,\n    padding: Platform.OS === 'ios' ? 5 : 2,\n    justifyContent: 'space-between',\n    alignItems: 'center',\n  },\n  createButton: {\n    borderRadius: 8,\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingHorizontal: 20,\n    paddingVertical: 12,\n  },\n  joinGroupButton: {\n    borderRadius: 8,\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingHorizontal: 20,\n    paddingVertical: 12,\n  },\n  joiningGroup: {\n    paddingHorizontal: 20,\n    flex: 1,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/login/AppCredentials.tsx",
    "content": "import React, { useEffect, useMemo, useState } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TextInput,\n  TouchableOpacity,\n  Image,\n  useColorScheme,\n  Platform,\n  StatusBar,\n  Dimensions,\n  ScrollView,\n  KeyboardAvoidingView,\n  BackHandler,\n  Keyboard,\n} from 'react-native';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport {\n  CometChatUIKit,\n  UIKitSettings,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { navigate, navigationRef } from '../../navigation/NavigationService';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useFocusEffect } from '@react-navigation/native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { useHeaderHeight } from '@react-navigation/elements';\n\nconst AppCredentials: React.FC = () => {\n  const [storedAppId, setStoredAppId] = useState<string>('');\n  const [storedAuthKey, setStoredAuthKey] = useState<string>('');\n  const [storedRegion, setStoredRegion] = useState<string>('US');\n\n  // These are the *editable* states bound to the TextInput fields\n  const [appId, setAppId] = useState<string>('');\n  const [authKey, setAuthKey] = useState<string>('');\n  const [selectedRegion, setSelectedRegion] = useState<string>('US');\n\n  // Toast state for showing error messages\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const { width } = Dimensions.get('window');\n  const mode = useColorScheme();\n\n  const insets = useSafeAreaInsets();\n  const headerHeight = useHeaderHeight(); // returns 0 if no header\n  const statusBarHeight =\n    Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;\n\n  const [keyboardBehavior, setKeyboardBehavior] = useState<\n    'padding' | 'height' | undefined\n  >(Platform.OS === 'ios' ? 'padding' : 'height');\n\n  useEffect(() => {\n    const showListener = Keyboard.addListener('keyboardDidShow', () => {\n      setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');\n    });\n\n    const hideListener = Keyboard.addListener('keyboardDidHide', () => {\n      setKeyboardBehavior(undefined); // Remove behavior when keyboard hides\n    });\n\n    return () => {\n      showListener.remove();\n      hideListener.remove();\n    };\n  }, []);\n  // For iOS use insets.top (not status bar) + header height\n  const keyboardVerticalOffset = useMemo(() => {\n  return Platform.OS === 'ios'\n    ? (insets.top || 0) + (headerHeight || 0)\n    : (statusBarHeight || 0) + (headerHeight || 0);\n}, [insets.top, headerHeight, statusBarHeight]);\n\n  // Compute if form is valid (all fields provided)\n  const isFormValid =\n    appId.trim().length > 0 &&\n    authKey.trim().length > 0 &&\n    selectedRegion.trim().length > 0;\n\n  // Load existing credentials if any\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        navigationRef.goBack();\n        return true; // Prevent default behavior\n      };\n      const backHandler = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      async function loadCredentials() {\n        try {\n          const credentialsStr = await AsyncStorage.getItem('appCredentials');\n          if (credentialsStr) {\n            const credentials = JSON.parse(credentialsStr);\n            // Update 'stored' states\n            setStoredAppId(credentials.appId || '');\n            setStoredAuthKey(credentials.authKey || '');\n            setStoredRegion(credentials.region || 'US');\n\n            // Also set the fields so user sees them pre-populated\n            setAppId(credentials.appId || '');\n            setAuthKey(credentials.authKey || '');\n            setSelectedRegion(credentials.region || 'US');\n          }\n        } catch (error) {\n          console.log('Error loading stored credentials:', error);\n        }\n      }\n\n      loadCredentials();\n\n      return () => backHandler.remove();\n    }, []),\n  );\n\n  const showToast = (message: string) => {\n    setToastMessage(message);\n    setTimeout(() => {\n      setToastMessage(null);\n    }, 2500);\n  };\n\n  const handleContinue = async (): Promise<void> => {\n    // Validate the inputs.\n    // If the user has modified a field and cleared it (i.e. the input becomes empty),\n    // show a toast message.\n    if (!appId.trim()) {\n      showToast('Please enter App ID');\n      return;\n    }\n    if (!authKey.trim()) {\n      showToast('Please enter Auth Key');\n      return;\n    }\n    if (!selectedRegion.trim()) {\n      showToast('Please select a region');\n      return;\n    }\n\n    // Since all fields are non-empty, use their current values.\n    const newAppId = appId.trim();\n    const newAuthKey = authKey.trim();\n    const newRegion = selectedRegion.trim();\n\n    try {\n      const credentials = {\n        region: newRegion,\n        appId: newAppId,\n        authKey: newAuthKey,\n      };\n      console.log('Saving credentials:', credentials);\n      await AsyncStorage.setItem('appCredentials', JSON.stringify(credentials));\n\n      // Re-initialize with updated credentials\n      await CometChatUIKit.init({\n        appId: newAppId,\n        authKey: newAuthKey,\n        region: newRegion,\n        subscriptionType: CometChat.AppSettings\n          .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n      });\n    } catch (error) {\n      console.error('Failed to save credentials', error);\n    }\n\n    // Navigate to the next screen.\n    navigate('BottomTabNavigator');\n    navigationRef.reset({\n      index: 0,\n      routes: [{ name: SCREEN_CONSTANTS.SAMPLE_USER }],\n    });\n  };\n\n  return (\n    <View\n      style={[styles.container, { backgroundColor: theme.color.background2 }]}\n    >\n      <KeyboardAvoidingView\n        style={{ flex: 1 }}\n        behavior={keyboardBehavior} // Use dynamic behavior\n        keyboardVerticalOffset={keyboardVerticalOffset}\n      >\n        <View style={styles.contentContainer}>\n          <ScrollView\n            contentContainerStyle={styles.scrollContent}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {/* Header/Logo */}\n            <View style={styles.logoContainer}>\n              <Image\n                // NOTE: actual asset filenames are lowercase (dark.png, light.png)\n                source={\n                  mode === 'dark'\n                    ? require('../../assets/icons/dark.png')\n                    : require('../../assets/icons/light.png')\n                }\n                style={{\n                  width: width * 0.25,\n                  height: width * 0.25,\n                  resizeMode: 'contain',\n                }}\n              />\n            </View>\n\n            {/* Title */}\n            <Text\n              style={[\n                theme.typography.heading2.bold,\n                {\n                  color: theme.color.textPrimary,\n                  marginBottom: 20,\n                  alignSelf: 'center',\n                },\n              ]}\n            >\n              {t('APP_CREDENTIALS')}\n            </Text>\n\n            {/* Region Selector */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, marginBottom: 10 },\n                ]}\n              >\n                {t('REGION')}\n              </Text>\n              <View style={styles.regionRow}>\n                {/* US */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'US'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'US'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('US')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      // us flag filename is lowercase in assets\n                      source={require('../../assets/icons/us.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      US\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* EU */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('EU')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      // eu flag filename is lowercase in assets\n                      source={require('../../assets/icons/eu.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      EU\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* IN */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('IN')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      // india flag filename appears as india.png in assets\n                      source={require('../../assets/icons/india.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      IN\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n              </View>\n            </View>\n\n            {/* App ID */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, paddingBottom: 5 },\n                ]}\n              >\n                APP ID\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={appId}\n                onChangeText={setAppId}\n                placeholder=\"Enter the App ID\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n\n            {/* Auth Key */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, paddingBottom: 5 },\n                ]}\n              >\n                Auth Key\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={authKey}\n                onChangeText={setAuthKey}\n                placeholder=\"Enter the Auth Key\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n          </ScrollView>\n\n          {/* Continue Button */}\n          <View style={styles.buttonWrapper}>\n            <TouchableOpacity\n              style={[\n                styles.continueButton,\n                {\n                  backgroundColor: theme.color.primaryButtonBackground,\n                  opacity: isFormValid ? 1 : 0.6,\n                },\n              ]}\n              onPress={handleContinue}\n            >\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  { textAlign: 'center', color: theme.color.staticWhite },\n                ]}\n              >\n                {t('CONTINUE')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n      {/* Toast Message */}\n      {toastMessage && (\n        <View style={styles.toastContainer}>\n          <Text style={styles.toastText}>{toastMessage}</Text>\n        </View>\n      )}\n    </View>\n  );\n};\n\nexport default AppCredentials;\n\nconst styles = StyleSheet.create({\n  logoContainer: {\n    alignItems: 'center',\n    marginTop: Platform.OS === 'android' ? 30 : 50,\n    marginBottom: 20,\n  },\n  inputContainer: {\n    width: '100%',\n    marginTop: 20,\n  },\n  regionRow: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n  },\n  flagContainer: {\n    width: '32%',\n    borderWidth: 2,\n    borderColor: 'transparent',\n    borderRadius: 12,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  flagInnerContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 10,\n    gap: 5,\n  },\n  flagImage: {\n    width: 30,\n    height: 30,\n    resizeMode: 'contain',\n  },\n  input: {\n    paddingHorizontal: 12,\n    paddingVertical: 10,\n    borderRadius: 8,\n    borderWidth: 1,\n  },\n  continueButton: {\n    borderRadius: 8,\n    paddingVertical: 12,\n    width: '100%',\n  },\n  toastContainer: {\n    position: 'absolute',\n    bottom: '8%',\n    left: 20,\n    right: 20,\n    backgroundColor: '#C73C3E',\n    padding: 6,\n    borderRadius: 8,\n    alignItems: 'center',\n  },\n  toastText: {\n    color: '#fff',\n    fontSize: 14,\n  },\n  container: {\n    flex: 1,\n  },\n  contentContainer: {\n    flex: 1,\n    paddingHorizontal: 16,\n  },\n  scrollContent: {\n    flexGrow: 1,\n    paddingBottom: 80,\n  },\n  buttonWrapper: {\n    justifyContent: 'center',\n    alignItems: 'center',\n    paddingVertical: 10,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/login/SampleUser.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TouchableOpacity,\n  TextInput,\n  Image,\n  Dimensions,\n  useColorScheme,\n  Pressable,\n  ImageSourcePropType,\n  KeyboardAvoidingView,\n  Platform,\n  ScrollView,\n  ActivityIndicator,\n  StatusBar,\n  Keyboard,\n} from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatAvatar,\n  CometChatUIKit,\n  Icon,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport Check from '../../assets/icons/CheckFill';\nimport { sampleData } from '../../utils/helper';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { navigate, navigationRef } from '../../navigation/NavigationService';\nimport Skeleton from './Skeleton';\nimport {\n  SafeAreaView,\n  useSafeAreaInsets,\n} from 'react-native-safe-area-context';\nimport { useHeaderHeight } from '@react-navigation/elements';\n\ntype GridItem = CometChat.User | { dummy: true };\n\nconst LoginScreen: React.FC = () => {\n  const [users, setUsers] = useState<CometChat.User[]>([]);\n  const [selectedUser, setSelectedUser] = useState<string | null>(null);\n  const [userUID, setUserUID] = useState<string>('');\n  const [isLoading, setIsLoading] = useState<boolean>(false);\n  const [loadingUsers, setLoadingUsers] = useState<boolean>(true);\n\n  const theme = useTheme();\n  const mode = useColorScheme();\n  const { width } = Dimensions.get('window');\n  const statusBarHeight =\n    Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;\n\n  const insets = useSafeAreaInsets();\n  const headerHeight = useHeaderHeight(); // returns 0 if no header\n  const keyboardVerticalOffset =\n    Platform.OS === 'ios'\n      ? (insets.top + 8 || 0) + (headerHeight || 0)\n      : (statusBarHeight + 6 || 0) + (headerHeight || 0);\n\n  const [keyboardBehavior, setKeyboardBehavior] = useState<\n    'padding' | 'height' | undefined\n  >(Platform.OS === 'ios' ? 'padding' : 'height');\n\n  useEffect(() => {\n    const showListener = Keyboard.addListener('keyboardDidShow', () => {\n      setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');\n    });\n\n    const hideListener = Keyboard.addListener('keyboardDidHide', () => {\n      setKeyboardBehavior(undefined); // Remove behavior when keyboard hides\n    });\n\n    return () => {\n      showListener.remove();\n      hideListener.remove();\n    };\n  }, []);\n\n  useEffect(() => {\n    (async function loadUsers(): Promise<void> {\n      try {\n        setLoadingUsers(true);\n        const fetchedUsers = await fetchUsers();\n        setUsers(fetchedUsers);\n      } catch (error) {\n        console.error(error);\n      } finally {\n        setLoadingUsers(false);\n      }\n    })();\n  }, []);\n\n  const handleSelectUser = (user: CometChat.User): void => {\n    setSelectedUser(user.getUid());\n    setUserUID('');\n  };\n\n  const handleContinue = async () => {\n    if ((!selectedUser && !userUID.trim()) || isLoading) return;\n    setIsLoading(true);\n    const uid: string = userUID.trim() || selectedUser!;\n    try {\n      await CometChatUIKit.login({ uid });\n      navigate('BottomTabNavigator');\n      navigationRef.reset({\n        index: 0,\n        routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n      });\n    } catch (error: any) {\n      console.log('Login failed with exception:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  /**\n   * Fetch users from a remote sample JSON file.\n   * Falls back to local sample data if there's an error.\n   */\n  async function fetchUsers(): Promise<CometChat.User[]> {\n    try {\n      const response = await fetch(\n        'https://assets.cometchat.io/sampleapp/sampledata.json',\n      );\n\n      if (response.ok) {\n        const data = await response.json();\n        const fetchedUsers = data.users || [];\n        return fetchedUsers.map((user: any) => new CometChat.User(user));\n      } else {\n        throw new Error('Failed to load users');\n      }\n    } catch (error) {\n      console.error('Exception while fetching users:', error);\n      return await getDefaultUsers();\n    }\n  }\n\n  /**\n   * Get users from local sample data (used in case the remote fetch fails).\n   */\n  async function getDefaultUsers(): Promise<CometChat.User[]> {\n    const localUsers = sampleData.users || [];\n    return localUsers.map((user: any) => new CometChat.User(user));\n  }\n\n  /**\n   * Returns the appropriate image source object for the avatar.\n   */\n  const getAvatarSource = (\n    avatar: string | ImageSourcePropType,\n  ): ImageSourcePropType => {\n    if (typeof avatar === 'string') {\n      if (avatar.startsWith('http://') || avatar.startsWith('https://')) {\n        return { uri: avatar };\n      }\n    }\n    return avatar as ImageSourcePropType;\n  };\n\n  // Show skeleton if the API is still loading or if no users are available.\n  const showSkeleton = loadingUsers || users.length === 0;\n\n  // Compute grid data only if users are available.\n  let gridData: GridItem[] = [];\n  if (users.length > 0) {\n    gridData = [...users];\n    const numColumns = 3;\n    const numberOfElementsLastRow = users.length % numColumns;\n    if (numberOfElementsLastRow !== 0) {\n      for (let i = 0; i < numColumns - numberOfElementsLastRow; i++) {\n        gridData.push({ dummy: true });\n      }\n    }\n  }\n\n  const Loading = () => {\n    return (\n      <View\n        style={{\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        <ActivityIndicator\n          size=\"small\"\n          color={theme.color.staticWhite}\n          style={{ alignSelf: 'center', justifyContent: 'center' }}\n        />\n      </View>\n    );\n  };\n\n  return (\n    <SafeAreaView\n      style={[styles.container, { backgroundColor: theme.color.background2 }]}\n      edges={['top']}\n    >\n      <KeyboardAvoidingView\n        style={styles.keyboardAvoidingContainer}\n        behavior={keyboardBehavior} // Use dynamic behavior\n        keyboardVerticalOffset={keyboardVerticalOffset}\n      >\n        <ScrollView\n          style={{ flex: 1 }}\n          contentContainerStyle={styles.scrollContainer}\n          keyboardShouldPersistTaps=\"handled\"\n          showsVerticalScrollIndicator={false}\n        >\n          {/* App Logo */}\n          <View style={styles.logoContainer}>\n            <Image\n              // NOTE: actual asset filenames are lowercase (dark.png, light.png)\n              source={\n                mode === 'dark'\n                  ? require('../../assets/icons/dark.png')\n                  : require('../../assets/icons/light.png')\n              }\n              style={{\n                width: width * 0.25,\n                height: width * 0.25,\n                resizeMode: 'contain',\n              }}\n            />\n          </View>\n\n          {/* Title */}\n          <Text\n            style={[\n              theme.typography.heading2.bold,\n              styles.logInTitle,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Log In\n          </Text>\n\n          {/* Subtitle */}\n          <Text\n            style={[\n              theme.typography.body.medium,\n              styles.subtitle,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Choose a Sample User\n          </Text>\n\n          {/* Sample Users Grid */}\n          <View style={styles.userGridWrapper}>\n            {showSkeleton ? (\n              <Skeleton />\n            ) : (\n              <View style={styles.usersContainer}>\n                {gridData.map((item, index) => {\n                  // Render a blank view for dummy items\n                  if ('dummy' in item && item.dummy) {\n                    return (\n                      <View key={`dummy-${index}`} style={styles.userCard} />\n                    );\n                  }\n\n                  // Otherwise, render a user\n                  const user = item as CometChat.User;\n                  const isSelected = selectedUser === user.getUid();\n                  const firstName = user.getName();\n\n                  return (\n                    <Pressable\n                      key={user.getUid()}\n                      style={[\n                        styles.userCard,\n                        {\n                          borderWidth: isSelected ? 1.5 : 1,\n                          borderColor: isSelected\n                            ? theme.color.borderHighlight\n                            : theme.color.borderLight,\n                          backgroundColor: isSelected\n                            ? theme.color.extendedPrimary50\n                            : theme.color.background1,\n                        },\n                      ]}\n                      onPress={() => handleSelectUser(user)}\n                    >\n                      {/* Show the check icon ONLY if selected */}\n                      {isSelected && (\n                        <View style={styles.checkIconContainer}>\n                          <Icon\n                            icon={\n                              <Check\n                                color={theme.color.staticWhite}\n                                height={18}\n                                width={18}\n                              />\n                            }\n                          />\n                        </View>\n                      )}\n                      <CometChatAvatar\n                        name={user.getName()}\n                        image={getAvatarSource(user.getAvatar())}\n                      />\n                      {/* Display only the first name */}\n                      <Text\n                        style={[\n                          theme.typography.body.medium,\n                          styles.firstNameText,\n                          { color: theme.color.textPrimary },\n                        ]}\n                      >\n                        {firstName}\n                      </Text>\n                      <Text\n                        style={[\n                          theme.typography.caption1.regular,\n                          styles.uidText,\n                          { color: theme.color.textSecondary },\n                        ]}\n                      >\n                        {user.getUid()}\n                      </Text>\n                    </Pressable>\n                  );\n                })}\n              </View>\n            )}\n          </View>\n\n          {/* Horizontal divider with \"Or\" in the middle */}\n          <View style={styles.dividerRow}>\n            <View\n              style={[\n                styles.divider,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                { color: theme.color.textTertiary },\n              ]}\n            >\n              Or\n            </Text>\n            <View\n              style={[\n                styles.divider,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            />\n          </View>\n\n          {/* UID Input */}\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.uidLabel,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Enter Your UID\n          </Text>\n          <TextInput\n            placeholder=\" Enter UID\"\n            placeholderTextColor={theme.color.textTertiary}\n            style={[\n              theme.typography.body.regular,\n              styles.uidInput,\n              {\n                borderColor: theme.color.borderLight,\n                color: theme.color.textPrimary,\n              },\n            ]}\n            value={userUID}\n            onChangeText={(text: string) => {\n              setUserUID(text);\n              setSelectedUser(null);\n            }}\n          />\n        </ScrollView>\n\n        {/* Bottom container with \"Continue\" button and \"Change App Credentials\" */}\n        <View style={styles.bottomContainer}>\n          <TouchableOpacity\n            style={[\n              styles.continueButton,\n              {\n                backgroundColor: theme.color.primaryButtonBackground,\n              },\n            ]}\n            onPress={handleContinue}\n            disabled={(!selectedUser && !userUID.trim()) || isLoading}\n          >\n            {isLoading ? (\n              <Loading />\n            ) : (\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.continueButtonText,\n                  { color: theme.color.staticWhite },\n                ]}\n              >\n                Continue\n              </Text>\n            )}\n          </TouchableOpacity>\n\n          <View style={styles.changeCredentialsWrapper}>\n            <Text\n              style={[\n                theme.typography.body.regular,\n                { color: theme.color.textSecondary },\n              ]}\n            >\n              Change{' '}\n            </Text>\n            <TouchableOpacity\n              style={styles.changeCredentialsContainer}\n              onPress={() => {\n                navigationRef.navigate(SCREEN_CONSTANTS.APP_CRED);\n              }}\n            >\n              <Text\n                style={[\n                  theme.typography.body.regular,\n                  { color: theme.color.primary },\n                ]}\n              >\n                App Credentials\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </SafeAreaView>\n  );\n};\n\nexport default LoginScreen;\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  keyboardAvoidingContainer: {\n    flex: 1,\n  },\n  scrollContainer: {\n    paddingHorizontal: 16,\n    paddingTop: 16,\n  },\n  logoContainer: {\n    alignItems: 'center',\n    marginBottom: 16,\n  },\n  logInTitle: {\n    marginBottom: 16,\n    alignSelf: 'center',\n  },\n  subtitle: {\n    marginBottom: 6,\n  },\n  usersContainer: {\n    flexDirection: 'row',\n    flexWrap: 'wrap',\n    justifyContent: 'space-around',\n  },\n  userCard: {\n    position: 'relative',\n    width: '30%',\n    borderRadius: 8,\n    paddingVertical: 16,\n    paddingHorizontal: 8,\n    marginBottom: 12,\n    alignItems: 'center',\n    overflow: 'hidden',\n  },\n  checkIconContainer: {\n    position: 'absolute',\n    top: 0,\n    right: 0,\n    borderBottomLeftRadius: 10,\n    borderTopRightRadius: 7,\n    width: '27%',\n    height: '22%',\n    backgroundColor: '#7367F0',\n    alignItems: 'center',\n    justifyContent: 'center',\n    zIndex: 2,\n  },\n  firstNameText: {\n    marginTop: 8,\n    textAlign: 'center',\n  },\n  uidText: {\n    marginTop: 4,\n    textAlign: 'center',\n  },\n  dividerRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginVertical: 16,\n    gap: 10,\n  },\n  divider: {\n    flex: 1,\n    height: 1,\n    borderWidth: 0.5,\n  },\n  uidLabel: {\n    paddingBottom: 5,\n  },\n  uidInput: {\n    borderWidth: 1,\n    borderRadius: 8,\n    padding: 10,\n    marginBottom: 24,\n  },\n  bottomContainer: {\n    paddingHorizontal: 16,\n    backgroundColor: 'transparent',\n  },\n  continueButton: {\n    paddingVertical: 12,\n    borderRadius: 6,\n    marginBottom: 12,\n  },\n  continueButtonText: {\n    alignSelf: 'center',\n  },\n  changeCredentialsWrapper: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'center',\n    marginBottom: 10,\n  },\n  changeCredentialsContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  userGridWrapper: {\n    minHeight: 240,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/login/Skeleton.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Dimensions, Easing, StyleSheet, useColorScheme, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\n// import { useThemeInternal } from \"../../../theme/hook\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonBox = ({ index, boxSize, gradientColors }: any) => {\n  // Determine if the box is the last in its row\n  const isLastInRow = (index + 1) % 3 === 0;\n\n  return (\n    <Svg\n      height={boxSize}\n      width={boxSize}\n      viewBox=\"0 0 100 100\"\n      fill=\"none\"\n      style={[\n        styles.skeletonBox,\n        {\n          width: boxSize,\n          height: boxSize,\n          marginRight: isLastInRow ? 0 : 8,\n        },\n      ]}\n    >\n      <Defs>\n        <LinearGradient\n          id={`paint0_linear_${index}`}\n          x1={10}\n          y1={50}\n          x2={90}\n          y2={50}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <Stop stopColor={gradientColors[0]} />\n          <Stop offset={1} stopColor={gradientColors[1]} />\n        </LinearGradient>\n      </Defs>\n      <Rect\n        x={10}\n        y={10}\n        width={80}\n        height={80}\n        rx={15}\n        ry={15}\n        fill={`url(#paint0_linear_${index})`}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const animatedValue = useRef(new Animated.Value(0)).current;\n  const [isLoading, setIsLoading] = useState(true);\n  const  mode  = useColorScheme();\n\n  // Define static colors\n  const color = {\n    staticBlack: \"#000000\",\n    staticWhite: \"#FFFFFF\",\n  };\n\n  // Define skeletonStyle based on the theme mode\n  const skeletonStyle =\n    mode === \"light\"\n      ? {\n          linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n          shimmerBackgroundColor: color.staticBlack,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        }\n      : {\n          linearGradientColors: [\"#383838\", \"#272727\"],\n          shimmerBackgroundColor: color.staticWhite,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        };\n\n  const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } =\n    skeletonStyle;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n\n    // Simulate a loading time of 3 seconds\n    const loadData = setTimeout(() => {\n      setIsLoading(false);\n    }, 3000);\n\n    return () => clearTimeout(loadData);\n  }, [animatedValue, speed]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth, screenWidth],\n  });\n\n  if (!isLoading) {\n    return null;\n  }\n\n  const boxSize = (screenWidth - 22) / 3;\n  const shimmerWidth = screenWidth;\n  const shimmerHeight = boxSize + 16;\n\n  return (\n    <View style={styles.container}>\n      <View style={styles.grid}>\n        {new Array(6).fill(0).map((_, index) => (\n          <SkeletonBox\n            key={index}\n            index={index}\n            boxSize={boxSize}\n            gradientColors={linearGradientColors}\n          />\n        ))}\n      </View>\n      <Animated.View\n        style={[\n          styles.animatedShimmer,\n          {\n            width: shimmerWidth,\n            height: shimmerHeight,\n            transform: [{ translateX: shimmerTranslateX }],\n            backgroundColor: shimmerBackgroundColor,\n            opacity: shimmerOpacity,\n          },\n        ]}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    position: \"relative\",\n    borderRadius: 16,\n    overflow: \"hidden\",\n  },\n  grid: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n  },\n  skeletonBox: {\n    marginBottom: 10,\n    marginHorizontal: -10,\n  },\n  animatedShimmer: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n  },\n});\n\nexport default Skeleton;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/components/users/Users.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatUsers, useTheme } from '@cometchat/chat-uikit-react-native';\nimport React, { useCallback } from 'react';\nimport { View } from 'react-native';\nimport { useFocusEffect, useNavigation } from '@react-navigation/native';\nimport { RootStackParamList } from '../../navigation/types';\nimport { StackNavigationProp } from '@react-navigation/stack';\n\ntype UserNavigationProp = StackNavigationProp<RootStackParamList, 'Users'>;\n\nconst Users: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<UserNavigationProp>();\n  const [shouldHide, setShouldHide] = React.useState(false);\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  return shouldHide ? null : (\n    <View style={{ flex: 1, backgroundColor: theme.color.background1 }}>\n      <CometChatUsers\n        onItemPress={(user: CometChat.User) => {\n          navigation.navigate('Messages', {\n            user: user,\n          });\n        }}\n        usersRequestBuilder={new CometChat.UsersRequestBuilder()\n          .setLimit(30)\n          .hideBlockedUsers(false)\n          // .setRoles(['@agentic'])\n          .friendsOnly(false)\n          .setStatus('')\n          .setTags([])\n          .sortBy('name')\n          .setUIDs([])}\n      />\n    </View>\n  );\n};\n\nexport default Users;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/config/config.json",
    "content": "{\n  \"builderId\": \"8c0c1c80-b08e-4960-80d4-686396119711\",\n  \"settings\": {\n    \"chatFeatures\": {\n      \"coreMessagingExperience\": {\n        \"typingIndicator\": true,\n        \"threadConversationAndReplies\": true,\n        \"photosSharing\": true,\n        \"videoSharing\": true,\n        \"audioSharing\": true,\n        \"fileSharing\": true,\n        \"editMessage\": true,\n        \"deleteMessage\": true,\n        \"messageDeliveryAndReadReceipts\": true,\n        \"userAndFriendsPresence\": true\n      },\n      \"deeperUserEngagement\": {\n        \"mentions\": true,\n        \"reactions\": true,\n        \"messageTranslation\": true,\n        \"polls\": true,\n        \"collaborativeWhiteboard\": true,\n        \"collaborativeDocument\": true,\n        \"voiceNotes\": true,\n        \"emojis\": true,\n        \"stickers\": true,\n        \"userInfo\": true,\n        \"groupInfo\": true\n      },\n      \"aiUserCopilot\": {\n        \"conversationStarter\": true,\n        \"conversationSummary\": true,\n        \"smartReply\": true\n      },\n      \"groupManagement\": {\n        \"createGroup\": true,\n        \"addMembersToGroups\": true,\n        \"joinLeaveGroup\": true,\n        \"deleteGroup\": true,\n        \"viewGroupMembers\": true\n      },\n      \"moderatorControls\": {\n        \"kickUsers\": true,\n        \"banUsers\": true,\n        \"promoteDemoteMembers\": true\n      },\n      \"privateMessagingWithinGroups\": {\n        \"sendPrivateMessageToGroupMembers\": true\n      }\n    },\n    \"callFeatures\": {\n      \"voiceAndVideoCalling\": {\n        \"oneOnOneVoiceCalling\": true,\n        \"oneOnOneVideoCalling\": true,\n        \"groupVideoConference\": true,\n        \"groupVoiceConference\": true\n      }\n    },\n    \"layout\": {\n      \"withSideBar\": true,\n      \"tabs\": [\n        \"chats\",\n        \"calls\",\n        \"users\",\n        \"groups\"\n      ],\n      \"chatType\": \"both\"\n    },\n    \"style\": {\n          \"theme\": \"system\",\n          \"color\": {\n              \"brandColor\": \"#6852D6\",\n              \"primaryTextLight\": \"#141414\",\n              \"primaryTextDark\": \"#FFFFFF\",\n              \"secondaryTextLight\": \"#727272\",\n              \"secondaryTextDark\": \"#989898\"\n          },\n          \"typography\": {\n              \"font\": \"roboto\",\n              \"size\": \"default\"\n          }\n    },\n    \"noCode\": {\n      \"docked\": false,\n      \"styles\": {\n        \"buttonBackGround\": \"#6952d6\",\n        \"buttonShape\": \"rounded\",\n        \"openIcon\": \"https://nocode-js.cometchat.io/v1/resources/docked_open_icon.svg\",\n        \"closeIcon\": \"https://nocode-js.cometchat.io/v1/resources/docked_close_icon.svg\",\n        \"customJs\": \"\",\n        \"customCss\": \"\"\n      }\n    }\n  },\n  \"name\": \"ttttt\",\n  \"type\": \"low-code\",\n  \"createdAt\": 1749032525,\n  \"updatedAt\": 1749032525,\n  \"expiresAt\": 1812108125\n}"
  },
  {
    "path": "examples/SampleAppExpo/src/config/store.ts",
    "content": "import { create } from 'zustand';\nimport config from './config.json';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\n// TypeScript interfaces for the config structure\ninterface ColorConfig {\n  brandColor: string;\n  primaryTextLight: string;\n  primaryTextDark: string;\n  secondaryTextLight: string;\n  secondaryTextDark: string;\n}\n\ninterface TypographyConfig {\n  font: string;\n  size: string;\n}\n\ninterface StyleConfig {\n  theme: string;\n  color: ColorConfig;\n  typography: TypographyConfig;\n}\n\ninterface NoCodeStyles {\n  buttonBackGround: string;\n  buttonShape: string;\n  openIcon: string;\n  closeIcon: string;\n  customJs: string;\n  customCss: string;\n}\n\ninterface NoCodeConfig {\n  docked: boolean;\n  styles: NoCodeStyles;\n}\n\ninterface LayoutConfig {\n  withSideBar: boolean;\n  tabs: string[];\n  chatType: string;\n  compactMessageComposer: boolean;\n}\n\ninterface CoreMessagingConfig {\n  typingIndicator: boolean;\n  threadConversationAndReplies: boolean;\n  photosSharing: boolean;\n  videoSharing: boolean;\n  audioSharing: boolean;\n  fileSharing: boolean;\n  editMessage: boolean;\n  deleteMessage: boolean;\n  messageDeliveryAndReadReceipts: boolean;\n  userAndFriendsPresence: boolean;\n}\n\ninterface DeeperEngagementConfig {\n  mentions: boolean;\n  reactions: boolean;\n  messageTranslation: boolean;\n  polls: boolean;\n  collaborativeWhiteboard: boolean;\n  collaborativeDocument: boolean;\n  voiceNotes: boolean;\n  emojis: boolean;\n  stickers: boolean;\n  userInfo: boolean;\n  groupInfo: boolean;\n}\n\ninterface AiUserCopilotConfig {\n  conversationStarter: boolean;\n  conversationSummary: boolean;\n  smartReply: boolean;\n}\n\ninterface GroupManagementConfig {\n  createGroup: boolean;\n  addMembersToGroups: boolean;\n  joinLeaveGroup: boolean;\n  deleteGroup: boolean;\n  viewGroupMembers: boolean;\n}\n\ninterface ModeratorControlsConfig {\n  kickUsers: boolean;\n  banUsers: boolean;\n  promoteDemoteMembers: boolean;\n}\n\ninterface PrivateMessagingConfig {\n  sendPrivateMessageToGroupMembers: boolean;\n}\n\ninterface ChatFeaturesConfig {\n  coreMessagingExperience: CoreMessagingConfig;\n  deeperUserEngagement: DeeperEngagementConfig;\n  aiUserCopilot: AiUserCopilotConfig;\n  groupManagement: GroupManagementConfig;\n  moderatorControls: ModeratorControlsConfig;\n  privateMessagingWithinGroups: PrivateMessagingConfig;\n}\n\ninterface VoiceVideoCallingConfig {\n  oneOnOneVoiceCalling: boolean;\n  oneOnOneVideoCalling: boolean;\n  groupVideoConference: boolean;\n  groupVoiceConference: boolean;\n}\n\ninterface CallFeaturesConfig {\n  voiceAndVideoCalling: VoiceVideoCallingConfig;\n}\n\ninterface SettingsConfig {\n  chatFeatures: ChatFeaturesConfig;\n  callFeatures: CallFeaturesConfig;\n  layout: LayoutConfig;\n  style: StyleConfig;\n  noCode: NoCodeConfig;\n}\n\ninterface AppConfig {\n  builderId: string;\n  settings: SettingsConfig;\n  name: string;\n  type: string;\n  createdAt: number;\n  updatedAt: number;\n  expiresAt: number;\n}\n\n// Zustand store interface\ninterface ConfigStore {\n  config: AppConfig;\n  updateConfig: (newConfig: Partial<AppConfig>) => void;\n  resetConfig: () => void;\n}\n\n// Initialize config from storage\nconst initializeConfig = async (): Promise<AppConfig> => {\n  try {\n    const savedConfig = await AsyncStorage.getItem('@app_config');\n    if (savedConfig) {\n      const parsedConfig = JSON.parse(savedConfig);\n      \n      // Handle both old format (direct config) and new API response format (nested in data)\n      let actualConfig: AppConfig;\n      if (parsedConfig.data && parsedConfig.data.settings) {\n        // New API response format - extract data property\n        actualConfig = parsedConfig.data as AppConfig;\n      } else if (parsedConfig.settings && parsedConfig.builderId) {\n        // Old format - direct config\n        actualConfig = parsedConfig as AppConfig;\n      } else {\n        // Invalid structure, use default\n        return config as AppConfig;\n      }\n      \n      // Validate that the config has the expected structure\n      if (actualConfig && actualConfig.settings && actualConfig.builderId) {\n        return actualConfig;\n      }\n    }\n  } catch (error) {\n    console.error('Error loading config from storage:', error);\n  }\n  return config as AppConfig;\n};\n\n// Create the Zustand store\nexport const useConfigStore = create<ConfigStore>((set, _get) => ({\n  // Initialize with the config.json data\n  config: config as AppConfig,\n\n  // Update the entire config\n  updateConfig: (newConfig) =>\n    set((state) => ({\n      config: { ...state.config, ...newConfig },\n    })),\n\n  // Reset to original config\n  resetConfig: () =>\n    set({\n      config: config as AppConfig,\n    }),\n}));\n\n// Initialize the store with saved config if available\ninitializeConfig().then((initialConfig) => {\n  useConfigStore.setState({ config: initialConfig });\n}).catch((error) => {\n  console.error('Error initializing config:', error);\n  // Fallback to default config from JSON file\n  useConfigStore.setState({ config: config as AppConfig });\n});\n\nexport const useConfig = <T>(selector: (state: AppConfig) => T) =>\n  useConfigStore((state) => selector(state.config));"
  },
  {
    "path": "examples/SampleAppExpo/src/declarations.d.ts",
    "content": "declare module '*.png' {\n    const value: any;\n    export default value;\n  }\n  "
  },
  {
    "path": "examples/SampleAppExpo/src/hooks/useGroupMemberStatus.ts",
    "content": "import { useState, useEffect, useRef } from 'react';\n//@ts-ignore\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatUIEventHandler } from '@cometchat/chat-uikit-react-native';\n\n/**\n * Hook that detects whether the logged-in user is no longer a member\n * of the given group (kicked or banned).\n *\n * - On mount: checks current membership via CometChat.getGroup()\n * - Real-time: listens to SDK + UI events for kicked/banned/re-added\n *\n * @param group - The CometChat group object (pass undefined for 1-on-1 chats)\n * @returns `true` when the current user is no longer a member\n */\nexport const useGroupMemberStatus = (group?: CometChat.Group): boolean => {\n  const [isNoLongerMember, setIsNoLongerMember] = useState(false);\n  const loggedInUser = useRef<CometChat.User | null>(null);\n\n  // Fetch logged-in user once\n  useEffect(() => {\n    CometChat.getLoggedinUser().then((u: CometChat.User | null) => {\n      if (u) loggedInUser.current = u;\n    });\n  }, []);\n\n  // Check membership on mount by fetching fresh group data\n  useEffect(() => {\n    if (!group) return;\n\n    CometChat.getGroup(group.getGuid())\n      .then((freshGroup: CometChat.Group) => {\n        if (!freshGroup.getHasJoined()) {\n          setIsNoLongerMember(true);\n        }\n      })\n      .catch(() => {\n        setIsNoLongerMember(true);\n      });\n  }, [group]);\n\n  useEffect(() => {\n    if (!group) return;\n\n    const uiListenerId = 'composer_group_status_' + new Date().getTime();\n    const sdkListenerId = 'composer_sdk_group_status_' + new Date().getTime();\n\n    CometChatUIEventHandler.addGroupListener(uiListenerId, {\n      ccGroupMemberKicked: ({ kickedUser }: any) => {\n        if (kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()) {\n          setIsNoLongerMember(true);\n        }\n      },\n      ccGroupMemberBanned: ({ kickedUser, bannedUser }: any) => {\n        const affected = bannedUser || kickedUser;\n        if (affected?.getUid?.() === loggedInUser.current?.getUid?.()) {\n          setIsNoLongerMember(true);\n        }\n      },\n      ccGroupMemberAdded: ({ usersAdded }: any) => {\n        if (Array.isArray(usersAdded)) {\n          const wasReAdded = usersAdded.some(\n            (u: any) =>\n              u?.getUid?.() === loggedInUser.current?.getUid?.() ||\n              u?.uid === loggedInUser.current?.getUid?.()\n          );\n          if (wasReAdded) {\n            setIsNoLongerMember(false);\n          }\n        }\n      },\n    });\n\n    CometChat.addGroupListener(\n      sdkListenerId,\n      new CometChat.GroupListener({\n        onGroupMemberKicked: (\n          _message: any,\n          kickedUser: any,\n          _kickedBy: any,\n          kickedFrom: any\n        ) => {\n          if (\n            kickedFrom?.getGuid?.() === group.getGuid() &&\n            kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(true);\n          }\n        },\n        onGroupMemberBanned: (\n          _message: any,\n          bannedUser: any,\n          _bannedBy: any,\n          bannedFrom: any\n        ) => {\n          if (\n            bannedFrom?.getGuid?.() === group.getGuid() &&\n            bannedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(true);\n          }\n        },\n        onMemberAddedToGroup: (\n          _message: any,\n          addedUser: any,\n          _addedBy: any,\n          addedTo: any\n        ) => {\n          if (\n            addedTo?.getGuid?.() === group.getGuid() &&\n            addedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(false);\n          }\n        },\n      })\n    );\n\n    return () => {\n      CometChatUIEventHandler.removeGroupListener(uiListenerId);\n      CometChat.removeGroupListener(sdkListenerId);\n    };\n  }, [group]);\n\n  return isNoLongerMember;\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/hooks/useIsKeyboardVisible.ts",
    "content": "import { useState, useEffect } from 'react';\nimport { Keyboard, KeyboardEvent } from 'react-native';\n\n/**\n * Custom hook that returns whether the keyboard is currently visible.\n * Uses React Native's Keyboard API directly, independent of ChatUiKit.\n * @returns {boolean} True if keyboard is visible, false otherwise.\n */\nexport const useIsKeyboardVisible = (): boolean => {\n  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);\n\n  useEffect(() => {\n    const showSubscription = Keyboard.addListener('keyboardDidShow', () => {\n      setIsKeyboardVisible(true);\n    });\n\n    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {\n      setIsKeyboardVisible(false);\n    });\n\n    return () => {\n      showSubscription.remove();\n      hideSubscription.remove();\n    };\n  }, []);\n\n  return isKeyboardVisible;\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/navigation/AuthContext.tsx",
    "content": "import React from 'react';\n\nexport interface AuthContextProps {\n  isLoggedIn: boolean;\n  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const AuthContext = React.createContext<AuthContextProps>({\n  isLoggedIn: false,\n  setIsLoggedIn: () => {},\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/navigation/BottomTabNavigator.tsx",
    "content": "import React from 'react';\nimport {\n  StyleSheet,\n  Platform,\n  View,\n  TouchableWithoutFeedback,\n  Text,\n} from 'react-native';\nimport {\n  createBottomTabNavigator,\n  BottomTabBarButtonProps,\n} from '@react-navigation/bottom-tabs';\nimport {useTheme, Icon, useCometChatTranslation } from '@cometchat/chat-uikit-react-native';\nimport {SCREEN_CONSTANTS} from '../utils/AppConstants';\nimport { useIsKeyboardVisible } from '../hooks/useIsKeyboardVisible';\nimport ChatFill from '../assets/icons/Chatfill';\nimport Chat from '../assets/icons/Chat';\nimport PersonFill from '../assets/icons/PersonFill';\nimport Person from '../assets/icons/Person';\nimport GroupFill from '../assets/icons/GroupFill';\nimport CallFill from '../assets/icons/CallFill';\nimport Call from '../assets/icons/Call';\nimport Group from '../assets/icons/Group';\nimport Conversations from '../components/conversations/screens/Conversations';\nimport Calls from '../components/calls/Calls';\nimport Users from '../components/users/Users';\nimport Groups from '../components/groups/Groups';\nimport {BottomTabParamList} from './types';\nimport { useConfig } from '../config/store';\n\n// Create the tab navigator.\nconst Tab = createBottomTabNavigator<BottomTabParamList>();\n\n// Define a type for icon components that accept color, height, and width props.\ntype IconComponentType = React.ComponentType<{\n  color?: string;\n  height?: number;\n  width?: number;\n}>;\n\n// Update the icons mapping to use the imported image components.\nconst icons: Record<\n  string,\n  {active: IconComponentType; inactive: IconComponentType}\n> = {\n  Chats: {active: ChatFill, inactive: Chat},\n  Users: {active: PersonFill, inactive: Person},\n  Calls: {active: CallFill, inactive: Call},\n  Groups: {active: GroupFill, inactive: Group},\n};\n\nconst CustomTabBarButton = ({children, onPress}: BottomTabBarButtonProps) => (\n  <TouchableWithoutFeedback onPress={onPress}>\n    <View style={styles.tabButton}>{children}</View>\n  </TouchableWithoutFeedback>\n);\n\nconst BottomTabNavigator = () => {\n  const theme = useTheme();\n  const tabs = useConfig(state => state.settings.layout.tabs);\n  const { t } = useCometChatTranslation();\n  // Use the custom hook to track keyboard visibility\n  const isKeyboardVisible = useIsKeyboardVisible();\n\n  // Map tab keys to screen names and components\n  const TAB_COMPONENTS: Record<string, { name: string; component: React.ComponentType<any> }> = {\n    chats: { name: SCREEN_CONSTANTS.CHATS, component: Conversations },\n    calls: { name: SCREEN_CONSTANTS.CALLS, component: Calls },\n    users: { name: SCREEN_CONSTANTS.USERS, component: Users },\n    groups: { name: SCREEN_CONSTANTS.GROUPS, component: Groups },\n  };\n\n  return (\n    <Tab.Navigator\n      initialRouteName=\"Chats\"\n      screenOptions={({route}) => ({\n        headerShown: false,\n        // Hide tab bar when keyboard is visible\n        tabBarStyle: isKeyboardVisible \n          ? { display: 'none' } \n          : styles.tabBar,\n        animation: 'none',\n        tabBarIcon: ({focused}) => {\n          const iconSet = icons[route.name];\n          if (!iconSet) return null;\n\n          const IconComponent = focused ? iconSet.active : iconSet.inactive;\n          const iconColor = focused\n            ? theme.color.primary\n            : theme.color.iconSecondary;\n\n          return (\n            <Icon\n              icon={\n                <IconComponent\n                  color={iconColor as string}\n                  height={24}\n                  width={24}\n                />\n              }\n            />\n          );\n        },\n        tabBarShowLabel: true,\n        tabBarLabel: ({focused}) => \n          focused ? (\n            <View>\n              <Text\n                style={[\n                  styles.tabLabel,\n                  {\n                    color: theme.color.primary,\n                    fontFamily: theme.typography.heading1.bold.fontFamily,\n                  },\n                ]}\n              >\n                {t(route.name.toUpperCase())}\n              </Text>\n            </View>\n       ) : null,\n        tabBarButton: props => <CustomTabBarButton {...props} />,\n        tabBarBackground: () => (\n          <View style={{backgroundColor: theme.color.background1, flex: 1}} />\n        ),\n      })}\n    >\n      {tabs.map(tabKey => {\n        const tab = TAB_COMPONENTS[tabKey.toLowerCase()];\n        return tab ? (\n          <Tab.Screen\n            key={tab.name}\n            name={tab.name as keyof BottomTabParamList}\n            component={tab.component}\n          />\n        ) : null;\n      })}\n    </Tab.Navigator>\n  );\n};\n\nconst styles = StyleSheet.create({\n  tabBar: {\n    height: Platform.OS === 'ios' ? 60 : 70,\n    paddingBottom: Platform.OS === 'ios' ? 0 : 10,\n    paddingTop: 15,\n    borderTopWidth: 0,\n    elevation: 5,\n    shadowColor: '#000',\n    shadowOffset: {width: 0, height: -2},\n    shadowOpacity: 0.1,\n    shadowRadius: 3,\n  },\n  tabLabel: {\n    fontSize: 12,\n    marginBottom: 5,\n  },\n  tabButton: {\n    flex: 1,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n});\n\nexport default BottomTabNavigator;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/navigation/NavigationService.ts",
    "content": "import {createNavigationContainerRef} from '@react-navigation/native';\nimport {RootStackParamList} from './types';\n\nexport const navigationRef = createNavigationContainerRef<RootStackParamList>();\n\ninterface PendingNavigation {\n  name: keyof RootStackParamList;\n  params?: any;\n}\n\nlet pendingNavigation: PendingNavigation | null = null;\n\nexport function navigate<RouteName extends keyof RootStackParamList>(\n  name: RouteName,\n  params?: RootStackParamList[RouteName] extends undefined\n    ? undefined\n    : RootStackParamList[RouteName],\n) {\n  if (navigationRef.isReady()) {\n    // navigationRef.navigate(name as never);\n    navigationRef.navigate(name as any, params as any); \n  } else {\n    // Save the navigation intent for later processing\n    pendingNavigation = {name, params};\n  }\n}\n\nexport function processPendingNavigation() {\n  if (pendingNavigation && navigationRef.isReady()) {\n    const {name, params} = pendingNavigation;\n    navigationRef.navigate(name as any, params as any);\n    pendingNavigation = null;\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/src/navigation/RootStackNavigator.tsx",
    "content": "import React from 'react';\nimport {createNativeStackNavigator} from '@react-navigation/native-stack';\nimport {NavigationContainer, DefaultTheme} from '@react-navigation/native';\nimport BottomTabNavigator from './BottomTabNavigator';\nimport OngoingCallScreen from '../components/conversations/screens/OngoingCallScreen';\nimport {SCREEN_CONSTANTS} from '../utils/AppConstants';\nimport {useTheme} from '@cometchat/chat-uikit-react-native';\nimport {RootStackParamList} from './types';\nimport {navigationRef, processPendingNavigation} from './NavigationService';\nimport SampleUser from '../components/login/SampleUser';\nimport AppCredentials from '../components/login/AppCredentials';\nimport {Platform, StatusBar, useColorScheme} from 'react-native';\nimport {navigateToConversation} from '../utils/helper';\nimport Conversations from '../components/conversations/screens/Conversations';\nimport CreateConversation from '../components/conversations/screens/CreateConversation';\nimport Messages from '../components/conversations/screens/Messages';\nimport ThreadView from '../components/conversations/screens/ThreadView';\nimport UserInfo from '../components/conversations/screens/UserInfo';\nimport AddMember from '../components/conversations/screens/AddMember';\nimport BannedMember from '../components/conversations/screens/BannedMember';\nimport ViewMembers from '../components/conversations/screens/ViewMembers';\nimport GroupInfo from '../components/conversations/screens/GroupInfo';\nimport TransferOwnership from '../components/conversations/screens/TransferOwnership';\nimport Calls from '../components/calls/Calls';\nimport {CallDetails} from '../components/calls/CallDetails';\nimport Users from '../components/users/Users';\nimport Groups from '../components/groups/Groups';\nimport QRScreen from '../components/conversations/screens/qr_screen';\nimport AIAgents from '../components/AIAgent/AIAgents';\nimport SearchMessages from '../components/conversations/screens/SearchMessages';\n\ntype Props = {\n  isLoggedIn: boolean;\n  hasValidAppCredentials: boolean;\n};\n\nconst Stack = createNativeStackNavigator<RootStackParamList>();\n\nconst RootStackNavigator = ({isLoggedIn, hasValidAppCredentials: _hasValidAppCredentials}: Props) => {\n  const theme = useTheme();\n  const NavigationTheme = {\n    ...DefaultTheme,\n    colors: {\n      ...DefaultTheme.colors,\n      background: theme.color.background1 as string,\n    },\n  };\n\n  const isDark = useColorScheme() === 'dark';\n  const backgroundColor = theme.color.background2;\n  const barStyle = isDark ? 'light-content' : 'dark-content';\n  return (\n    <>\n      <StatusBar\n        backgroundColor={backgroundColor}\n        barStyle={barStyle}\n        translucent={false}\n      />\n      <NavigationContainer\n        ref={navigationRef}\n        onReady={() => {\n          processPendingNavigation();\n        }}\n        theme={NavigationTheme}>\n        <Stack.Navigator\n          id={undefined}\n          initialRouteName={\n            isLoggedIn\n              ? SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR\n              : _hasValidAppCredentials\n            ? SCREEN_CONSTANTS.SAMPLE_USER\n            : SCREEN_CONSTANTS.APP_CRED\n          }\n          screenOptions={{\n            gestureEnabled: true,\n            headerShown: false,\n            animation: 'slide_from_right',\n          }}>\n          {/* Auth Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.APP_CRED}\n            component={AppCredentials}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SAMPLE_USER}\n            component={SampleUser}\n          />\n          {/* Tab Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR}\n            component={BottomTabNavigator}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.USERS} component={Users} />\n          <Stack.Screen name={SCREEN_CONSTANTS.GROUPS} component={Groups} />\n          <Stack.Screen name={SCREEN_CONSTANTS.AI_AGENTS} component={AIAgents} />\n\n          {/* Chat Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CONVERSATION}\n            component={Conversations}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CREATE_CONVERSATION}\n            component={CreateConversation}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.MESSAGES} component={Messages} />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.THREAD_VIEW}\n            component={ThreadView}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SEARCH_MESSAGES}\n            component={SearchMessages}\n          />\n\n          {/* Info Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.USER_INFO}\n            component={UserInfo}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.GROUP_INFO}\n            component={GroupInfo}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.ADD_MEMBER}\n            component={AddMember}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.TRANSFER_OWNERSHIP}\n            component={TransferOwnership}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BANNED_MEMBER}\n            component={BannedMember}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.VIEW_MEMBER}\n            component={ViewMembers}\n          />\n\n          {/* Call Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.ONGOING_CALL_SCREEN}\n            component={OngoingCallScreen}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.CALL_LOGS} component={Calls} />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CALL_DETAILS}\n            component={CallDetails}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.QR_SCREEN} component={QRScreen} />\n        </Stack.Navigator>\n      </NavigationContainer>\n    </>\n  );\n};\n\nexport default RootStackNavigator;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/navigation/types.ts",
    "content": "import { NavigatorScreenParams } from '@react-navigation/native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\n\nexport type CallType = 'audio' | 'video';\n\nexport type RootStackParamList = {\n  Login: undefined;\n  BottomTabNavigator: NavigatorScreenParams<BottomTabParamList>;\n  OngoingCallScreen: { sessionId: string; callType?: CallType } | { call: any };\n  AppCredentials: undefined;\n  SampleUser: undefined;\n  Conversation: undefined;\n  CreateConversation: undefined;\n  Messages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    fromMention?: boolean;\n    fromMessagePrivately?: boolean;\n    parentMessageId?: string;\n    messageId?: string;\n    searchKeyword?: string;\n    navigatedFromSearch?: boolean;\n  };\n  SearchMessages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  };\n  AIAgents: undefined;\n  BannedMembers: undefined;\n  UserInfo: {\n    user: CometChat.User;\n  };\n  GroupInfo: {\n    group: CometChat.Group;\n  };\n  ThreadView: {\n    message: CometChat.BaseMessage;\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    highlightMessageId?: string;\n  };\n  AddMember: {\n    group: CometChat.Group;\n  };\n  TransferOwnershipSection: {\n    group: CometChat.Group;\n  };\n  BannedMember: {\n    group: CometChat.Group;\n  };\n  ViewMembers: {\n    group: CometChat.Group;\n  };\n  CallLogs: undefined;\n  CallDetails: {\n    call: any;\n  };\n  Users: undefined;\n  Groups: undefined;\n  QRScreen: undefined;\n};\n\nexport type BottomTabParamList = {\n  Chats: undefined;\n  Calls: undefined;\n  Users: undefined;\n  Groups: undefined;\n};\n"
  },
  {
    "path": "examples/SampleAppExpo/src/utils/ActiveChatContext.tsx",
    "content": "import React, { createContext, useContext, useState } from 'react';\n\ntype ActiveChat = {\n  type: 'user' | 'group';\n  id: string; // userUID or groupID\n} | null;\n\ntype ActiveChatContextType = {\n  activeChat: ActiveChat;\n  setActiveChat: React.Dispatch<React.SetStateAction<ActiveChat>>;\n};\n\nconst ActiveChatContext = createContext<ActiveChatContextType | undefined>(undefined);\n\nexport function ActiveChatProvider({ children }: { children?: React.ReactNode }) {\n  const [activeChat, setActiveChat] = useState<ActiveChat>(null);\n\n  return (\n    <ActiveChatContext.Provider value={{ activeChat, setActiveChat }}>\n      {children ?? null}\n    </ActiveChatContext.Provider>\n  );\n}\n\n// Hook for using in any component\nexport function useActiveChat() {\n  const context = useContext(ActiveChatContext);\n  if (!context) {\n    throw new Error('useActiveChat must be used within an ActiveChatProvider');\n  }\n  return context;\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/src/utils/AppConstants.tsx",
    "content": "export const AppConstants = {\n  fcmProviderId: '',\n  apnsProviderId: '',\n  authKey: '',\n  appId: '',\n  region: '',\n  subscriptionType: 'ALL_USERS',\n  versionNumber: 'V5.3.4',\n  webClientId:\n    '',\n  iosClientId:\n    '',\n};\n\nexport const SCREEN_CONSTANTS = {\n  LOGIN: 'Login',\n  APP_CRED: 'AppCredentials',\n  SAMPLE_USER: 'SampleUser',\n  ONGOING_CALL_SCREEN: 'OngoingCallScreen',\n  BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator',\n  CHATS: 'Chats',\n  CALLS: 'Calls',\n  USERS: 'Users',\n  GROUPS: 'Groups',\n  CONVERSATION: 'Conversation',\n  CREATE_CONVERSATION: 'CreateConversation',\n  MESSAGES: 'Messages',\n  SEARCH_MESSAGES: 'SearchMessages',\n  THREAD_VIEW: 'ThreadView',\n  USER_INFO: 'UserInfo',\n  GROUP_INFO: 'GroupInfo',\n  ADD_MEMBER: 'AddMember',\n  TRANSFER_OWNERSHIP: 'TransferOwnershipSection',\n  BANNED_MEMBER: 'BannedMember',\n  VIEW_MEMBER: 'ViewMembers',\n  CALL_LOGS: 'CallLogs',\n  CALL_DETAILS: 'CallDetails',\n  QR_SCREEN: 'QRScreen',\n  AI_AGENTS: 'AIAgents',\n} as const;\n"
  },
  {
    "path": "examples/SampleAppExpo/src/utils/CommonUtils.ts",
    "content": "export class CommonUtils {\n  static clone<T extends any>(arg: T): T {\n    /*\n    If there are additional properties attached to a function or an array object other than the standard properties, those properties will be ignored\n    Cannot copy private properties (those that start with a \"#\" symbol inside a class block)\n    Functions are copied by reference\n    */\n    if (typeof arg !== 'object' || !arg) {\n      return arg;\n    }\n    let res;\n    if (Array.isArray(arg)) {\n      // arg is an array, there's no hatch to fool the Array.isArray method, so lets create an array\n      res = [];\n      for (const value of arg) {\n        res.push(CommonUtils.clone(value));\n      }\n      return res as T;\n    } else {\n      // arg is an object\n      res = {};\n      const descriptor = Object.getOwnPropertyDescriptors(arg);\n      for (const k of Reflect.ownKeys(descriptor)) {\n        const curDescriptor = descriptor[k as any];\n        if (curDescriptor.hasOwnProperty('value')) {\n          // Property is a data property\n          Object.defineProperty(res, k, {\n            ...curDescriptor,\n            value: CommonUtils.clone(curDescriptor['value']),\n          });\n        } else {\n          // Property is an accessor property\n          Object.defineProperty(res, k, curDescriptor);\n        }\n      }\n      Object.setPrototypeOf(res, Object.getPrototypeOf(arg));\n    }\n    return res as T;\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/src/utils/TooltipMenu.tsx",
    "content": "import { Icon, useTheme } from \"@cometchat/chat-uikit-react-native\";\nimport { JSX, useMemo } from \"react\";\nimport {\n  ColorValue,\n  Dimensions,\n  ImageSourcePropType,\n  Modal,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  TouchableWithoutFeedback,\n  View,\n} from \"react-native\";\n\nconst { width: screenWidth, height: screenHeight } = Dimensions.get(\"window\");\n\ntype CometChatTooltipMenuProps = {\n  visible?: boolean;\n  onDismiss?: () => void;\n  onClose?: () => void;\n  event: {\n    nativeEvent: {\n      pageX: number;\n      pageY: number;\n    };\n  };\n  menuItems: {\n    text: string;\n    onPress: () => void;\n    textColor?: ColorValue;\n    iconColor?: ColorValue;\n    icon?: ImageSourcePropType | JSX.Element;\n  }[];\n};\n\nexport const TooltipMenu = (props: CometChatTooltipMenuProps) => {\n  const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;\n  const theme = useTheme();\n\n  const position = useMemo(() => {\n    let x = event.nativeEvent.pageX;\n    let y = event.nativeEvent.pageY;\n    const position: {\n      left?: number;\n      right?: number;\n      top?: number;\n      bottom?: number;\n    } = {};\n    if (x <= screenWidth / 3) {\n      position.left = x + 10;\n    } else {\n      position.right = 12;\n    }\n\n    if (y <= screenHeight / 2) {\n      position.top = y + 20;\n    } else if (y >= screenHeight / 2) {\n      position.bottom = Math.max(screenHeight - y + 10, 40);\n    }\n    return position;\n  }, [event]);\n\n  return (\n    <Modal\n      presentationStyle='overFullScreen'\n      transparent={true}\n      visible={visible}\n      onRequestClose={onClose}\n      onDismiss={onDismiss}\n      animationType='fade'\n    >\n      <TouchableWithoutFeedback onPress={onClose}>\n        <View style={tooltipStyles.overlay}>\n          <View\n            style={[\n              tooltipStyles.menu,\n              position,\n              {\n                backgroundColor: theme.color.background1,\n                borderWidth: 1,\n                borderColor: theme.color.borderLight,\n                borderRadius: theme.spacing.radius.r2,\n                shadowColor: theme.color.neutral900,\n              },\n            ]}\n          >\n            {menuItems.map((item, i) => {\n              return (\n                <TouchableOpacity\n                  key={i} // Ensure each item has a unique key\n                  onPress={() => {\n                    item.onPress();\n                    onClose();\n                  }}\n                  style={[\n                    {\n                      flexDirection: \"row\",\n                      alignItems: \"center\",\n                      paddingVertical: 10,\n                      paddingHorizontal: 16,\n                      gap: 8,\n                      backgroundColor: theme.color.background1,\n                      minWidth: 160,\n                    },\n                    i === 0\n                      ? {\n                          borderTopLeftRadius: theme.spacing.radius.r2,\n                          borderTopRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                    i === menuItems.length - 1\n                      ? {\n                          borderBottomLeftRadius: theme.spacing.radius.r2,\n                          borderBottomRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                  ]}\n                >\n                  <Icon\n                    color={item.iconColor ?? theme.color.textSecondary}\n                    size={theme.spacing.spacing.s6}\n                    icon={item.icon}\n                  />\n                  <Text\n                    style={[\n                      {\n                        color: item.textColor ?? theme.color.textPrimary,\n                        ...theme.typography.heading4.regular,\n                      },\n                    ]}\n                  >\n                    {item.text}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n      </TouchableWithoutFeedback>\n    </Modal>\n  );\n};\n\nconst tooltipStyles = StyleSheet.create({\n  overlay: {\n    flex: 1,\n    backgroundColor: \"transparent\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  menu: {\n    position: \"absolute\",\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.025,\n    shadowRadius: 4,\n    elevation: 3,\n  },\n  menuItem: {\n    fontSize: 16,\n    paddingVertical: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppExpo/src/utils/helper.ts",
    "content": "import {Platform, PermissionsAndroid} from 'react-native';\nimport messaging from '@react-native-firebase/messaging';\nimport notifee, {AndroidImportance} from '@notifee/react-native';\nimport PushNotificationIOS from '@react-native-community/push-notification-ios';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {navigate} from '../navigation/NavigationService';\nimport Ironman from '../assets/icons/ironman.png';\nimport Captainamerica from '../assets/icons/captainamerica.png';\nimport Wolverine from '../assets/icons/wolverine.png';\nimport Spiderman from '../assets/icons/spiderman.png';\nimport Cyclops from '../assets/icons/cyclops.png';\nimport {registerPushToken} from './PushNotification';\nimport {\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  NavigationContainerRefWithCurrent,\n  StackActions,\n} from '@react-navigation/native';\nimport {RootStackParamList} from '../navigation/types';\nimport {SCREEN_CONSTANTS} from './AppConstants';\nimport dayjs from 'dayjs';\n\ninterface Translations {\n  lastSeen: string;\n  minutesAgo: (minutes: number) => string;\n  hoursAgo: (hours: number) => string;\n}\n\ninterface NotifeeData {\n  receiverType?: 'user' | 'group';\n  conversationId?: string;\n  sender?: string;\n  messageId?: string;\n  parentId?: string;\n  [key: string]: any;\n}\n\n/**\n * Display a local notification (Android) using Notifee.\n * This is triggered when the app is in the foreground.\n */\nexport async function displayLocalNotification(\n  remoteMessage: any,\n  activeChat?: any,\n) {\n  try {\n    if (remoteMessage?.data?.type !== 'chat') {\n      return;\n    }\n    if (\n      activeChat &&\n      ((activeChat.type === 'user' &&\n        String(activeChat.id) === String(remoteMessage?.data?.sender)) ||\n        (activeChat.type === 'group' &&\n          String(activeChat.id) === String(remoteMessage?.data?.receiver)))\n    ) {\n      return;\n    }\n\n    const {title, body, senderAvatar} = remoteMessage.data || {};\n    const skey = remoteMessage.sentTime.toString();\n    const channelId = await notifee.createChannel({\n      id: 'chat-messages',\n      name: 'Chat Messages',\n      vibration: true,\n      importance: AndroidImportance.HIGH,\n    });\n\n    // Extract parent ID for agentic messages\n    let parentId: string | undefined;\n    let messageId: string | undefined;\n    \n    try {\n      if (remoteMessage.data?.message) {\n        const parsedMessage = JSON.parse(remoteMessage.data.message);\n        parentId = parsedMessage.parentId;\n        messageId = parsedMessage.id;\n      }\n      // Fallback to tag if message parsing fails\n      if (!messageId && remoteMessage.data?.tag) {\n        messageId = remoteMessage.data.tag;\n      }\n    } catch (error) {\n      console.log('Error parsing message data:', error);\n      // Use tag as fallback\n      if (remoteMessage.data?.tag) {\n        messageId = remoteMessage.data.tag;\n      }\n    }\n\n    const notificationData = {\n      receiverType: remoteMessage.data?.receiverType,\n      sender: remoteMessage.data?.sender,\n      conversationId: remoteMessage.data?.conversationId,\n      ...(messageId && { messageId }),\n      ...(parentId && { parentId }),\n    };\n\n    await notifee.displayNotification({\n      title: title || 'New Message',\n      body: body || 'You received a new message.',\n      android: {\n        channelId,\n        sortKey: skey,\n        autoCancel: true,\n        smallIcon: 'ic_notification',\n        largeIcon:\n          senderAvatar ||\n          'https://cdn-icons-png.flaticon.com/512/149/149071.png',\n        importance: AndroidImportance.HIGH,\n        pressAction: {\n          id: 'default',\n        },\n      },\n      data: notificationData,\n    });\n  } catch (error) {\n    console.error('displayLocalNotification error:', error);\n  }\n}\n\n/**\n * Request common Android permissions (notifications, camera, etc.)\n * Only needed on Android.\n */\nexport async function requestAndroidPermissions() {\n  if (Platform.OS !== 'android') return;\n\n  try {\n    // Ask for push‑notification permission\n    const authStatus = await messaging().requestPermission();\n    const enabled =\n      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||\n      authStatus === messaging.AuthorizationStatus.PROVISIONAL;\n\n    if (!enabled) {\n      console.warn('Notification permission denied (FCM).');\n    }\n  } catch (error) {\n    console.warn('FCM permission request error:', error);\n  }\n\n  try {\n    await PermissionsAndroid.requestMultiple([\n      PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,\n    ]);\n  } catch (err) {\n    console.warn('Android permissions error:', err);\n  }\n}\n\n/**\n * Retrieve the initial iOS push notification (if the user tapped on one\n * to open the app) and navigate to the correct screen. (iOS only)\n */\nexport async function checkInitialNotificationIOS() {\n  if (Platform.OS !== 'ios') return;\n\n  try {\n    const notification = await PushNotificationIOS.getInitialNotification();\n    if (notification) {\n      const data = notification.getData();\n      if (data && data.type === 'chat') {\n        // Extract parent ID for agentic messages\n        let parentId: string | undefined;\n        try {\n          if (data.message) {\n            const parsedMessage = JSON.parse(data.message);\n            parentId = parsedMessage.parentId;\n          }\n        } catch (error) {\n          console.log('Error parsing iOS message data:', error);\n        }\n\n        if (data.receiverType === 'group') {\n          try {\n            const group = await CometChat.getGroup(data.receiver);\n            const params: any = { group };\n            if (parentId) {\n              params.parentMessageId = parentId;\n            }\n            navigate(SCREEN_CONSTANTS.MESSAGES, params);\n          } catch (error) {\n            console.log('Error fetching group details:', error);\n          }\n        } else if (data.receiverType === 'user') {\n          try {\n            const user = await CometChat.getUser(data.sender);\n            const params: any = { user };\n            if (parentId) {\n              params.parentMessageId = parentId;\n            }\n            navigate(SCREEN_CONSTANTS.MESSAGES, params);\n          } catch (error) {\n            console.log('Error fetching user details:', error);\n          }\n        }\n      }\n    }\n  } catch (error) {\n    console.error('checkInitialNotificationIOS error:', error);\n  }\n}\n\n/**\n * Handle remote notification in iOS. If the user taps on it,\n * navigate accordingly. (Foreground or background scenario)\n */\nexport async function onRemoteNotificationIOS(notification: any) {\n  const isClicked = notification.getData().userInteraction === 1;\n  if (isClicked) {\n    const data = notification.getData();\n    if (data && data.type === 'chat') {\n      // Extract parent ID for agentic messages\n      let parentId: string | undefined;\n      try {\n        if (data.message) {\n          const parsedMessage = JSON.parse(data.message);\n          parentId = parsedMessage.parentId;\n        }\n      } catch (error) {\n        console.log('Error parsing iOS message data:', error);\n      }\n\n      if (data.receiverType === 'group') {\n        try {\n          const group = await CometChat.getGroup(data.receiver);\n          const params: any = { group };\n          if (parentId) {\n            params.parentMessageId = parentId;\n          }\n          navigate(SCREEN_CONSTANTS.MESSAGES, params);\n        } catch (error) {\n          console.log('Error fetching group details:', error);\n        }\n      } else if (data.receiverType === 'user') {\n        try {\n          const user = await CometChat.getUser(data.sender);\n          const params: any = { user };\n          if (parentId) {\n            params.parentMessageId = parentId;\n          }\n          navigate(SCREEN_CONSTANTS.MESSAGES, params);\n        } catch (error) {\n          console.log('Error fetching user details:', error);\n        }\n      }\n    }\n  }\n  // Must call finish to let iOS know we're done processing the notification\n  notification.finish(PushNotificationIOS.FetchResult.NoData);\n}\n\n/**\n * Retrieve and register the FCM token with CometChat (Android only).\n */\nexport async function getAndRegisterFCMToken(\n  user: boolean,\n  currentToken: string,\n  isTokenRegistered: boolean,\n  setIsTokenRegistered: (val: boolean) => void,\n  setCurrentToken: (token: string) => void,\n) {\n  try {\n    await messaging().registerDeviceForRemoteMessages();\n    const token = await messaging().getToken();\n    console.log('FCM Token:', token);\n\n    if (user && !isTokenRegistered) {\n      if (token !== currentToken) {\n        await registerPushToken(token, true, false);\n        setIsTokenRegistered(true);\n        setCurrentToken(token);\n      }\n    }\n  } catch (error) {\n    console.error('Failed to get FCM Token:', error);\n  }\n}\n\n/**\n * Register iOS's APNs (non-VoIP) token with CometChat.\n */\nexport async function handleIosApnsToken(\n  user: boolean,\n  deviceToken: string,\n  currentToken: string,\n  isTokenRegistered: boolean,\n  setCurrentToken: (token: string) => void,\n  setIsTokenRegistered: (val: boolean) => void,\n) {\n  if (user && deviceToken !== currentToken && !isTokenRegistered) {\n    try {\n      await registerPushToken(deviceToken, false, false);\n      console.log('APNs device token registered successfully with CometChat.');\n      setCurrentToken(deviceToken);\n      setIsTokenRegistered(true);\n    } catch (err) {\n      console.error('APNs device token registration failed:', err);\n    }\n  }\n}\n\n/**\n * Register iOS VoIP token with CometChat.\n */\nexport async function handleIosVoipToken(user: boolean, voipToken: string) {\n  if (user) {\n    try {\n      await registerPushToken(voipToken, false, true);\n      console.log('APNs VOIP token registered successfully with CometChat.');\n    } catch (err) {\n      console.error('APNs VOIP token registration failed:', err);\n    }\n  }\n}\n\n/**\n * getLastSeenTime UserInfoSection.\n */\nexport function getLastSeenTime(\n  timestamp: number | null,\n  translations: Translations,\n): string {\n  if (timestamp === null) {\n    return `${translations.lastSeen} Unknown`;\n  }\n\n  // If timestamp is in seconds (length = 10), convert to milliseconds.\n  if (String(timestamp).length === 10) {\n    timestamp *= 1000;\n  }\n\n  const now = new Date();\n  const lastSeen = new Date(timestamp);\n\n  // Calculate the time differences\n  const diffInMillis = now.getTime() - lastSeen.getTime();\n  const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));\n  const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));\n\n  // Check if within last hour\n  if (diffInMinutes === 0) {\n    return `${translations.lastSeen} ${translations.minutesAgo(1)}`;\n  } else if (diffInMinutes < 60) {\n    return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;\n  }\n\n  // Check if within the last 24 hours\n  if (diffInHours < 24) {\n    return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;\n  }\n\n  // Determine if timestamp is within the current year\n  const isSameYear = lastSeen.getFullYear() === now.getFullYear();\n\n  // Options for date formatting\n  const dateOptions: Intl.DateTimeFormatOptions = {\n    day: '2-digit',\n    month: 'short',\n    ...(isSameYear ? {} : { year: 'numeric' }),\n  };\n\n  // Options for time formatting\n  const timeOptions: Intl.DateTimeFormatOptions = {\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: true,\n  };\n\n  const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);\n  const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);\n  if (formattedDate === 'Invalid Date' || formattedTime === 'Invalid Date') {\n    return `Offline`;\n  }\n\n  return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;\n}\n\n/**\n * UNBLOCK\n */\nexport const unblock = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n  setUserObj: React.Dispatch<React.SetStateAction<CometChat.User>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.unblockUsers([uid]);\n    const unBlockedUser = await CometChat.getUser(uid);\n    if (response) {\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, {\n        user: unBlockedUser,\n      });\n      setBlocked(false);\n      setUserObj(unBlockedUser);\n    } else {\n      console.log(\n        `Failed to unblock user with UID ${uid}. Response:`,\n        response,\n      );\n    }\n  } catch (error) {\n    console.error('Error unblocking user:', error);\n  }\n};\n\n/**\n * BLOCK\n */\nexport const blockUser = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.blockUsers([uid]);\n    if (response) {\n      user.setBlockedByMe(true);\n      setBlocked(true);\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, {\n        user,\n      });\n    } else {\n      console.log(`Failed to block user with UID ${uid}. Response:`, response);\n    }\n  } catch (error) {\n    console.error('Error blocking user:', error);\n  }\n};\n\n/**\n * LEAVE GROUP\n */\nexport const leaveGroup = (\n  group: CometChat.Group,\n  navigation: any,\n  pop: number,\n) => {\n  if (group) {\n    const groupID = group.getGuid();\n    CometChat.leaveGroup(groupID).then(\n      () => {\n        let actionMessage: CometChat.Action = new CometChat.Action(\n          groupID,\n          CometChat.MESSAGE_TYPE.TEXT,\n          CometChat.RECEIVER_TYPE.GROUP,\n          CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n        );\n        actionMessage.setMessage(\n          `${CometChatUIKit.loggedInUser!.getName()} has left`,\n        );\n        // Initialize data to prevent crash when SDK accesses getData().metadata during render\n        actionMessage.setData({ metadata: {} });\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          message: actionMessage, //Note: Add Action message after discussion\n          leftUser: CometChatUIKit.loggedInUser,\n          leftGroup: group,\n        });\n        navigation.pop(pop);\n      },\n      error => {\n        console.log('Group leaving failed:', error);\n      },\n    );\n  } else {\n    console.log('Group is not defined');\n  }\n};\n\n/**\n * Sample Users Data\n */\nexport const sampleData = {\n  users: [\n    {uid: 'superhero1', name: 'Iron Man', avatar: Ironman},\n    {uid: 'superhero2', name: 'Captain America', avatar: Captainamerica},\n    {uid: 'superhero3', name: 'Spiderman', avatar: Spiderman},\n    {uid: 'superhero4', name: 'Wolverine', avatar: Wolverine},\n    {uid: 'superhero5', name: 'Cyclops', avatar: Cyclops},\n  ],\n};\n\n/**\n * Navigate to conversation based on notification data.\n */\nexport async function navigateToConversation(\n  navigationRef: NavigationContainerRefWithCurrent<RootStackParamList>,\n  data?: NotifeeData,\n) {\n  if (!data || !navigationRef.current) {\n    return;\n  }\n  \n  try {\n    // Handle group\n    if (data.receiverType === 'group') {\n      const extractedId =\n        typeof data.conversationId === 'string'\n          ? data.conversationId.split('_').slice(1).join('_')\n          : '';\n      const group = await CometChat.getGroup(extractedId);\n\n      // Navigate with parent message ID if available (for agentic conversations)\n      const params: any = {group};\n      if (data.parentId) {\n        params.parentMessageId = data.parentId;\n      }\n\n      navigationRef.current?.dispatch(StackActions.push(SCREEN_CONSTANTS.MESSAGES, params));\n    }\n\n    // Handle user\n    else if (data.receiverType === 'user') {\n      const ccUser = await CometChat.getUser(data.sender);\n\n      // Navigate with parent message ID if available (for agentic conversations)\n      const params: any = {user: ccUser};\n      if (data.parentId) {\n        params.parentMessageId = data.parentId;\n      }\n\n      navigationRef.current?.dispatch(\n        StackActions.push(SCREEN_CONSTANTS.MESSAGES, params),\n      );\n    }\n  } catch (error) {\n    console.log('Error in navigateToConversation:', error);\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppExpo/src/utils/themeTypography.ts",
    "content": "import { Platform } from 'react-native';\n\nexport type TypographyVariant = {\n  regular: { fontFamily: string };\n  medium: { fontFamily: string };\n  bold: { fontFamily: string };\n};\n\nexport type Typography = {\n  fontFamily: string;\n  link: { fontFamily: string };\n} & Record<\n  | \"title\"\n  | \"heading1\"\n  | \"heading2\"\n  | \"heading3\"\n  | \"heading4\"\n  | \"body\"\n  | \"caption1\"\n  | \"caption2\"\n  | \"button\",\n  TypographyVariant\n>;\n\n// Simple font mapping - response name to file names (without extension)\nconst FONT_MAP: Record<string, { regular: string; medium: string; bold: string }> = {\n  'times new roman': {\n    regular: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_regular',\n    medium: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_medium',\n    bold: Platform.OS === 'ios' ? 'TimesNewRomanPS-BoldMT' : 'times_new_roman_bold',\n  },\n  'inter': {\n    regular: Platform.OS === 'ios' ? 'Inter-Regular' : 'inter_regular',\n    medium: Platform.OS === 'ios' ? 'Inter-Medium' : 'inter_medium',\n    bold: Platform.OS === 'ios' ? 'Inter-Bold' : 'inter_bold',\n  },\n  'roboto': {\n    regular: Platform.OS === 'ios' ? 'Roboto-Regular' : 'roboto_regular',\n    medium: Platform.OS === 'ios' ? 'Roboto-Medium' : 'roboto_medium',\n    bold: Platform.OS === 'ios' ? 'Roboto-Bold' : 'roboto_bold',\n  },\n};\n\n/**\n * Creates a complete Typography object for CometChat theme.\n * Normalizes font name, provides fallback, and builds all variants.\n * \n * @param font - The font name from backend (e.g., 'inter', 'roboto', 'times new roman')\n * @returns Complete Typography object with all variants configured\n */\nexport const createTypography = (font: string): Typography => {\n  const types = [\n    \"title\",\n    \"heading1\",\n    \"heading2\",\n    \"heading3\",\n    \"heading4\",\n    \"body\",\n    \"caption1\",\n    \"caption2\",\n    \"button\",\n  ] as const;\n\n  // Normalize font name from backend and provide fallback\n  const fontKey = font ? font.toLowerCase().trim() : '';\n  const fontVariants = FONT_MAP[fontKey] || FONT_MAP['times new roman'];\n\n  // Defensive: ensure all variants exist\n  const baseStyle: TypographyVariant = {\n    regular: { fontFamily: fontVariants.regular },\n    medium: { fontFamily: fontVariants.medium },\n    bold: { fontFamily: fontVariants.bold },\n  };\n\n  const typography: Typography = {\n    fontFamily: fontVariants.regular,\n    link: { fontFamily: fontVariants.regular },\n  } as Typography;\n\n  types.forEach((type) => {\n    typography[type] = { ...baseStyle };\n  });\n\n  return typography;\n};"
  },
  {
    "path": "examples/SampleAppExpo/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {},\n  \"extends\": \"expo/tsconfig.base\"\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/.bundle/config",
    "content": "BUNDLE_PATH: \"vendor/bundle\"\nBUNDLE_FORCE_RUBY_PLATFORM: 1\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: '@react-native',\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/.gitignore",
    "content": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\n**/.xcode.env.local\n\n# Android/IntelliJ\n#\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n*.hprof\n.cxx/\n*.keystore\n!debug.keystore\n.kotlin/\n\n# node.js\n#\nnode_modules/\nnpm-debug.log\nyarn-error.log\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/\n\n**/fastlane/report.xml\n**/fastlane/Preview.html\n**/fastlane/screenshots\n**/fastlane/test_output\n\n# Bundle artifact\n*.jsbundle\n\n# Ruby / CocoaPods\n**/Pods/\n/vendor/bundle/\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# testing\n/coverage\n\n# Yarn\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/.prettierrc.js",
    "content": "module.exports = {\n  arrowParens: 'avoid',\n  singleQuote: true,\n  trailingComma: 'all',\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/.vscode/settings.json",
    "content": "{\n    \"workbench.colorCustomizations\": {\n        \"activityBar.activeBackground\": \"#fbed80\",\n        \"activityBar.background\": \"#fbed80\",\n        \"activityBar.foreground\": \"#15202b\",\n        \"activityBar.inactiveForeground\": \"#15202b99\",\n        \"activityBarBadge.background\": \"#06b9a5\",\n        \"activityBarBadge.foreground\": \"#15202b\",\n        \"commandCenter.border\": \"#15202b99\",\n        \"sash.hoverBorder\": \"#fbed80\",\n        \"statusBar.background\": \"#f9e64f\",\n        \"statusBar.foreground\": \"#15202b\",\n        \"statusBarItem.hoverBackground\": \"#f7df1e\",\n        \"statusBarItem.remoteBackground\": \"#f9e64f\",\n        \"statusBarItem.remoteForeground\": \"#15202b\",\n        \"titleBar.activeBackground\": \"#f9e64f\",\n        \"titleBar.activeForeground\": \"#15202b\",\n        \"titleBar.inactiveBackground\": \"#f9e64f99\",\n        \"titleBar.inactiveForeground\": \"#15202b99\"\n    },\n    \"peacock.color\": \"#f9e64f\"\n}"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/.watchmanconfig",
    "content": "{}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/App.tsx",
    "content": "import './gesture-handler';\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n  Platform,\n  View,\n  PlatformColor,\n  AppState,\n  AppStateStatus,\n} from 'react-native';\nimport {\n  CometChatI18nProvider,\n  CometChatIncomingCall,\n  CometChatTheme,\n  CometChatThemeProvider,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  UIKitSettings,\n} from '@cometchat/chat-uikit-react-native';\n\nimport messaging from '@react-native-firebase/messaging';\nimport { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';\nimport { GestureHandlerRootView } from 'react-native-gesture-handler';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport RootStackNavigator from './src/navigation/RootStackNavigator';\nimport { AppConstants } from './src/utils/AppConstants';\nimport PushNotificationIOS from '@react-native-community/push-notification-ios';\nimport VoipPushNotification from 'react-native-voip-push-notification';\nimport {\n  displayLocalNotification,\n  requestAndroidPermissions,\n  checkInitialNotificationIOS,\n  onRemoteNotificationIOS,\n  getAndRegisterFCMToken,\n  handleIosApnsToken,\n  handleIosVoipToken,\n  navigateToConversation,\n} from './src/utils/helper';\nimport { registerPushToken } from './src/utils/PushNotification';\nimport { voipHandler } from './src/utils/VoipNotificationHandler';\nimport { navigationRef, navigate } from './src/navigation/NavigationService';\nimport notifee, { EventType } from '@notifee/react-native';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { useActiveChat } from './src/utils/ActiveChatContext';\nimport RNCallKeep from 'react-native-callkeep';\nimport { consumePendingAnsweredCall, isPendingStale } from './src/utils/PendingCallManager';\nimport { useConfig } from './src/config/store';\nimport { DeepPartial } from '@cometchat/chat-uikit-react-native/src/shared/helper/types';\nimport { createTypography } from './src/utils/themeTypography';\n\n// Listener ID for registering and removing CometChat listeners.\nconst listenerId = 'app';\n\nconst App = (): React.ReactElement => {\n  const { activeChat } = useActiveChat();\n  const [callReceived, setCallReceived] = useState(false);\n  const incomingCall = useRef<CometChat.Call | CometChat.CustomMessage | null>(\n    null,\n  );\n  const [isInitializing, setIsInitializing] = useState(true);\n  const [isLoggedIn, setIsLoggedIn] = useState(false);\n  const [userLoggedIn, setUserLoggedIn] = useState(false);\n  const [currentToken, setCurrentToken] = useState('');\n  const [isTokenRegistered, setIsTokenRegistered] = useState(false);\n  const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);\n  const [navigationReadyFlag, setNavigationReadyFlag] = useState(false);\n  const styleConfig = useConfig(state => state?.settings?.style);\n\n  const theme : { light:  DeepPartial<CometChatTheme>; dark: DeepPartial<CometChatTheme> } = {\n  light: {\n    color: {\n      primary: styleConfig.color.brandColor,\n      textPrimary: styleConfig.color.primaryTextLight,\n      textSecondary: styleConfig.color.secondaryTextLight,\n    },\n    typography: createTypography(styleConfig.typography.font),\n  },\n  dark: {\n    color: {\n      primary: styleConfig.color.brandColor,\n      textPrimary: styleConfig.color.primaryTextDark,\n      textSecondary: styleConfig.color.secondaryTextDark,\n    },\n    typography: createTypography(styleConfig.typography.font),\n  },\n};\n\n  /**\n   * Initialize CometChat UIKit and configure Google Sign-In.\n   * Retrieves credentials from AsyncStorage and uses fallback constants if needed.\n   */\n  useEffect(() => {\n    async function init() {\n      try {\n        // Retrieve stored app credentials or default to an empty object.\n        const AppData = (await AsyncStorage.getItem('appCredentials')) || '{}';\n        const storedCredentials = JSON.parse(AppData);\n\n        // Determine the final credentials (from AsyncStorage or AppConstants).\n        const finalAppId = storedCredentials.appId || AppConstants.appId;\n        const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;\n        const finalRegion = storedCredentials.region || AppConstants.region;\n\n        // Set hasValidAppCredentials based on whether all values are available.\n        if (finalAppId && finalAuthKey && finalRegion) {\n          setHasValidAppCredentials(true);\n        } else {\n          setHasValidAppCredentials(false);\n        }\n\n        await CometChatUIKit.init({\n          appId: finalAppId,\n          authKey: finalAuthKey,\n          region: finalRegion,\n          subscriptionType: CometChat.AppSettings\n            .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n        });\n\n        // If a user is already logged in, update the state.\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        if (loggedInUser) {\n          setIsLoggedIn(true);\n        } else {\n          // Clear badge on fresh install or when no user is logged in\n          try {\n            if (Platform.OS === 'ios') {\n              PushNotificationIOS.setApplicationIconBadgeNumber(0);\n            } else if (Platform.OS === 'android') {\n              await notifee.cancelAllNotifications();\n            }\n          } catch (error) {\n            console.error('Error :', error);\n          }\n        }\n\n      } catch (error) {\n        console.log('Error during initialization', error);\n      } finally {\n        // Mark initialization as complete.\n        setIsInitializing(false);\n      }\n    }\n    init();\n  }, []);\n\n  // Track when navigation ref is ready (should be ready shortly after first render of navigator)\n  useEffect(() => {\n    const interval = setInterval(() => {\n      if (navigationRef.isReady()) {\n        setNavigationReadyFlag(true);\n        clearInterval(interval);\n      }\n    }, 200);\n    return () => clearInterval(interval);\n  }, []);\n\n  // When user becomes logged in & navigation ready, check for any pending answered call\n  useEffect(() => {\n    async function maybeResumePendingCall() {\n      if (!navigationReadyFlag || !isLoggedIn) return;\n      const pending = await consumePendingAnsweredCall();\n      if (pending && !isPendingStale(pending)) {\n        try {\n          console.log(\n            '[App] Resuming pending answered call',\n            pending.sessionId,\n          );\n          // Attempt acceptance (may already be joined)\n          let acceptedCall: any = null;\n          try {\n            acceptedCall = await CometChat.acceptCall(pending.sessionId);\n          } catch (err: any) {\n            if (err?.code === 'ERR_CALL_USER_ALREADY_JOINED') {\n              acceptedCall = CometChat.getActiveCall();\n            } else {\n              throw err;\n            }\n          }\n          // Close native incoming UI now that we're resuming\n          RNCallKeep.endAllCalls();\n          const active = acceptedCall || CometChat.getActiveCall();\n          const callTypeForNav =\n            (typeof active?.getType === 'function'\n              ? active.getType()\n              : undefined) ??\n            (pending.raw?.callType as any) ??\n            (pending.raw?.type as any);\n\n          voipHandler.msg = active || pending.raw || {};\n          voipHandler.isAnswered = true;\n\n          navigate('OngoingCallScreen', {\n            sessionId: pending.sessionId,\n            callType: callTypeForNav,\n          });\n\n        } catch (e) {\n          console.log('[App] Failed resuming pending call', e);\n        }\n      }\n    }\n    maybeResumePendingCall();\n  }, [navigationReadyFlag, isLoggedIn]);\n\n  /**\n   * Handle incoming call events.\n   * iOS specific --> To disable the incoming call screen when the call is answered through the VOIP)\n   * This effect listens for incoming calls and updates the state accordingly.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'ios') {\n      RNCallKeep.addEventListener('didDisplayIncomingCall', () => {\n        setCallReceived(false);\n        incomingCall.current = null;\n      });\n    }\n  }, []);\n\n  /**\n   * Monitor app state changes to verify the logged-in status and clear notifications.\n   * When the app becomes active, it cancels Android notifications and checks the login status.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android') {\n      // Request required Android permissions for notifications.\n      requestAndroidPermissions();\n    }\n    const handleAppStateChange = async (nextState: AppStateStatus) => {\n      if (nextState === 'active') {\n        // Clear badge only for iOS when app becomes active\n        if (Platform.OS === 'ios') {\n          PushNotificationIOS.setApplicationIconBadgeNumber(0);\n        } else if (Platform.OS === 'android') {\n          // Clear all notifications when app becomes active (clears badge automatically)\n          await notifee.cancelAllNotifications();\n        }\n        try {\n          // Verify if there is a valid logged-in user.\n          const chatUser = await CometChat.getLoggedinUser();\n          setIsLoggedIn(!!chatUser);\n        } catch (error) {\n          console.log('Error verifying CometChat user on resume:', error);\n        }\n      }\n    };\n    const subscription = AppState.addEventListener(\n      'change',\n      handleAppStateChange,\n    );\n    return () => subscription.remove();\n  }, []);\n\n  /**\n   * Attach CometChat login listener to handle login and logout events.\n   * Updates user login status accordingly.\n   */\n  useEffect(() => {\n    CometChat.addLoginListener(\n      listenerId,\n      new CometChat.LoginListener({\n        loginSuccess: () => {\n          setUserLoggedIn(true);\n        },\n        loginFailure: (e: CometChat.CometChatException) => {\n          console.log('LoginListener :: loginFailure', e.message);\n        },\n        logoutSuccess: () => {\n          setUserLoggedIn(false);\n          setIsTokenRegistered(false);\n          // Clear badge on logout\n          if (Platform.OS === 'ios') {\n            PushNotificationIOS.setApplicationIconBadgeNumber(0);\n          } else if (Platform.OS === 'android') {\n            notifee.cancelAllNotifications();\n          }\n        },\n        logoutFailure: (e: CometChat.CometChatException) => {\n          console.log('LoginListener :: logoutFailure', e.message);\n        },\n      }),\n    );\n\n    // Clean up the login listener on component unmount.\n    return () => {\n      CometChat.removeLoginListener(listenerId);\n    };\n  }, []);\n\n  /**\n   * Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.\n   * Also handles UI events for call end.\n   */\n  useEffect(() => {\n    // Listener for call events.\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onIncomingCallReceived: (call: CometChat.Call) => {\n          // Check if there's already an active call\n          try {\n            const activeCall = CometChat.getActiveCall();\n            if (activeCall) {\n              // If there's an active call, reject the incoming call with busy status\n              setTimeout(() => {\n                CometChat.rejectCall(\n                  call.getSessionId(),\n                  CometChat.CALL_STATUS.BUSY,\n                )\n                  .then(() => {\n                    console.log('Incoming call rejected due to active call');\n                  })\n                  .catch(error => {\n                    console.error(\n                      'Error rejecting call with busy status:',\n                      error,\n                    );\n                  });\n              }, 2000);\n            } else {\n              // No active call, proceed with normal incoming call handling\n              // Hide any bottom sheet UI before showing the incoming call screen.\n              CometChatUIEventHandler.emitUIEvent(\n                CometChatUIEvents.ccToggleBottomSheet,\n                {\n                  isBottomSheetVisible: false,\n                },\n              );\n              // Store the incoming call and update state.\n              incomingCall.current = call;\n              setCallReceived(true);\n            }\n          } catch (error) {\n            console.error('Error getting active call:', error);\n            // If error getting active call, proceed with normal handling\n            CometChatUIEventHandler.emitUIEvent(\n              CometChatUIEvents.ccToggleBottomSheet,\n              {\n                isBottomSheetVisible: false,\n              },\n            );\n            incomingCall.current = call;\n            setCallReceived(true);\n          }\n        },\n        onOutgoingCallRejected: () => {\n          // Clear the call state if outgoing call is rejected.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n        onIncomingCallCancelled: () => {\n          // Clear the call state if the incoming call is cancelled.\n          incomingCall.current = null;\n          setCallReceived(false);\n        },\n      }),\n    );\n\n    // Additional listener to handle call end events.\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallEnded: () => {\n        incomingCall.current = null;\n        setCallReceived(false);\n      },\n    });\n\n    // Remove call listeners on cleanup.\n    return () => {\n      CometChatUIEventHandler.removeCallListener(listenerId);\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [userLoggedIn]);\n\n  /**\n   * Android only: Listen for incoming FCM messages while the app is in the foreground.\n   * Displays a local notification when a message is received.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android') {\n      // Subscribe to FCM messages.\n      const unsubscribe = messaging().onMessage(async remoteMessage => {\n        // Handle badge count from push notification\n        const unreadCount = remoteMessage.data?.unreadMessageCount;\n        if (unreadCount !== undefined && unreadCount !== null) {\n          const count = parseInt(unreadCount as string, 10);\n          if (!isNaN(count) && count >= 0) {\n            try {\n              await notifee.setBadgeCount(count);\n            } catch (error) {\n              console.error('Error setting badge:', error);\n            }\n          }\n        } else {\n          console.log('No unreadMessageCount in payload - check dashboard settings');\n        }\n        // Display local notification\n        try {\n          await displayLocalNotification(remoteMessage, activeChat);\n        } catch (error) {\n          console.log('Error displaying local notification:', error);\n        }\n      });\n      return () => unsubscribe();\n    }\n  }, [activeChat]);\n\n  /**\n   * Android only: Listen to Notifee's foreground events to handle notification presses.\n   * Navigates to the corresponding conversation based on the notification data.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android') {\n      const unsubscribeNotifee = notifee.onForegroundEvent(\n        ({ type, detail }) => {\n          try {\n            if (type === EventType.PRESS) {\n              const { notification } = detail;\n              // Cancel the notification after it is pressed.\n              if (notification?.id) {\n                notifee.cancelNotification(notification.id);\n              }\n              // Retrieve notification data and navigate to the corresponding conversation.\n              const data = detail?.notification?.data || {};\n              navigateToConversation(navigationRef, data);\n            }\n          } catch (error) {\n            console.log('Error handling notifee foreground event:', error);\n          }\n        },\n      );\n      return () => unsubscribeNotifee();\n    }\n  }, []);\n\n  /**\n   * Android only: Check if the app was launched from a notification.\n   * Cancels the initial notification and navigates to the conversation if applicable.\n   */\n  useEffect(() => {\n    async function checkAndNavigate() {\n      if (Platform.OS === 'android') {\n        // Get the initial notification if the app was opened via a notification.\n        const initialNotification = await notifee.getInitialNotification();\n        if (initialNotification) {\n          const { notification } = initialNotification;\n          if (notification?.id) {\n            // Cancel the notification.\n            await notifee.cancelNotification(notification.id);\n          }\n          // Navigate using the notification data.\n          const data = notification?.data || {};\n          if (navigationRef.isReady()) {\n            navigateToConversation(navigationRef, data);\n          }\n        }\n      }\n    }\n    checkAndNavigate();\n  }, []);\n\n  /**\n   * Android only: Listen for FCM token refresh events.\n   * When a new token is received and the user is logged in, register it with CometChat.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android') {\n      const unsubscribeOnTokenRefresh = messaging().onTokenRefresh(\n        async newToken => {\n          try {\n            console.log('FCM Token refreshed:', newToken);\n            if (\n              userLoggedIn &&\n              newToken !== currentToken &&\n              !isTokenRegistered\n            ) {\n              await registerPushToken(newToken, true, false);\n              console.log('New token registered with CometChat (FCM).');\n              setCurrentToken(newToken);\n              setIsTokenRegistered(true);\n            }\n          } catch (error) {\n            console.error(\n              'Failed to register new token with CometChat:',\n              error,\n            );\n          }\n        },\n      );\n      return () => unsubscribeOnTokenRefresh();\n    }\n  }, [userLoggedIn, currentToken, isTokenRegistered]);\n\n  /**\n   * Android only: After user logs in, trigger initial FCM token retrieval.\n   * Uses a small delay to ensure that the user login process has completed.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'android' && userLoggedIn && !isTokenRegistered) {\n      const timer = setTimeout(() => {\n        getAndRegisterFCMToken(\n          userLoggedIn,\n          currentToken,\n          isTokenRegistered,\n          setIsTokenRegistered,\n          setCurrentToken,\n        );\n      }, 2000);\n      return () => clearTimeout(timer);\n    }\n  }, [userLoggedIn, isTokenRegistered, currentToken]);\n\n  /**\n   * iOS only: Listen for VoIP token registration events.\n   * Handles the registration of the VoIP token with CometChat.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'ios') {\n      const voipListener = VoipPushNotification.addEventListener(\n        'register',\n        async (voipToken: string) => {\n          try {\n            console.log('VoIP Token:', voipToken);\n            await handleIosVoipToken(userLoggedIn, voipToken);\n          } catch (error) {\n            console.log('Error handling VoIP token:', error);\n          }\n        },\n      );\n      return () => {\n        VoipPushNotification.removeEventListener('register');\n      };\n    }\n  }, [userLoggedIn]);\n\n  /**\n   * iOS only: Listen for push notifications (both background and foreground)\n   * and handle them accordingly.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'ios') {\n      // Check if the app was launched from a push notification.\n      checkInitialNotificationIOS();\n      const onNotification = async (notification: any) => {\n        try {\n          await onRemoteNotificationIOS(notification);\n        } catch (error) {\n          console.log('Error in onRemoteNotificationIOS:', error);\n        }\n      };\n      PushNotificationIOS.addEventListener('notification', onNotification);\n\n      return () => {\n        PushNotificationIOS.removeEventListener('notification');\n      };\n    }\n  }, []);\n\n  /**\n   * Initialize the VoIP handler after the user logs in.\n   * For iOS, initialization is immediate. For Android, a delay is used to ensure login completion.\n   */\n  useEffect(() => {\n    try {\n      if (Platform.OS === 'ios') {\n        voipHandler.initialize();\n      } else if (Platform.OS === 'android' && userLoggedIn) {\n        const timer = setTimeout(() => {\n          voipHandler.initialize();\n        }, 3000);\n        return () => clearTimeout(timer);\n      }\n    } catch (error) {\n      console.log('Error initializing VoIP handler:', error);\n    }\n  }, [userLoggedIn]);\n\n  /**\n   * iOS only: Request push notification permissions and handle APNs token registration.\n   * Also triggers VoIP token registration.\n   */\n  useEffect(() => {\n    if (Platform.OS === 'ios') {\n      // Request iOS push notification permissions.\n      PushNotificationIOS.requestPermissions()\n        .then(data => {\n          console.log('PushNotificationIOS.requestPermissions:', data);\n        })\n        .catch(error => {\n          console.error('PushNotificationIOS.requestPermissions error:', error);\n        });\n\n      // Function to handle APNs token registration.\n      const handleApnsToken = async (deviceToken: string) => {\n        try {\n          console.log('iOS Device (APNs) Token:', deviceToken);\n          // Register for VoIP notifications.\n          VoipPushNotification.registerVoipToken();\n          await handleIosApnsToken(\n            userLoggedIn,\n            deviceToken,\n            currentToken,\n            isTokenRegistered,\n            setCurrentToken,\n            setIsTokenRegistered,\n          );\n        } catch (err) {\n          console.log('Error handling APNs token:', err);\n        }\n      };\n\n      // Listen for the APNs token registration event.\n      PushNotificationIOS.addEventListener('register', handleApnsToken);\n      return () => {\n        PushNotificationIOS.removeEventListener('register');\n      };\n    }\n  }, [userLoggedIn, currentToken, isTokenRegistered]);\n\n  // Show a blank/splash screen while the app is initializing.\n  if (isInitializing) {\n    return (\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: Platform.select({\n            ios: PlatformColor('systemBackgroundColor'),\n            android: PlatformColor('?android:attr/colorBackground'),\n          }),\n        }}\n      />\n    );\n  }\n\n  // Once initialization is complete, render the main app UI.\n  return (\n    <GestureHandlerRootView style={{ flex: 1 }}>\n    <SafeAreaProvider>\n      {/* Render the incoming call UI if the user is logged in and a call is received */}\n      <CometChatThemeProvider theme={theme}>\n        <CometChatI18nProvider>\n            {isLoggedIn && callReceived && incomingCall.current ? (\n              <CometChatIncomingCall\n                call={incomingCall.current}\n                onDecline={() => {\n                  // Handle call decline by clearing the incoming call state.\n                  incomingCall.current = null;\n                  setCallReceived(false);\n                }}\n              />\n            ) : null}\n          <SafeAreaView edges={['top', 'bottom']} style={{ flex: 1 }}>\n            {/* Render the main navigation stack, passing the login status as a prop */}\n            <RootStackNavigator\n              isLoggedIn={isLoggedIn}\n              hasValidAppCredentials={hasValidAppCredentials}\n            />\n          </SafeAreaView>\n        </CometChatI18nProvider>\n      </CometChatThemeProvider>\n    </SafeAreaProvider>\n    </GestureHandlerRootView>\n  );\n};\n\nexport default App;"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/AppErrorBoundary.tsx",
    "content": "import React from 'react';\nimport { View, Text, StyleSheet, Appearance, Button } from 'react-native';\n\ninterface State {\n  hasError: boolean;\n  error: Error | null;\n  colorScheme: 'light' | 'dark' | null;\n}\n\ninterface Props {\n  children: React.ReactNode;\n}\n\nclass AppErrorBoundary extends React.Component<Props, State> {\n  constructor(props: Props) {\n    super(props);\n    const colorScheme = Appearance.getColorScheme() ?? null;\n    this.state = { hasError: false, error: null, colorScheme };\n  }\n\n  static getDerivedStateFromError(error: Error) {\n    // Update state so the next render shows the fallback UI.\n    return { hasError: true, error };\n  }\n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    // Log error to an error reporting service here if needed.\n    console.log('ErrorBoundary caught an error', error, errorInfo);\n  }\n\n  componentDidMount() {\n    // Listen for changes in color scheme.\n    Appearance.addChangeListener(({ colorScheme }) => {\n      this.setState({ colorScheme: colorScheme ?? null });\n    });\n  }\n\n  // Reset error state to allow retrying\n  handleRetry = () => {\n    this.setState({ hasError: false, error: null });\n  };\n\n  render() {\n    if (this.state.hasError) {\n      const styles = createStyles(this.state.colorScheme);\n      return (\n        <View style={styles.container}>\n          <View style={styles.card}>\n            <Text style={styles.title}>Something went wrong</Text>\n             {/* Uncomment the next line to show the error message */}\n            {/* <Text style={styles.errorText}>\n              {this.state.error ? this.state.error.toString() : 'Unknown error'}\n            </Text> */}\n            <View style={styles.buttonContainer}>\n              <Button title=\"Retry\" onPress={this.handleRetry} color={this.state.colorScheme === 'dark' ? \"#bbbbbb\" : \"#333333\"} />\n            </View>\n          </View>\n        </View>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst createStyles = (colorScheme: 'light' | 'dark' | null) => {\n  const isDark = colorScheme === 'dark';\n  return StyleSheet.create({\n    container: {\n      flex: 1,\n      backgroundColor: isDark ? '#121212' : '#f2f2f2',\n      justifyContent: 'center',\n      alignItems: 'center',\n      padding: 16,\n    },\n    card: {\n      backgroundColor: isDark ? '#1e1e1e' : '#ffffff',\n      padding: 24,\n      borderRadius: 12,\n      alignItems: 'center',\n      shadowColor: isDark ? '#000000' : '#aaa',\n      shadowOffset: { width: 0, height: 2 },\n      shadowOpacity: 0.3,\n      shadowRadius: 4,\n      elevation: 5,\n      width: '100%',\n      maxWidth: 400,\n    },\n    title: {\n      fontSize: 20,\n      fontWeight: '700',\n      marginBottom: 12,\n      color: isDark ? '#ffffff' : '#333333',\n    },\n    errorText: {\n      fontSize: 16,\n      textAlign: 'center',\n      color: isDark ? '#cccccc' : '#666666',\n      marginBottom: 20,\n    },\n    buttonContainer: {\n      width: '100%',\n    },\n  });\n};\n\nexport default AppErrorBoundary;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/Gemfile",
    "content": "source 'https://rubygems.org'\n\n# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version\nruby \">= 2.6.10\"\n\n# Exclude problematic versions of cocoapods and activesupport that causes build failures.\ngem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'\ngem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'\ngem 'xcodeproj', '< 1.26.0'\ngem 'concurrent-ruby', '< 1.3.4'\n\n# Ruby 3.4.0 has removed some libraries from the standard library.\ngem 'bigdecimal'\ngem 'logger'\ngem 'benchmark'\ngem 'mutex_m'\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/README.md",
    "content": "\n<p align=\"center\">\n  <img alt=\"CometChat\" src=\"https://assets.cometchat.io/website/images/logos/banner.png\">\n</p>\n\n# React Native Sample App with Push Notifications by CometChat\n\nThis is a reference application showcasing the integration of [CometChat's React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) in a React Native project. It demonstrates how to implement real-time messaging and voice/video calling features with ease.\n\n<div style=\"display: flex; align-items: center; justify-content: center\">\n   <img src=\"../../screenshots/overview_cometchat_screens.png\" />\n</div>\n\n\n## Prerequisites\n\nSign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_\n\n- **Node.js** 18 or higher\n- **React Native** Version 0.77 or later (up to the latest version) \n\n**iOS**\n- XCode\n- Pod (CocoaPods) for iOS\n- An iOS device or emulator with iOS 12.0 or above.\n- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.\n\n**Android**\n- Android Studio\n- Android device or emulator with Android version 5.0 or above.\n\n\n## Installation\n\n1. Clone the repository:\n   ```sh\n   git clone https://github.com/cometchat/cometchat-uikit-react-native.git\n   ```\n\n1. Change into the specific app's directory (e.g., SampleAppWithPushNotifications).\n   ```sh\n     cd examples/SampleAppWithPushNotifications\n   ```\n   \n1. Run `npm install` to install the dependencies.\n\n1. `[Optional]` Configure CometChat credentials:\n    - Open the `AppConstants.tsx` file located at `examples/SampleApp/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:\n      ```ts\n      export const AppConstants = {\n          appId: 'YOUR_APP_ID',\n          authKey: 'YOUR_AUTH_KEY',\n          region: 'REGION',\n          //other properties\n      }\n      ```\n\n1. Push Notification Setup guide\n   - APNs\n      - Follow our [APNs integration](https://www.cometchat.com/docs/notifications/push-integration#add-apns-credentials).\n   \n   - FCM\n      - Go to the [Firebase Console](https://console.firebase.google.com/) and create a project.\n      - Add your Android app to the Firebase project and download the `google-services.json` file.\n      - Place the `google-services.json` file in the `SampleAppWithPushNotification/android/app` directory of your project.\n\n   - CometChat Push Notification \n      - Go to the [Notification Documentation](https://www.cometchat.com/docs/notifications/push-integration) and follow integration steps.\n\n1. Update the `fcmProviderId` & `apnProviderId` from the step 6 in\n      ```ts\n      export const AppConstants = {\n         fcmProviderId: '',\n         apnProviderId: '',\n      }\n      ```\n\n\n1. For iOS, install dependencies after navigating to ios:\n   ```sh\n    cd ios\n    pod install\n   ```\n\n1. Run the app on a device or emulator from the repo root.\n   ```sh\n    npm start\n    npm run android\n    npm run ios\n   ```\n\n\n## Help and Support\n\nFor issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/)."
  },
  {
    "path": "examples/SampleAppWithPushNotifications/__tests__/App.test.tsx",
    "content": "/**\n * @format\n */\n\nimport React from 'react';\nimport ReactTestRenderer from 'react-test-renderer';\nimport App from '../App';\n\ntest('renders correctly', async () => {\n  await ReactTestRenderer.act(() => {\n    ReactTestRenderer.create(<App />);\n  });\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/build.gradle",
    "content": "apply plugin: \"com.android.application\"\napply plugin: \"org.jetbrains.kotlin.android\"\napply plugin: \"com.facebook.react\"\napply plugin: 'com.google.gms.google-services' \n\n/**\n * This is the configuration block to customize your React Native Android app.\n * By default you don't need to apply any configuration, just uncomment the lines you need.\n */\nreact {\n    /* Folders */\n    //   The root of your project, i.e. where \"package.json\" lives. Default is '../..'\n    // root = file(\"../../\")\n    //   The folder where the react-native NPM package is. Default is ../../node_modules/react-native\n    // reactNativeDir = file(\"../../node_modules/react-native\")\n    //   The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen\n    // codegenDir = file(\"../../node_modules/@react-native/codegen\")\n    //   The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js\n    // cliFile = file(\"../../node_modules/react-native/cli.js\")\n\n    /* Variants */\n    //   The list of variants to that are debuggable. For those we're going to\n    //   skip the bundling of the JS bundle and the assets. By default is just 'debug'.\n    //   If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.\n    // debuggableVariants = [\"liteDebug\", \"prodDebug\"]\n\n    /* Bundling */\n    //   A list containing the node command and its flags. Default is just 'node'.\n    // nodeExecutableAndArgs = [\"node\"]\n    //\n    //   The command to run when bundling. By default is 'bundle'\n    // bundleCommand = \"ram-bundle\"\n    //\n    //   The path to the CLI configuration file. Default is empty.\n    // bundleConfig = file(../rn-cli.config.js)\n    //\n    //   The name of the generated asset file containing your JS bundle\n    // bundleAssetName = \"MyApplication.android.bundle\"\n    //\n    //   The entry file for bundle generation. Default is 'index.android.js' or 'index.js'\n    // entryFile = file(\"../js/MyApplication.android.js\")\n    //\n    //   A list of extra flags to pass to the 'bundle' commands.\n    //   See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle\n    // extraPackagerArgs = []\n\n    /* Hermes Commands */\n    //   The hermes compiler command to run. By default it is 'hermesc'\n    // hermesCommand = \"$rootDir/my-custom-hermesc/bin/hermesc\"\n    //\n    //   The list of flags to pass to the Hermes compiler. By default is \"-O\", \"-output-source-map\"\n    // hermesFlags = [\"-O\", \"-output-source-map\"]\n\n    /* Autolinking */\n    autolinkLibrariesWithApp()\n}\n\n/**\n * Set this to true to Run Proguard on Release builds to minify the Java bytecode.\n */\ndef enableProguardInReleaseBuilds = false\n\n/**\n * The preferred build flavor of JavaScriptCore (JSC)\n *\n * For example, to use the international variant, you can use:\n * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`\n *\n * The international variant includes ICU i18n library and necessary data\n * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that\n * give correct results when using with locales other than en-US. Note that\n * this variant is about 6MiB larger per architecture than default.\n */\ndef jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'\n\nandroid {\n    ndkVersion rootProject.ext.ndkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n    compileSdk rootProject.ext.compileSdkVersion\n\n    namespace \"com.cometchat.sampleapp.reactnative.android\"\n    defaultConfig {\n        applicationId \"com.cometchat.sampleapp.reactnative.android\"\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"5.3.4\"\n    }\n    signingConfigs {\n        debug {\n            storeFile file('debug.keystore')\n            storePassword 'android'\n            keyAlias 'androiddebugkey'\n            keyPassword 'android'\n        }\n    }\n    buildTypes {\n        debug {\n            signingConfig signingConfigs.debug\n        }\n        release {\n            // Caution! In production, you need to generate your own keystore file.\n            // see https://reactnative.dev/docs/signed-apk-android.\n            signingConfig signingConfigs.debug\n            minifyEnabled enableProguardInReleaseBuilds\n            proguardFiles getDefaultProguardFile(\"proguard-android.txt\"), \"proguard-rules.pro\"\n        }\n    }\n}\n\ndependencies {\n    // The version of react-native is set by the React Native Gradle Plugin\n    implementation(\"com.facebook.react:react-android\")\n\n    if (hermesEnabled.toBoolean()) {\n        implementation(\"com.facebook.react:hermes-android\")\n    } else {\n        implementation jscFlavor\n    }\n\n    implementation 'com.google.firebase:firebase-messaging:21.7.1' \n    implementation project(':react-native-callkeep')\n    implementation 'com.facebook.fresco:animated-gif:3.6.0' //gif android\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_number\": \"950141434859\",\n    \"project_id\": \"cometchatteammessanger\",\n    \"storage_bucket\": \"cometchatteammessanger.firebasestorage.app\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:709cc32dfb5c6494ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.cometchat.sampleapp.flutter.android\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-2hg1r6ohg1aagi8771dckfs61j0lb8b7.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.cometchat.sampleapp.flutter.android\",\n            \"certificate_hash\": \"e84a8177acee26b8fa3e8800109b846f9b4bc5d1\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:1bf2a2a876ac5181ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.cometchat.sampleapp.java\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-qmol4a74rulcqm3kslk7e7attisrg1o8.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.cometchat.sampleapp.java\",\n            \"certificate_hash\": \"e84a8177acee26b8fa3e8800109b846f9b4bc5d1\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:4e05baf5d9f0dfa9ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.cometchat.sampleapp.reactnative.android\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-m10vsv0jv1nbdu29klkf2vd1rfok7i3p.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.cometchat.sampleapp.reactnative.android\",\n            \"certificate_hash\": \"e84a8177acee26b8fa3e8800109b846f9b4bc5d1\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:ad90b1a3c44c9690ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.example.sample_app\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-082r7la3902v8l4627r0fq2gfqcek8t2.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.example.sample_app\",\n            \"certificate_hash\": \"b0b5b1feebbef0e33e48df234c454e403231f9fa\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-hdt6kq0ptr1cmbvl0mdaf8eo1dniffd9.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.example.sample_app\",\n            \"certificate_hash\": \"b8035954d4c604ffe74ec6e00a6eb6a6c216afa5\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    }\n  ],\n  \"configuration_version\": \"1\"\n}"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\">\n<!-- package='com.cometchat.sampleapp.reactnative.android'> -->\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\"/>  \n    <uses-permission android:name=\"android.permission.BIND_TELECOM_CONNECTION_SERVICE\"/>\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.CALL_PHONE\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n\n    <application\n      android:name=\".MainApplication\"\n      android:label=\"@string/app_name\"\n      android:icon=\"@mipmap/ic_launcher\"\n      android:roundIcon=\"@mipmap/ic_launcher_round\"\n      android:allowBackup=\"false\"\n      android:theme=\"@style/AppTheme\"\n      android:usesCleartextTraffic=\"${usesCleartextTraffic}\"\n      android:supportsRtl=\"true\">\n      \n        <meta-data\n            android:name=\"com.google.firebase.messaging.default_notification_icon\"\n            android:resource=\"@drawable/ic_notification\" />\n            <!-- android:resource=\"@android:drawable/ic_dialog_info\" /> -->\n        <meta-data\n            android:name=\"com.google.firebase.messaging.default_notification_color\"\n            android:resource=\"@color/notification_color\"\n            tools:replace='android:resource' />\n            \n      <activity\n        android:name=\".MainActivity\"\n        android:label=\"@string/app_name\"\n        android:configChanges=\"keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|fontScale\"\n        android:launchMode=\"singleTask\"\n        android:windowSoftInputMode=\"adjustResize\"\n        android:exported=\"true\">\n        <intent-filter>\n            <action android:name=\"android.intent.action.MAIN\" />\n            <category android:name=\"android.intent.category.LAUNCHER\" />\n        </intent-filter>\n      </activity>\n<service android:name=\"io.wazo.callkeep.RNCallKeepBackgroundMessagingService\" />\n      <service android:name=\"io.wazo.callkeep.VoiceConnectionService\"\n        android:label=\"Wazo\"\n        android:permission=\"android.permission.BIND_TELECOM_CONNECTION_SERVICE\"\n        android:foregroundServiceType=\"camera|microphone\"\n        android:exported=\"true\"\n    >\n        \n        <intent-filter>\n            <action android:name=\"android.telecom.ConnectionService\" />\n        </intent-filter>\n    </service>\n\n    </application>\n</manifest>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/java/com/sampleappwithpushnotifications/MainActivity.kt",
    "content": "package com.cometchat.sampleapp.reactnative.android\nimport android.os.Bundle\n\nimport com.facebook.react.ReactActivity\nimport com.facebook.react.ReactActivityDelegate\nimport com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled\nimport com.facebook.react.defaults.DefaultReactActivityDelegate\nimport io.wazo.callkeep.RNCallKeepModule \n\nclass MainActivity : ReactActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n    // REQUIRED for react-native-screens\n    super.onCreate(null)\n  }\n\n\n  /**\n   * Returns the name of the main component registered from JavaScript. This is used to schedule\n   * rendering of the component.\n   */\n  override fun getMainComponentName(): String = \"sampleapp\"\n\n  /**\n   * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]\n   * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]\n   */\n  override fun createReactActivityDelegate(): ReactActivityDelegate =\n      DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)\n\n  override fun onRequestPermissionsResult(\n      requestCode: Int,\n      permissions: Array<String>,\n      grantResults: IntArray\n  ) {\n      super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n      if (requestCode == RNCallKeepModule.REQUEST_READ_PHONE_STATE) {\n          RNCallKeepModule.onRequestPermissionsResult(requestCode, permissions, grantResults)\n      }\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/java/com/sampleappwithpushnotifications/MainApplication.kt",
    "content": "package com.cometchat.sampleapp.reactnative.android\n\nimport android.app.Application\nimport com.facebook.react.PackageList\nimport com.facebook.react.ReactApplication\nimport com.facebook.react.ReactHost\nimport com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative\nimport com.facebook.react.ReactNativeHost\nimport com.facebook.react.ReactPackage\nimport com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost\nimport com.facebook.react.defaults.DefaultReactNativeHost\nimport io.wazo.callkeep.RNCallKeepPackage\n\nclass MainApplication : Application(), ReactApplication {\n\n  override val reactNativeHost: ReactNativeHost =\n      object : DefaultReactNativeHost(this) {\n        override fun getPackages(): List<ReactPackage> =\n            PackageList(this).packages.apply {\n              add(RNCallKeepPackage()) \n              // Packages that cannot be autolinked yet can be added manually here, for example:\n              // add(MyReactNativePackage())\n            }\n\n        override fun getJSMainModuleName(): String = \"index\"\n\n        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG\n\n        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED\n        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED\n      }\n\n  override val reactHost: ReactHost\n    get() = getDefaultReactHost(applicationContext, reactNativeHost)\n\n  override fun onCreate() {\n    super.onCreate()\n    loadReactNative(this)\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/res/drawable/rn_edit_text_material.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<inset xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:insetLeft=\"@dimen/abc_edit_text_inset_horizontal_material\"\n       android:insetRight=\"@dimen/abc_edit_text_inset_horizontal_material\"\n       android:insetTop=\"@dimen/abc_edit_text_inset_top_material\"\n       android:insetBottom=\"@dimen/abc_edit_text_inset_bottom_material\"\n       >\n\n    <selector>\n        <!--\n          This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).\n          The item below with state_pressed=\"false\" and state_focused=\"false\" causes a NullPointerException.\n          NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'\n\n          <item android:state_pressed=\"false\" android:state_focused=\"false\" android:drawable=\"@drawable/abc_textfield_default_mtrl_alpha\"/>\n\n          For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.\n        -->\n        <item android:state_enabled=\"false\" android:drawable=\"@drawable/abc_textfield_default_mtrl_alpha\"/>\n        <item android:drawable=\"@drawable/abc_textfield_activated_mtrl_alpha\"/>\n    </selector>\n\n</inset>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/res/values/colors.xml",
    "content": "<resources>\n    <!-- Other color definitions -->\n    <color name=\"notification_color\">#FFFFFF</color> <!-- Replace with your desired color -->\n</resources>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">sampleapp</string>\n</resources>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"android:editTextBackground\">@drawable/rn_edit_text_material</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/build.gradle",
    "content": "buildscript {\n    ext {\n        buildToolsVersion = \"36.0.0\"\n        minSdkVersion = 24\n        compileSdkVersion = 36\n        targetSdkVersion = 36\n        ndkVersion = \"27.1.12297006\"\n        kotlinVersion = \"2.1.20\"\n        version = \"V5.3.4\"\n    }\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath(\"com.android.tools.build:gradle\")\n        classpath(\"com.facebook.react:react-native-gradle-plugin\")\n        classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin\")\n        classpath 'com.google.gms:google-services:4.4.2'\n    }\n}\n\napply plugin: \"com.facebook.react.rootproject\"\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m\norg.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n\n# Use this property to specify which architecture you want to build.\n# You can also override it from the CLI using\n# ./gradlew <task> -PreactNativeArchitectures=x86_64\nreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64\n\n# Use this property to enable support to the new architecture.\n# This will allow you to use TurboModules and the Fabric render in\n# your application. You should enable this flag either if you want\n# to write custom TurboModules/Fabric components OR use libraries that\n# are providing them.\nnewArchEnabled=true\n\n# Use this property to enable or disable the Hermes JS engine.\n# If set to false, you will be using JSC instead.\nhermesEnabled=true\n\n# Use this property to enable edge-to-edge display support.\n# This allows your app to draw behind system bars for an immersive UI.\n# Note: Only works with ReactActivity and should not be used with custom Activity.\nedgeToEdgeEnabled=false\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/gradlew.bat",
    "content": "@REM Copyright (c) Meta Platforms, Inc. and affiliates.\n@REM\n@REM This source code is licensed under the MIT license found in the\n@REM LICENSE file in the root directory of this source tree.\n\n@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/android/settings.gradle",
    "content": "pluginManagement { includeBuild(\"../node_modules/@react-native/gradle-plugin\") }\nplugins { id(\"com.facebook.react.settings\") }\nextensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }\nrootProject.name = 'SampleAppWithPushNotifications'\ninclude ':app'\nincludeBuild('../node_modules/@react-native/gradle-plugin')\ninclude ':react-native-callkeep'\nproject(':react-native-callkeep').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-callkeep/android')\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/app.json",
    "content": "{\n  \"name\": \"sampleapp\",\n  \"displayName\": \"sampleapp\"\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/babel.config.js",
    "content": "module.exports = {\n  presets: ['module:@react-native/babel-preset'],\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/gesture-handler.js",
    "content": ""
  },
  {
    "path": "examples/SampleAppWithPushNotifications/gesture-handler.native.js",
    "content": "// Only import react-native-gesture-handler on native platforms\nimport 'react-native-gesture-handler';"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_number\": \"950141434859\",\n    \"project_id\": \"cometchatteammessanger\",\n    \"storage_bucket\": \"cometchatteammessanger.firebasestorage.app\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:709cc32dfb5c6494ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.cometchat.sampleapp.flutter.android\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-2hg1r6ohg1aagi8771dckfs61j0lb8b7.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.cometchat.sampleapp.flutter.android\",\n            \"certificate_hash\": \"e84a8177acee26b8fa3e8800109b846f9b4bc5d1\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:1bf2a2a876ac5181ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.cometchat.sampleapp.java\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-qmol4a74rulcqm3kslk7e7attisrg1o8.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.cometchat.sampleapp.java\",\n            \"certificate_hash\": \"e84a8177acee26b8fa3e8800109b846f9b4bc5d1\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:4e05baf5d9f0dfa9ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.cometchat.sampleapp.reactnative.android\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-m10vsv0jv1nbdu29klkf2vd1rfok7i3p.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.cometchat.sampleapp.reactnative.android\",\n            \"certificate_hash\": \"e84a8177acee26b8fa3e8800109b846f9b4bc5d1\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:950141434859:android:ad90b1a3c44c9690ee77ae\",\n        \"android_client_info\": {\n          \"package_name\": \"com.example.sample_app\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"950141434859-082r7la3902v8l4627r0fq2gfqcek8t2.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.example.sample_app\",\n            \"certificate_hash\": \"b0b5b1feebbef0e33e48df234c454e403231f9fa\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-hdt6kq0ptr1cmbvl0mdaf8eo1dniffd9.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.example.sample_app\",\n            \"certificate_hash\": \"b8035954d4c604ffe74ec6e00a6eb6a6c216afa5\"\n          }\n        },\n        {\n          \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyA3FyreEn5kuKehB4c7huXA8lkbWWXxd0c\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"950141434859-aaanfrsbgqd9noso8hkivsd8kcv1j5ac.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"950141434859-3ji1m4h0scsdr6li4bvm6v4chqpv0t41.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.example.sampleApp\"\n              }\n            }\n          ]\n        }\n      }\n    }\n  ],\n  \"configuration_version\": \"1\"\n}"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/index.js",
    "content": "import {AppRegistry, Platform} from 'react-native';\nimport messaging from '@react-native-firebase/messaging';\nimport App from './App';\nimport {name as appName} from './app.json';\nimport {voipHandler} from './src/utils/VoipNotificationHandler';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {navigationRef} from './src/navigation/NavigationService';\nimport {displayLocalNotification} from './src/utils/helper';\nimport notifee, {EventType} from '@notifee/react-native';\nimport {StackActions} from '@react-navigation/native';\nimport AppErrorBoundary from './AppErrorBoundary';\nimport {ActiveChatProvider} from './src/utils/ActiveChatContext';\n\nif (global?.ErrorUtils) {\n  const defaultHandler = global.ErrorUtils.getGlobalHandler();\n\n  function globalErrorHandler(error, isFatal) {\n    console.log(\n      '[GlobalErrorHandler]:',\n      isFatal ? 'Fatal:' : 'Non-Fatal:',\n      error,\n    );\n    defaultHandler?.(error, isFatal);\n  }\n\n  global.ErrorUtils.setGlobalHandler(globalErrorHandler);\n}\n\nif (typeof process === 'object' && process.on) {\n  process.on('unhandledRejection', (reason, promise) => {\n    console.log('[Unhandled Promise Rejection]:', reason);\n  });\n}\n\nconst Root = () => (\n  <AppErrorBoundary>\n    <ActiveChatProvider>\n      <App />\n    </ActiveChatProvider>\n  </AppErrorBoundary>\n);\n\n// Run Notifee background event handler only on Android\nif (Platform.OS === 'android') {\n  notifee.onBackgroundEvent(async ({type, detail}) => {\n    try {\n      if (type === EventType.PRESS) {\n        const {notification} = detail;\n        if (notification?.id) {\n          await notifee.cancelNotification(notification.id);\n        }\n        const data = detail?.notification?.data || {};\n\n        if (data.receiverType === 'group') {\n          const extractedId =\n            typeof data.conversationId === 'string'\n              ? data.conversationId.split('_').slice(1).join('_')\n              : '';\n          CometChat.getGroup(extractedId).then(\n            group => {\n              navigationRef.current?.dispatch(\n                StackActions.push('Messages', {\n                  group,\n                  parentMessageId: data.parentId,\n                }),\n              );\n            },\n            error => console.log('Error fetching group details:', error),\n          );\n        } else if (data.receiverType === 'user') {\n          CometChat.getUser(data.sender).then(\n            ccUser => {\n              navigationRef.current?.dispatch(\n                StackActions.push('Messages', {\n                  user: ccUser,\n                  parentMessageId: data.parentId,\n                }),\n              );\n            },\n            error => console.log('Error fetching user details:', error),\n          );\n        }\n      }\n    } catch (error) {\n      console.log('Error handling notifee background event:', error);\n    }\n  });\n}\n\n// This runs for background/killed states on Android.\nif (Platform.OS === 'android') {\n  messaging().setBackgroundMessageHandler(async remoteMessage => {\n    try {\n      const data = remoteMessage.data || {};\n      if (data.type === 'call') {\n        await voipHandler.initialize();\n        switch (data.callAction) {\n          case 'initiated':\n            voipHandler.msg = data;\n            await voipHandler.displayCallAndroid();\n            break;\n          case 'ended':\n            CometChat.clearActiveCall();\n            await voipHandler.endCall({callUUID: voipHandler.callerId});\n            break;\n          case 'unanswered':\n            CometChat.clearActiveCall();\n            if (voipHandler?.callerId) {\n              voipHandler.removeCallDialerWithUUID(voipHandler.callerId);\n            } else {\n              console.warn('Caller ID is missing. Cannot remove call dialer.');\n            }\n            break;\n          case 'busy':\n            CometChat.clearActiveCall();\n            if (voipHandler?.callerId) {\n              voipHandler.removeCallDialerWithUUID(voipHandler.callerId);\n            } else {\n              console.warn('Caller ID is missing. Cannot remove call dialer.');\n            }\n            break;\n          case 'ongoing':\n            voipHandler.displayNotification({\n              title: data?.receiverName || '',\n              body: 'ongoing call',\n            });\n            break;\n          case 'rejected':\n            CometChat.clearActiveCall();\n            if (voipHandler?.callerId) {\n              voipHandler.removeCallDialerWithUUID(voipHandler.callerId);\n            } else {\n              console.warn('Caller ID is missing. Cannot remove call dialer.');\n            }\n            break;\n          case 'cancelled':\n            CometChat.clearActiveCall();\n            if (voipHandler?.callerId) {\n              voipHandler.removeCallDialerWithUUID(voipHandler.callerId);\n            } else {\n              console.warn('Caller ID is missing. Cannot remove call dialer.');\n            }\n            break;\n          default:\n            break;\n        }\n        return;\n      } else {\n        // Handle badge count from push notification\n        const unreadCount = data?.unreadMessageCount;\n        if (unreadCount !== undefined && unreadCount !== null) {\n          const count = parseInt(unreadCount, 10);\n          if (!isNaN(count) && count >= 0) {\n            try {\n              await notifee.setBadgeCount(count);\n            } catch (error) {\n              console.error('Error setting badge:', error);\n            }\n          }\n        } else {\n          console.log('No unreadMessageCount in payload - check dashboard settings');\n        }\n        await displayLocalNotification(remoteMessage);\n      }\n    } catch (error) {\n      console.error('Error in background message handler:', error);\n    }\n  });\n}\nAppRegistry.registerComponent(appName, () => Root);"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/.xcode.env",
    "content": "# This `.xcode.env` file is versioned and is used to source the environment\n# used when running script phases inside Xcode.\n# To customize your local environment, you can create an `.xcode.env.local`\n# file that is not versioned.\n\n# NODE_BINARY variable contains the PATH to the node executable.\n#\n# Customize the NODE_BINARY variable here.\n# For example, to use nvm with brew, add the following line\n# . \"$(brew --prefix nvm)/nvm.sh\" --no-use\nexport NODE_BINARY=$(command -v node)\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/GoogleService-Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CLIENT_ID</key>\n\t<string>950141434859-7specrmte1j6db65aur0t4p82b6g27op.apps.googleusercontent.com</string>\n\t<key>REVERSED_CLIENT_ID</key>\n\t<string>com.googleusercontent.apps.950141434859-7specrmte1j6db65aur0t4p82b6g27op</string>\n\t<key>ANDROID_CLIENT_ID</key>\n\t<string>950141434859-082r7la3902v8l4627r0fq2gfqcek8t2.apps.googleusercontent.com</string>\n\t<key>API_KEY</key>\n\t<string>AIzaSyC5EP9RWuOWJA8PeZ58sd8XvnLbpytuuI8</string>\n\t<key>GCM_SENDER_ID</key>\n\t<string>950141434859</string>\n\t<key>PLIST_VERSION</key>\n\t<string>1</string>\n\t<key>BUNDLE_ID</key>\n\t<string>com.cometchat.internal.reactnative.ios</string>\n\t<key>PROJECT_ID</key>\n\t<string>cometchatteammessanger</string>\n\t<key>STORAGE_BUCKET</key>\n\t<string>cometchatteammessanger.firebasestorage.app</string>\n\t<key>IS_ADS_ENABLED</key>\n\t<false></false>\n\t<key>IS_ANALYTICS_ENABLED</key>\n\t<false></false>\n\t<key>IS_APPINVITE_ENABLED</key>\n\t<true></true>\n\t<key>IS_GCM_ENABLED</key>\n\t<true></true>\n\t<key>IS_SIGNIN_ENABLED</key>\n\t<true></true>\n\t<key>GOOGLE_APP_ID</key>\n\t<string>1:950141434859:ios:46ce64f494a16d70ee77ae</string>\n</dict>\n</plist>"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/Podfile",
    "content": "# use_modular_headers!\n# Resolve react_native_pods.rb with node to allow for hoisting\nrequire Pod::Executable.execute_command('node', ['-p',\n  'require.resolve(\n    \"react-native/scripts/react_native_pods.rb\",\n    {paths: [process.argv[1]]},\n  )', __dir__]).strip\n\nplatform :ios, min_ios_version_supported\nprepare_react_native_project!\nuse_frameworks! :linkage => :static\n\ntarget 'SampleAppWithPushNotifications' do\n  config = use_native_modules!\n\n  pod 'SPTPersistentCache', :modular_headers => true\n  pod 'DVAssetLoaderDelegate', :modular_headers => true\n\n  pod 'RNCallKeep', :path => '../node_modules/react-native-callkeep'\n  pod 'RNVoipPushNotification', :path => '../node_modules/react-native-voip-push-notification'\n\n  \n\n  use_react_native!(\n    :path => config[:reactNativePath],\n    # An absolute path to your application root.\n    :app_path => \"#{Pod::Config.instance.installation_root}/..\"\n  )\n\n  post_install do |installer|\n    # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202\n    react_native_post_install(\n      installer,\n      config[:reactNativePath],\n      :mac_catalyst_enabled => false,\n      # :ccache_enabled => true\n    )\n\n    installer.pods_project.targets.each do |target|\n      if target.name == 'SampleAppWithPushNotifications'\n        target.build_configurations.each do |config|\n          config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] = 'SampleAppWithPushNotifications-Bridging-Header.h'\n        end\n      end\n    end\n\n    # Fix fmt consteval compilation error with Xcode 26+\n    # https://github.com/expo/expo/issues/44229\n    fmt_base = File.join(installer.sandbox.pod_dir('fmt'), 'include', 'fmt', 'base.h')\n    if File.exist?(fmt_base)\n      content = File.read(fmt_base)\n      patched = content.gsub(/^#\\s*define FMT_USE_CONSTEVAL 1$/, '# define FMT_USE_CONSTEVAL 0')\n      if patched != content\n        File.chmod(0644, fmt_base)\n        File.write(fmt_base, patched)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/AppDelegate.swift",
    "content": "import UIKit\nimport React\nimport React_RCTAppDelegate\nimport ReactAppDependencyProvider\nimport RNCallKeep\nimport PushKit\nimport UserNotifications\nimport RNCPushNotificationIOS\n\n// Make sure you've properly bridged the RNVoipPushNotificationManager in your Swift Bridging Header\n// #import \"RNVoipPushNotificationManager.h\"\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, PKPushRegistryDelegate {\n\n  // MARK: - ADD A PushKit Registry property\n  private var pushRegistry: PKPushRegistry?\n   var window: UIWindow?\n  var reactNativeDelegate: ReactNativeDelegate?\n  var reactNativeFactory: RCTReactNativeFactory?\n\n  func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n  ) -> Bool {\n\n\n    let delegate = ReactNativeDelegate()\n    let factory = RCTReactNativeFactory(delegate: delegate)\n    delegate.dependencyProvider = RCTAppDependencyProvider()\n \n    reactNativeDelegate = delegate\n    reactNativeFactory = factory\n\n    // ============================================\n    // (Recommended) Register for VoIP right away\n    // ============================================\n    RNVoipPushNotificationManager.voipRegistration()\n\n    // ============================================\n    // Set up PushKit with desiredPushTypes = [.voIP]\n    // ============================================\n    pushRegistry = PKPushRegistry(queue: .main)\n    pushRegistry?.delegate = self\n    pushRegistry?.desiredPushTypes = [.voIP]\n\n    // Setup user notifications center delegate\n    UNUserNotificationCenter.current().delegate = self\n    \n        window = UIWindow(frame: UIScreen.main.bounds)\n\n        factory.startReactNative(\n      withModuleName: \"sampleapp\",\n      in: window,\n      launchOptions: launchOptions\n    )\n\n    return true\n  }\n\n   // MARK: - Standard APNs Token Methods\n\n  func application(\n    _ application: UIApplication,\n    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data\n  ) {\n    print(\"⚡️[AppDelegate] didRegisterForRemoteNotificationsWithDeviceToken deviceToken => \\(deviceToken)\")\n    // Forward token to RNCPushNotificationIOS\n    RNCPushNotificationIOS.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)\n  }\n\n  func application(\n    _ application: UIApplication,\n    didFailToRegisterForRemoteNotificationsWithError error: Error\n  ) {\n    print(\"⚡️[AppDelegate] didFailToRegisterForRemoteNotificationsWithError => \\(error)\")\n    // Forward error to RNCPushNotificationIOS\n    RNCPushNotificationIOS.didFailToRegisterForRemoteNotificationsWithError(error)\n  }\n\n  func application(\n    _ application: UIApplication,\n    didReceiveRemoteNotification userInfo: [AnyHashable : Any],\n    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void\n  ) {\n    // Forward notification to RNCPushNotificationIOS\n    RNCPushNotificationIOS.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler)\n  }\n\n  // MARK: - UserNotificationCenter (for iOS 10+)\n\n  @available(iOS 10.0, *)\n  func userNotificationCenter(\n    _ center: UNUserNotificationCenter,\n    didReceive response: UNNotificationResponse,\n    withCompletionHandler completionHandler: @escaping () -> Void\n  ) {\n    RNCPushNotificationIOS.didReceive(response)\n    completionHandler()\n  }\n\n  @available(iOS 10.0, *)\n  func userNotificationCenter(\n    _ center: UNUserNotificationCenter,\n    willPresent notification: UNNotification,\n    withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void\n  ) {\n    // Show the notification while in foreground\n    completionHandler([.alert, .badge, .sound])\n  }\n\n  // MARK: - Handle CallKit for iOS 10+ (SiriKit etc.)\n  func application(\n    _ application: UIApplication,\n    continue userActivity: NSUserActivity,\n    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void\n  ) -> Bool {\n    return RNCallKeep.application(application, continue: userActivity) {\n      (restoredObjects: [Any]?) in\n        restorationHandler(restoredObjects as? [UIUserActivityRestoring])\n    }\n  }\n\n  // MARK: - PKPushRegistryDelegate (VoIP Token)\n\n  func pushRegistry(\n    _ registry: PKPushRegistry,\n    didUpdate pushCredentials: PKPushCredentials,\n    for type: PKPushType\n  ) {\n    guard type == .voIP else { return }\n    let voipToken = pushCredentials.token.map { String(format: \"%02x\", $0) }.joined()\n    print(\"⚡️[AppDelegate] VoIP Token updated: \\(voipToken)\")\n    // Let RN VoipPushNotificationManager know\n    RNVoipPushNotificationManager.didUpdate(pushCredentials, forType: type.rawValue)\n  }\n\n  func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {\n    print(\"⚡️[AppDelegate] pushRegistry didInvalidatePushTokenFor => type: \\(type)\")\n    print(\"VoIP Push Token invalidated.\")\n  }\n\n  // MARK: - Handle incoming VoIP push\n  func pushRegistry(\n  _ registry: PKPushRegistry,\n  didReceiveIncomingPushWith payload: PKPushPayload,\n  for type: PKPushType,\n  completion: @escaping () -> Void\n) {\n    // Only proceed with reporting the call if the app is not in the foreground.\n    if UIApplication.shared.applicationState == .active {\n        // If desired, you could still forward the push to your RN module here.\n        completion()\n        return\n    }\n\n    // Process the VoIP push only when the app is in background or killed.\n    if type == .voIP {\n        let payloadDict = payload.dictionaryPayload as? [String: Any] ?? [:]\n        let callUUID = (payloadDict[\"uuid\"] as? String) ?? UUID().uuidString\n\n        // --- Extract relevant fields from the payload\n        let callAction = payloadDict[\"callAction\"] as? String ?? \"\"\n        let callerNameValue = payloadDict[\"senderName\"] as? String\n            ?? payloadDict[\"title\"] as? String\n            ?? \"Unknown\"\n        let callerName = callerNameValue\n        let handle = callerNameValue\n        let hasVideo = (payloadDict[\"callType\"] as? String) != \"audio\"\n\n        // --- Add RNVoipPushNotificationManager completion handler\n        RNVoipPushNotificationManager.addCompletionHandler(callUUID, completionHandler: completion)\n\n        // --- Forward the push payload to RNVoipPushNotificationManager\n        RNVoipPushNotificationManager.didReceiveIncomingPush(with: payload, forType: type.rawValue)\n\n        // --- Decide how to handle the call action\n        if callAction == \"initiated\" {\n            RNCallKeep.reportNewIncomingCall(\n              callUUID,\n              handle: handle,\n              handleType: \"generic\",\n              hasVideo: hasVideo,\n              localizedCallerName: callerName,\n              supportsHolding: false,\n              supportsDTMF: false,\n              supportsGrouping: false,\n              supportsUngrouping: false,\n              fromPushKit: true,\n              payload: nil\n            )\n        } else if callAction == \"unanswered\" {\n            RNCallKeep.endCall(withUUID: callUUID, reason: 3)\n        } else if callAction == \"rejected\" {\n            RNCallKeep.endCall(withUUID: callUUID, reason: 6)\n        } else if callAction == \"busy\" {\n            RNCallKeep.endCall(withUUID: callUUID, reason: 1)\n        } else if callAction == \"cancelled\" {\n            RNCallKeep.endCall(withUUID: callUUID, reason: 6)\n        } else if callAction == \"ended\" {\n            RNCallKeep.endCall(withUUID: callUUID, reason: 2)\n        } else {\n            RNCallKeep.endCall(withUUID: callUUID, reason: 3)\n        }\n    } else {\n        completion()\n    }\n}\n\n}\nclass ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {\n  override func sourceURL(for bridge: RCTBridge) -> URL? {\n    self.bundleURL()\n  }\n\n  override func bundleURL() -> URL? {\n#if DEBUG\n    return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: \"index\")\n#else\n    return Bundle.main.url(forResource: \"main\", withExtension: \"jsbundle\")\n#endif\n  }\n} \n\n// MARK: - Static UUID Storage\nextension AppDelegate {\n  static var callUUID: String?\n}\n\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/Images.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"CometChat_React_Native_40x40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_60x60.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_58x58.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_87x87.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_80x80.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_120x120 1.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_120x120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"CometChat_React_Native_180x180.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"CometChat React native.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/Images.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>sampleapp</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(MARKETING_VERSION)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>com.googleusercontent.apps.950141434859-rpballfvb8n1a9j0l2vu2nnq4r25gd7h</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<false/>\n\t\t<key>NSAllowsLocalNetworking</key>\n\t\t<true/>\n\t</dict>\n\t<key>NSCameraUsageDescription</key>\n\t<string>This is for camera permission</string>\n\t<key>NSLocationWhenInUseUsageDescription</key>\n\t<string></string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>This is for Mic permission</string>\n\t<key>RCTNewArchEnabled</key>\n\t<true/>\n\t<key>UIBackgroundModes</key>\n\t<array>\n\t\t<string>voip</string>\n\t\t<string>remote-notification</string>\n\t\t<string>push-to-talk</string>\n\t\t<string>processing</string>\n\t\t<string>fetch</string>\n\t</array>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>arm64</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n\t<key>UIAppFonts</key>\n  \t<array>\n\t\t<string>inter_regular.ttf</string>\n  \t\t<string>inter_medium.ttf</string>\n  \t\t<string>inter_bold.ttf</string>\n  \t\t<string>roboto_regular.ttf</string>\n  \t\t<string>roboto_medium.ttf</string>\n  \t\t<string>roboto_bold.ttf</string>\n  \t\t<string>times_new_roman_regular.ttf</string>\n  \t\t<string>times_new_roman_medium.otf</string>\n  \t\t<string>times_new_roman_bold.ttf</string>\n  \t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"23504\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina6_1\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"23506\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"414\" height=\"896\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView clipsSubviews=\"YES\"\n                                       userInteractionEnabled=\"NO\"\n                                       contentMode=\"scaleAspectFit\"\n                                       horizontalHuggingPriority=\"251\"\n                                       verticalHuggingPriority=\"251\"\n                                       image=\"CometChat React native.png\"\n                                       translatesAutoresizingMaskIntoConstraints=\"NO\"\n                                       id=\"CtN-Dg-ods\">\n                                <!-- The rect is now just an initial placeholder -->\n                                <rect key=\"frame\" x=\"0\" y=\"0\" width=\"414\" height=\"128\"/>\n                            </imageView>\n                        </subviews>\n                        <!-- Auto Layout constraints for centering and sizing the image view -->\n                        <constraints>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"centerXConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"centerYConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"width\" constant=\"414\" id=\"widthConstraint\"/>\n                            <constraint firstItem=\"CtN-Dg-ods\" firstAttribute=\"height\" constant=\"128\" id=\"heightConstraint\"/>\n                        </constraints>\n                        <viewLayoutGuide key=\"safeArea\" id=\"Bcu-3y-fUS\"/>\n                        <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"52.173913043478265\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"CometChat React native.png\" width=\"1024\" height=\"1024\"/>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/PrivacyInfo.xcprivacy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrivacyAccessedAPITypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryUserDefaults</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>CA92.1</string>\n\t\t\t\t<string>1C8F.1</string>\n\t\t\t\t<string>C56D.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>C617.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategorySystemBootTime</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>35F9.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryDiskSpace</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>85F4.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSPrivacyCollectedDataTypes</key>\n\t<array/>\n\t<key>NSPrivacyTracking</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/SampleAppWithPushNotifications-Bridging-Header.h",
    "content": "#import \"RNVoipPushNotificationManager.h\"\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/SampleAppWithPushNotifications.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>aps-environment</key>\n\t<string>development</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications/SampleAppWithPushNotificationsRelease.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>aps-environment</key>\n\t<string>development</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications-Bridging-Header.h",
    "content": "#import \"RNVoipPushNotificationManager.h\""
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };\n\t\t3D3E5A042D636D7600A031B0 /* CometChat React native.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D3E5A032D636D7600A031B0 /* CometChat React native.png */; };\n\t\t761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };\n\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };\n\t\tA611343A807D5A29E7D201FF /* Pods_SampleAppWithPushNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37317A45E6A968E4889FF12D /* Pods_SampleAppWithPushNotifications.framework */; };\n\t\tBA57F70DEDCE19B228241404 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t13B07F961A680F5B00A75B9A /* SampleAppWithPushNotifications.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleAppWithPushNotifications.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = SampleAppWithPushNotifications/Images.xcassets; sourceTree = \"<group>\"; };\n\t\t13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleAppWithPushNotifications/Info.plist; sourceTree = \"<group>\"; };\n\t\t13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = SampleAppWithPushNotifications/PrivacyInfo.xcprivacy; sourceTree = \"<group>\"; };\n\t\t37317A45E6A968E4889FF12D /* Pods_SampleAppWithPushNotifications.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleAppWithPushNotifications.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t3D3E5A032D636D7600A031B0 /* CometChat React native.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = \"CometChat React native.png\"; sourceTree = \"<group>\"; };\n\t\t3D70A9A22D5F35DD00A635C8 /* SampleAppWithPushNotifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = SampleAppWithPushNotifications.entitlements; path = SampleAppWithPushNotifications/SampleAppWithPushNotifications.entitlements; sourceTree = \"<group>\"; };\n\t\t6D482708643121969477F40B /* Pods-SampleAppWithPushNotifications.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-SampleAppWithPushNotifications.debug.xcconfig\"; path = \"Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = SampleAppWithPushNotifications/AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t7D0B947E5C322704C4BC0F21 /* Pods-SampleAppWithPushNotifications.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-SampleAppWithPushNotifications.release.xcconfig\"; path = \"Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SampleAppWithPushNotifications/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA611343A807D5A29E7D201FF /* Pods_SampleAppWithPushNotifications.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t13B07FAE1A68108700A75B9A /* SampleAppWithPushNotifications */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3D3E5A032D636D7600A031B0 /* CometChat React native.png */,\n\t\t\t\t3D70A9A22D5F35DD00A635C8 /* SampleAppWithPushNotifications.entitlements */,\n\t\t\t\t13B07FB51A68108700A75B9A /* Images.xcassets */,\n\t\t\t\t761780EC2CA45674006654EE /* AppDelegate.swift */,\n\t\t\t\t13B07FB61A68108700A75B9A /* Info.plist */,\n\t\t\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,\n\t\t\t\t13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,\n\t\t\t);\n\t\t\tname = SampleAppWithPushNotifications;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */,\n\t\t\t\t37317A45E6A968E4889FF12D /* Pods_SampleAppWithPushNotifications.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t832341AE1AAA6A7D00B99B32 /* Libraries */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Libraries;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t83CBB9F61A601CBA00E9B192 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FAE1A68108700A75B9A /* SampleAppWithPushNotifications */,\n\t\t\t\t832341AE1AAA6A7D00B99B32 /* Libraries */,\n\t\t\t\t83CBBA001A601CBA00E9B192 /* Products */,\n\t\t\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */,\n\t\t\t\tBBD78D7AC51CEA395F1C20DB /* Pods */,\n\t\t\t);\n\t\t\tindentWidth = 2;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 2;\n\t\t\tusesTabs = 0;\n\t\t};\n\t\t83CBBA001A601CBA00E9B192 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07F961A680F5B00A75B9A /* SampleAppWithPushNotifications.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBBD78D7AC51CEA395F1C20DB /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6D482708643121969477F40B /* Pods-SampleAppWithPushNotifications.debug.xcconfig */,\n\t\t\t\t7D0B947E5C322704C4BC0F21 /* Pods-SampleAppWithPushNotifications.release.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t13B07F861A680F5B00A75B9A /* SampleAppWithPushNotifications */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"SampleAppWithPushNotifications\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t5EB4A0D233A7A89637A1057F /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t13B07F871A680F5B00A75B9A /* Sources */,\n\t\t\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */,\n\t\t\t\t13B07F8E1A680F5B00A75B9A /* Resources */,\n\t\t\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,\n\t\t\t\t72DD0FF143FD3B928778DAD4 /* [CP] Embed Pods Frameworks */,\n\t\t\t\t1F046FB6E7BAFFF8C274153F /* [CP] Copy Pods Resources */,\n\t\t\t\t581E30170F732918FE5C24E7 /* [CP-User] [RNFB] Core Configuration */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = SampleAppWithPushNotifications;\n\t\t\tproductName = SampleAppWithPushNotifications;\n\t\t\tproductReference = 13B07F961A680F5B00A75B9A /* SampleAppWithPushNotifications.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t83CBB9F71A601CBA00E9B192 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1210;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t13B07F861A680F5B00A75B9A = {\n\t\t\t\t\t\tLastSwiftMigration = 1120;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"SampleAppWithPushNotifications\" */;\n\t\t\tcompatibilityVersion = \"Xcode 12.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 83CBB9F61A601CBA00E9B192;\n\t\t\tproductRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t13B07F861A680F5B00A75B9A /* SampleAppWithPushNotifications */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t13B07F8E1A680F5B00A75B9A /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,\n\t\t\t\t3D3E5A042D636D7600A031B0 /* CometChat React native.png in Resources */,\n\t\t\t\tBA57F70DEDCE19B228241404 /* PrivacyInfo.xcprivacy in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"$(SRCROOT)/.xcode.env.local\",\n\t\t\t\t\"$(SRCROOT)/.xcode.env\",\n\t\t\t);\n\t\t\tname = \"Bundle React Native code and images\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"set -e\\n\\nWITH_ENVIRONMENT=\\\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\\\"\\nREACT_NATIVE_XCODE=\\\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\\\"\\n\\n/bin/sh -c \\\"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\\\"\\n\";\n\t\t};\n\t\t1F046FB6E7BAFFF8C274153F /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications-resources-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications-resources-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t581E30170F732918FE5C24E7 /* [CP-User] [RNFB] Core Configuration */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)\",\n\t\t\t);\n\t\t\tname = \"[CP-User] [RNFB] Core Configuration\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"#!/usr/bin/env bash\\n#\\n# Copyright (c) 2016-present Invertase Limited & Contributors\\n#\\n# Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n# you may not use this library except in compliance with the License.\\n# You may obtain a copy of the License at\\n#\\n#   http://www.apache.org/licenses/LICENSE-2.0\\n#\\n# Unless required by applicable law or agreed to in writing, software\\n# distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n# See the License for the specific language governing permissions and\\n# limitations under the License.\\n#\\n\\n##########################################################################\\n##########################################################################\\n#\\n#  NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\\n#\\n#  This file is installed as an Xcode build script in the project file\\n#  by cocoapods, and you will not see your changes until you pod install\\n#\\n##########################################################################\\n##########################################################################\\n\\nset -e\\n\\n_MAX_LOOKUPS=2;\\n_SEARCH_RESULT=''\\n_RN_ROOT_EXISTS=''\\n_CURRENT_LOOKUPS=1\\n_JSON_ROOT=\\\"'react-native'\\\"\\n_JSON_FILE_NAME='firebase.json'\\n_JSON_OUTPUT_BASE64='e30=' # { }\\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\\n_TARGET_PLIST=\\\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\\\"\\n_DSYM_PLIST=\\\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\\\"\\n\\n# plist arrays\\n_PLIST_ENTRY_KEYS=()\\n_PLIST_ENTRY_TYPES=()\\n_PLIST_ENTRY_VALUES=()\\n\\nfunction setPlistValue {\\n  echo \\\"note:      setting plist entry '$1' of type '$2' in file '$4'\\\"\\n  ${_PLIST_BUDDY} -c \\\"Add :$1 $2 '$3'\\\" $4 || echo \\\"note:      '$1' already exists\\\"\\n}\\n\\nfunction getFirebaseJsonKeyValue () {\\n  if [[ ${_RN_ROOT_EXISTS} ]]; then\\n    ruby -Ku -e \\\"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\\\"\\n  else\\n    echo \\\"\\\"\\n  fi;\\n}\\n\\nfunction jsonBoolToYesNo () {\\n  if [[ $1 == \\\"false\\\" ]]; then\\n    echo \\\"NO\\\"\\n  elif [[ $1 == \\\"true\\\" ]]; then\\n    echo \\\"YES\\\"\\n  else echo \\\"NO\\\"\\n  fi\\n}\\n\\necho \\\"note: -> RNFB build script started\\\"\\necho \\\"note: 1) Locating ${_JSON_FILE_NAME} file:\\\"\\n\\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\\n  _CURRENT_SEARCH_DIR=$(pwd)\\nfi;\\n\\nwhile true; do\\n  _CURRENT_SEARCH_DIR=$(dirname \\\"$_CURRENT_SEARCH_DIR\\\")\\n  if [[ \\\"$_CURRENT_SEARCH_DIR\\\" == \\\"/\\\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\\n  echo \\\"note:      ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\\\"\\n  _SEARCH_RESULT=$(find \\\"$_CURRENT_SEARCH_DIR\\\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\\n  if [[ ${_SEARCH_RESULT} ]]; then\\n    echo \\\"note:      ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\\\"\\n    break;\\n  fi;\\n  _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\\ndone\\n\\nif [[ ${_SEARCH_RESULT} ]]; then\\n  _JSON_OUTPUT_RAW=$(cat \\\"${_SEARCH_RESULT}\\\")\\n  if ! _RN_ROOT_EXISTS=$(ruby -Ku -e \\\"require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\\\"); then\\n    echo \\\"error: Failed to parse firebase.json, check for syntax errors.\\\"\\n    exit 1\\n  fi\\n\\n  if [[ ${_RN_ROOT_EXISTS} ]]; then\\n    if ! python3 --version >/dev/null 2>&1; then echo \\\"error: python3 not found, firebase.json file processing error.\\\" && exit 1; fi\\n    _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\\\"'${_SEARCH_RESULT}'\\\"', '\\\"'rb'\\\"').read())['${_JSON_ROOT}']), '\\\"'utf-8'\\\"')).decode())' || echo \\\"e30=\\\")\\n  fi\\n\\n  _PLIST_ENTRY_KEYS+=(\\\"firebase_json_raw\\\")\\n  _PLIST_ENTRY_TYPES+=(\\\"string\\\")\\n  _PLIST_ENTRY_VALUES+=(\\\"$_JSON_OUTPUT_BASE64\\\")\\n\\n  # config.app_data_collection_default_enabled\\n  _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"app_data_collection_default_enabled\\\")\\n  if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FirebaseDataCollectionDefaultEnabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_APP_DATA_COLLECTION_ENABLED\\\")\\\")\\n  fi\\n\\n  # config.analytics_auto_collection_enabled\\n  _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_auto_collection_enabled\\\")\\n  if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_AUTO_COLLECTION\\\")\\\")\\n  fi\\n\\n  # config.analytics_collection_deactivated\\n  _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_collection_deactivated\\\")\\n  if [[ $_ANALYTICS_DEACTIVATED ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_DEACTIVATED\\\")\\\")\\n  fi\\n\\n  # config.analytics_idfv_collection_enabled\\n  _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_idfv_collection_enabled\\\")\\n  if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_IDFV_COLLECTION\\\")\\\")\\n  fi\\n\\n  # config.analytics_default_allow_analytics_storage\\n  _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_default_allow_analytics_storage\\\")\\n  if [[ $_ANALYTICS_STORAGE ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_STORAGE\\\")\\\")\\n  fi\\n\\n  # config.analytics_default_allow_ad_storage\\n  _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_default_allow_ad_storage\\\")\\n  if [[ $_ANALYTICS_AD_STORAGE ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_AD_STORAGE\\\")\\\")\\n  fi\\n\\n  # config.analytics_default_allow_ad_user_data\\n  _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_default_allow_ad_user_data\\\")\\n  if [[ $_ANALYTICS_AD_USER_DATA ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_AD_USER_DATA\\\")\\\")\\n  fi\\n\\n  # config.analytics_default_allow_ad_personalization_signals\\n  _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"analytics_default_allow_ad_personalization_signals\\\")\\n  if [[ $_ANALYTICS_PERSONALIZATION ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_PERSONALIZATION\\\")\\\")\\n  fi\\n\\n  # config.analytics_registration_with_ad_network_enabled\\n  _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"google_analytics_registration_with_ad_network_enabled\\\")\\n  if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\\\")\\\")\\n  fi\\n\\n  # config.google_analytics_automatic_screen_reporting_enabled\\n  _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"google_analytics_automatic_screen_reporting_enabled\\\")\\n  if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FirebaseAutomaticScreenReportingEnabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_ANALYTICS_AUTO_SCREEN_REPORTING\\\")\\\")\\n  fi\\n\\n  # config.perf_auto_collection_enabled\\n  _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"perf_auto_collection_enabled\\\")\\n  if [[ $_PERF_AUTO_COLLECTION ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"firebase_performance_collection_enabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_PERF_AUTO_COLLECTION\\\")\\\")\\n  fi\\n\\n  # config.perf_collection_deactivated\\n  _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"perf_collection_deactivated\\\")\\n  if [[ $_PERF_DEACTIVATED ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"firebase_performance_collection_deactivated\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_PERF_DEACTIVATED\\\")\\\")\\n  fi\\n\\n  # config.messaging_auto_init_enabled\\n  _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"messaging_auto_init_enabled\\\")\\n  if [[ $_MESSAGING_AUTO_INIT ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FirebaseMessagingAutoInitEnabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_MESSAGING_AUTO_INIT\\\")\\\")\\n  fi\\n\\n  # config.in_app_messaging_auto_colllection_enabled\\n  _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"in_app_messaging_auto_collection_enabled\\\")\\n  if [[ $_FIAM_AUTO_INIT ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_FIAM_AUTO_INIT\\\")\\\")\\n  fi\\n\\n  # config.app_check_token_auto_refresh\\n  _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"app_check_token_auto_refresh\\\")\\n  if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\\n    _PLIST_ENTRY_KEYS+=(\\\"FirebaseAppCheckTokenAutoRefreshEnabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"$(jsonBoolToYesNo \\\"$_APP_CHECK_TOKEN_AUTO_REFRESH\\\")\\\")\\n  fi\\n\\n  # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\\n  _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \\\"$_JSON_OUTPUT_RAW\\\" \\\"crashlytics_disable_auto_disabler\\\")\\n  if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \\\"true\\\" ]]; then\\n    echo \\\"Disabled Crashlytics auto disabler.\\\" # do nothing\\n  else\\n    _PLIST_ENTRY_KEYS+=(\\\"FirebaseCrashlyticsCollectionEnabled\\\")\\n    _PLIST_ENTRY_TYPES+=(\\\"bool\\\")\\n    _PLIST_ENTRY_VALUES+=(\\\"NO\\\")\\n  fi\\nelse\\n  _PLIST_ENTRY_KEYS+=(\\\"firebase_json_raw\\\")\\n  _PLIST_ENTRY_TYPES+=(\\\"string\\\")\\n  _PLIST_ENTRY_VALUES+=(\\\"$_JSON_OUTPUT_BASE64\\\")\\n  echo \\\"warning:   A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\\\"\\nfi;\\n\\necho \\\"note: 2) Injecting Info.plist entries: \\\"\\n\\n# Log out the keys we're adding\\nfor i in \\\"${!_PLIST_ENTRY_KEYS[@]}\\\"; do\\n  echo \\\"    ->  $i) ${_PLIST_ENTRY_KEYS[$i]}\\\" \\\"${_PLIST_ENTRY_TYPES[$i]}\\\" \\\"${_PLIST_ENTRY_VALUES[$i]}\\\"\\ndone\\n\\nfor plist in \\\"${_TARGET_PLIST}\\\" \\\"${_DSYM_PLIST}\\\" ; do\\n  if [[ -f \\\"${plist}\\\" ]]; then\\n\\n    # paths with spaces break the call to setPlistValue. temporarily modify\\n    # the shell internal field separator variable (IFS), which normally\\n    # includes spaces, to consist only of line breaks\\n    oldifs=$IFS\\n    IFS=\\\"\\n\\\"\\n\\n    for i in \\\"${!_PLIST_ENTRY_KEYS[@]}\\\"; do\\n      setPlistValue \\\"${_PLIST_ENTRY_KEYS[$i]}\\\" \\\"${_PLIST_ENTRY_TYPES[$i]}\\\" \\\"${_PLIST_ENTRY_VALUES[$i]}\\\" \\\"${plist}\\\"\\n    done\\n\\n    # restore the original internal field separator value\\n    IFS=$oldifs\\n  else\\n    echo \\\"warning:   A Info.plist build output file was not found (${plist})\\\"\\n  fi\\ndone\\n\\necho \\\"note: <- RNFB build script finished\\\"\\n\";\n\t\t};\n\t\t5EB4A0D233A7A89637A1057F /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-SampleAppWithPushNotifications-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"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\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t72DD0FF143FD3B928778DAD4 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-SampleAppWithPushNotifications/Pods-SampleAppWithPushNotifications-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t13B07F871A680F5B00A75B9A /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t13B07F941A680F5B00A75B9A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 6D482708643121969477F40B /* Pods-SampleAppWithPushNotifications.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = SampleAppWithPushNotifications/SampleAppWithPushNotifications.entitlements;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = T9F7QDSC2S;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = SampleAppWithPushNotifications/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = V5.3.4;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.cometchat.internal.reactnative.ios;\n\t\t\t\tPRODUCT_NAME = SampleAppWithPushNotifications;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"SampleAppWithPushNotifications-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t13B07F951A680F5B00A75B9A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7D0B947E5C322704C4BC0F21 /* Pods-SampleAppWithPushNotifications.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = SampleAppWithPushNotifications/SampleAppWithPushNotifications.entitlements;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = T9F7QDSC2S;\n\t\t\t\tINFOPLIST_FILE = SampleAppWithPushNotifications/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = V5.3.4;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.cometchat.internal.reactnative.ios;\n\t\t\t\tPRODUCT_NAME = SampleAppWithPushNotifications;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"SampleAppWithPushNotifications-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t83CBBA201A601CBA00E9B192 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios\",\n\t\t\t\t);\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(SDKROOT)/usr/lib/swift\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tOTHER_CPLUSPLUSFLAGS = (\n\t\t\t\t\t\"$(OTHER_CFLAGS)\",\n\t\t\t\t\t\"-DFOLLY_NO_CONFIG\",\n\t\t\t\t\t\"-DFOLLY_MOBILE=1\",\n\t\t\t\t\t\"-DFOLLY_USE_LIBCPP=1\",\n\t\t\t\t\t\"-DFOLLY_CFG_NO_COROUTINES=1\",\n\t\t\t\t\t\"-DFOLLY_HAVE_CLOCK_GETTIME=1\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\" \",\n\t\t\t\t);\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"$(inherited) DEBUG\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"SampleAppWithPushNotifications-Bridging-Header.h\";\n\t\t\t\tUSE_HERMES = true;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t83CBBA211A601CBA00E9B192 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\",\n\t\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios\",\n\t\t\t\t);\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(SDKROOT)/usr/lib/swift\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tOTHER_CPLUSPLUSFLAGS = (\n\t\t\t\t\t\"$(OTHER_CFLAGS)\",\n\t\t\t\t\t\"-DFOLLY_NO_CONFIG\",\n\t\t\t\t\t\"-DFOLLY_MOBILE=1\",\n\t\t\t\t\t\"-DFOLLY_USE_LIBCPP=1\",\n\t\t\t\t\t\"-DFOLLY_CFG_NO_COROUTINES=1\",\n\t\t\t\t\t\"-DFOLLY_HAVE_CLOCK_GETTIME=1\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\" \",\n\t\t\t\t);\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"SampleAppWithPushNotifications-Bridging-Header.h\";\n\t\t\t\tUSE_HERMES = true;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"SampleAppWithPushNotifications\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t13B07F941A680F5B00A75B9A /* Debug */,\n\t\t\t\t13B07F951A680F5B00A75B9A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"SampleAppWithPushNotifications\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t83CBBA201A601CBA00E9B192 /* Debug */,\n\t\t\t\t83CBBA211A601CBA00E9B192 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications.xcodeproj/xcshareddata/xcschemes/SampleAppWithPushNotifications.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1210\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n               BuildableName = \"SampleAppWithPushNotifications.app\"\n               BlueprintName = \"SampleAppWithPushNotifications\"\n               ReferencedContainer = \"container:SampleAppWithPushNotifications.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"00E356ED1AD99517003FC87E\"\n               BuildableName = \"SampleAppWithPushNotificationsTests.xctest\"\n               BlueprintName = \"SampleAppWithPushNotificationsTests\"\n               ReferencedContainer = \"container:SampleAppWithPushNotifications.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"SampleAppWithPushNotifications.app\"\n            BlueprintName = \"SampleAppWithPushNotifications\"\n            ReferencedContainer = \"container:SampleAppWithPushNotifications.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"SampleAppWithPushNotifications.app\"\n            BlueprintName = \"SampleAppWithPushNotifications\"\n            ReferencedContainer = \"container:SampleAppWithPushNotifications.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:SampleAppWithPushNotifications.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/ios/SampleAppWithPushNotifications.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/jest.config.js",
    "content": "module.exports = {\n  preset: 'react-native',\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/metro.config.js",
    "content": "const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('@react-native/metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/package.json",
    "content": "{\n  \"name\": \"sample-app-with-push-notifications\",\n  \"version\": \"5.3.4\",\n  \"private\": true,\n  \"scripts\": {\n    \"android\": \"react-native run-android\",\n    \"ios\": \"react-native run-ios\",\n    \"lint\": \"eslint .\",\n    \"start\": \"react-native start\",\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"@cometchat/calls-sdk-react-native\": \"^4.4.0\",\n    \"@cometchat/chat-sdk-react-native\": \"^4.0.21\",\n    \"@cometchat/chat-uikit-react-native\": \"^5.3.4\",\n    \"@notifee/react-native\": \"^9.1.8\",\n    \"@react-native-async-storage/async-storage\": \"^2.2.0\",\n    \"@react-native-clipboard/clipboard\": \"^1.16.3\",\n    \"@react-native-community/datetimepicker\": \"^8.4.5\",\n    \"@react-native-community/netinfo\": \"^11.4.1\",\n    \"@react-native-community/push-notification-ios\": \"^1.11.0\",\n    \"@react-native-firebase/app\": \"^23.4.0\",\n    \"@react-native-firebase/auth\": \"^23.4.0\",\n    \"@react-native-firebase/database\": \"^23.4.0\",\n    \"@react-native-firebase/messaging\": \"^23.4.0\",\n    \"@react-native/new-app-screen\": \"0.81.4\",\n    \"@react-navigation/bottom-tabs\": \"^7.4.7\",\n    \"@react-navigation/native\": \"^7.1.17\",\n    \"@react-navigation/native-stack\": \"^7.14.12\",\n    \"@react-navigation/stack\": \"^7.4.8\",\n    \"dayjs\": \"^1.11.18\",\n    \"react\": \"19.1.0\",\n    \"react-native\": \"0.81.4\",\n    \"react-native-background-timer\": \"^2.4.1\",\n    \"react-native-callkeep\": \"github:cometchat/react-native-callkeep\",\n    \"react-native-callstats\": \"^3.73.22\",\n    \"react-native-device-info\": \"^14.1.1\",\n    \"react-native-gesture-handler\": \"^2.28.0\",\n    \"react-native-localize\": \"^3.5.3\",\n    \"react-native-safe-area-context\": \"^5.6.1\",\n    \"react-native-screens\": \"^4.16.0\",\n    \"react-native-vision-camera\": \"^4.7.2\",\n    \"react-native-svg\": \"^15.13.0\",\n    \"react-native-video\": \"^6.16.1\",\n    \"react-native-voip-push-notification\": \"^3.3.3\",\n    \"react-native-webrtc\": \"^124.0.7\",\n    \"zustand\": \"^5.0.8\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.25.2\",\n    \"@babel/preset-env\": \"^7.25.3\",\n    \"@babel/runtime\": \"^7.25.0\",\n    \"@react-native-community/cli\": \"20.0.0\",\n    \"@react-native-community/cli-platform-android\": \"20.0.0\",\n    \"@react-native-community/cli-platform-ios\": \"20.0.0\",\n    \"@react-native/babel-preset\": \"0.81.4\",\n    \"@react-native/eslint-config\": \"0.81.4\",\n    \"@react-native/metro-config\": \"0.81.4\",\n    \"@react-native/typescript-config\": \"0.81.4\",\n    \"@types/jest\": \"^29.5.13\",\n    \"@types/react\": \"^19.1.0\",\n    \"@types/react-test-renderer\": \"^19.1.0\",\n    \"eslint\": \"^8.19.0\",\n    \"jest\": \"^29.6.3\",\n    \"prettier\": \"2.8.8\",\n    \"react-test-renderer\": \"19.1.0\",\n    \"typescript\": \"^5.8.3\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/AccountCircle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.743 17.438q1.485-1.047 3-1.609a9.3 9.3 0 0 1 3.256-.562q1.742 0 3.26.562 1.52.563 3.02 1.608 1.046-1.262 1.508-2.601a8.6 8.6 0 0 0 .462-2.835q0-3.497-2.377-5.874T12 3.75 6.126 6.127Q3.75 8.505 3.75 12.001q0 1.496.471 2.835.471 1.34 1.523 2.601m6.255-4.638q-1.44 0-2.428-.989-.987-.989-.987-2.429t.988-2.428 2.43-.987 2.428.989q.987.988.987 2.428t-.989 2.428-2.429.988m.005 9.033a9.5 9.5 0 0 1-3.822-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.124 9.5 9.5 0 0 1-.777-3.824q0-2.026.777-3.817a10 10 0 0 1 2.113-3.125 10 10 0 0 1 3.124-2.11 9.5 9.5 0 0 1 3.824-.776q2.025 0 3.818.777a10 10 0 0 1 3.124 2.113 10 10 0 0 1 2.11 3.125 9.5 9.5 0 0 1 .776 3.814q0 2.027-.777 3.822a10 10 0 0 1-2.112 3.129 10 10 0 0 1-3.126 2.11 9.5 9.5 0 0 1-3.814.776m-.004-1.583q1.342 0 2.586-.387 1.244-.388 2.447-1.296a10.3 10.3 0 0 0-2.455-1.277A7.7 7.7 0 0 0 12 16.85a7.9 7.9 0 0 0-2.584.433q-1.26.434-2.451 1.284 1.204.907 2.447 1.296A8.6 8.6 0 0 0 12 20.25m0-9.033q.795 0 1.315-.519.518-.519.518-1.315t-.518-1.314-1.315-.519-1.314.519-.519 1.314.519 1.315 1.314.519'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/AddComment.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.212 10.783v2.338q0 .333.228.564.228.232.562.232t.564-.232a.77.77 0 0 0 .229-.564v-2.338h2.338q.333 0 .564-.232a.77.77 0 0 0 .232-.562.76.76 0 0 0-.232-.56.77.77 0 0 0-.564-.229h-2.338V6.854a.76.76 0 0 0-.232-.556.77.77 0 0 0-.567-.231.75.75 0 0 0-.56.231.77.77 0 0 0-.224.556V9.2H8.866a.75.75 0 0 0-.56.234.78.78 0 0 0-.227.56q0 .327.227.558a.76.76 0 0 0 .56.231zm-5.15 7.067L3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/AiIcon.tsx",
    "content": "import Svg, { Path, Defs, LinearGradient, Stop } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst AiIcon = ({ width = 24, height = 24 }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 14 14\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M4.06663 7.53366L4.485 8.37042C4.6266 8.6536 4.6974 8.7952 4.79198 8.9179C4.8759 9.02678 4.97351 9.12438 5.08239 9.20831C5.20509 9.30289 5.34668 9.37369 5.62987 9.51528L6.46663 9.93366L5.62987 10.352C5.34668 10.4936 5.20509 10.5644 5.08239 10.659C4.97351 10.7429 4.8759 10.8405 4.79198 10.9494C4.6974 11.0721 4.6266 11.2137 4.485 11.4969L4.06663 12.3337L3.64825 11.4969C3.50665 11.2137 3.43586 11.0721 3.34128 10.9494C3.25735 10.8405 3.15974 10.7429 3.05086 10.659C2.92816 10.5644 2.78657 10.4936 2.50338 10.352L1.66663 9.93366L2.50338 9.51528C2.78657 9.37369 2.92816 9.30289 3.05086 9.20831C3.15974 9.12438 3.25735 9.02678 3.34128 8.9179C3.43586 8.7952 3.50665 8.6536 3.64825 8.37041L4.06663 7.53366Z\"\n      fill=\"url(#paint0_linear)\"\n    />\n    <Path\n      d=\"M8.59996 1.66699L9.22856 3.30135C9.37896 3.6924 9.45417 3.88793 9.57111 4.0524C9.67476 4.19817 9.80212 4.32552 9.94788 4.42917C10.1124 4.54612 10.3079 4.62132 10.6989 4.77173L12.3333 5.40033L10.6989 6.02892C10.3079 6.17933 10.1124 6.25453 9.94788 6.37148C9.80212 6.47513 9.67476 6.60248 9.57111 6.74825C9.45417 6.91272 9.37896 7.10825 9.22856 7.4993L8.59996 9.13366L7.97136 7.4993C7.82095 7.10825 7.74575 6.91272 7.62881 6.74825C7.52516 6.60248 7.3978 6.47513 7.25204 6.37148C7.08757 6.25453 6.89204 6.17933 6.50098 6.02892L4.86663 5.40033L6.50098 4.77173C6.89204 4.62132 7.08757 4.54612 7.25203 4.42917C7.3978 4.32552 7.52516 4.19817 7.62881 4.0524C7.74575 3.88793 7.82095 3.6924 7.97136 3.30135L8.59996 1.66699Z\"\n      fill=\"url(#paint1_linear)\"\n    />\n    <Defs>\n      <LinearGradient\n        id=\"paint0_linear\"\n        x1=\"6.99996\"\n        y1=\"1.66699\"\n        x2=\"6.99996\"\n        y2=\"19.2003\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <Stop stopColor=\"#AD94F2\" />\n        <Stop offset=\"1\" stopColor=\"#3302B8\" />\n      </LinearGradient>\n      <LinearGradient\n        id=\"paint1_linear\"\n        x1=\"6.99996\"\n        y1=\"1.66699\"\n        x2=\"6.99996\"\n        y2=\"19.2003\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <Stop stopColor=\"#AD94F2\" />\n        <Stop offset=\"1\" stopColor=\"#3302B8\" />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\n\nexport default AiIcon;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/ArrowBack.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m7.193 12.787 5.366 5.367a.8.8 0 0 1 .24.563.75.75 0 0 1-.236.554.79.79 0 0 1-.562.243.74.74 0 0 1-.554-.243L4.73 12.554a.75.75 0 0 1-.185-.256.8.8 0 0 1-.057-.298.8.8 0 0 1 .057-.298.8.8 0 0 1 .185-.265l6.717-6.716a.76.76 0 0 1 .552-.234q.318 0 .564.234a.8.8 0 0 1 .238.564q0 .319-.238.557l-5.37 5.362h11.854a.76.76 0 0 1 .558.23.77.77 0 0 1 .229.566.76.76 0 0 1-.229.562.77.77 0 0 1-.558.225z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Block.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.002 21.833a9.6 9.6 0 0 1-3.834-.774 10 10 0 0 1-3.123-2.105 9.9 9.9 0 0 1-2.104-3.12 9.6 9.6 0 0 1-.773-3.833q0-2.043.775-3.835a10 10 0 0 1 2.104-3.122 9.9 9.9 0 0 1 3.12-2.104A9.6 9.6 0 0 1 12 2.166q2.043 0 3.835.775a10 10 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.105 3.12 9.6 9.6 0 0 1 .772 3.833q0 2.043-.774 3.835a10 10 0 0 1-2.104 3.122 9.9 9.9 0 0 1-3.12 2.105 9.6 9.6 0 0 1-3.834.772m0-1.583q1.469 0 2.816-.488a8 8 0 0 0 2.45-1.412L5.655 6.733a8.6 8.6 0 0 0-1.408 2.46A8 8 0 0 0 3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399m6.33-2.983a8.8 8.8 0 0 0 1.407-2.449A7.9 7.9 0 0 0 20.25 12q0-3.452-2.399-5.851t-5.85-2.399q-1.464 0-2.807.496a8.4 8.4 0 0 0-2.46 1.42z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Builder.tsx",
    "content": "import React from \"react\";\nimport Svg, { Path, SvgProps } from \"react-native-svg\";\n\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d=\"M2.30775 17.5C1.81058 17.5 1.385 17.323 1.031 16.969C0.677 16.615 0.5 16.1894 0.5 15.6923V2.30775C0.5 1.81058 0.677 1.385 1.031 1.031C1.385 0.677 1.81058 0.5 2.30775 0.5H15.6923C16.1894 0.5 16.615 0.677 16.969 1.031C17.323 1.385 17.5 1.81058 17.5 2.30775V15.6923C17.5 16.1894 17.323 16.615 16.969 16.969C16.615 17.323 16.1894 17.5 15.6923 17.5H2.30775ZM2.30775 16H15.6923C15.7821 16 15.8558 15.9712 15.9135 15.9135C15.9712 15.8558 16 15.7821 16 15.6923V4H2V15.6923C2 15.7821 2.02883 15.8558 2.0865 15.9135C2.14417 15.9712 2.21792 16 2.30775 16ZM9 13.5C7.77433 13.5 6.67533 13.1757 5.703 12.527C4.7305 11.8782 4.01217 11.0358 3.548 10C4.01217 8.96417 4.7305 8.12183 5.703 7.473C6.67533 6.82433 7.77433 6.5 9 6.5C10.2257 6.5 11.3247 6.82433 12.297 7.473C13.2695 8.12183 13.9878 8.96417 14.452 10C13.9878 11.0358 13.2695 11.8782 12.297 12.527C11.3247 13.1757 10.2257 13.5 9 13.5ZM9 12.3077C9.86283 12.3077 10.6568 12.1045 11.3818 11.698C12.1068 11.2917 12.6955 10.7257 13.148 10C12.6955 9.27433 12.1068 8.70833 11.3818 8.302C10.6568 7.8955 9.86283 7.69225 9 7.69225C8.13717 7.69225 7.34325 7.8955 6.61825 8.302C5.89325 8.70833 5.3045 9.27433 4.852 10C5.3045 10.7257 5.89325 11.2917 6.61825 11.698C7.34325 12.1045 8.13717 12.3077 9 12.3077ZM9.00225 11.3077C9.36608 11.3077 9.67458 11.1803 9.92775 10.9255C10.1811 10.6708 10.3077 10.3616 10.3077 9.99775C10.3077 9.63392 10.1803 9.32542 9.9255 9.07225C9.67083 8.81892 9.36158 8.69225 8.99775 8.69225C8.63392 8.69225 8.32542 8.81967 8.07225 9.0745C7.81892 9.32917 7.69225 9.63842 7.69225 10.0022C7.69225 10.3661 7.81967 10.6746 8.0745 10.9277C8.32917 11.1811 8.63842 11.3077 9.00225 11.3077Z\"\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/CallFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Chat.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.062 17.85 3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275zm1.5-2.334h6.15a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.229h-6.15a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.557q0 .335.232.564a.77.77 0 0 0 .564.23m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.23H6.895a.77.77 0 0 0-.564.234.76.76 0 0 0-.232.557q0 .335.232.564.231.23.564.229m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.563.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.229H6.895a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.558q0 .334.232.564.231.229.564.229'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Chatfill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.039 17.5-2.002 2.002q-.427.426-.982.192-.555-.235-.555-.84V4.308q0-.758.525-1.283T4.308 2.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zM7 13.75h6q.319 0 .534-.216A.73.73 0 0 0 13.75 13a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 13.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 10a.73.73 0 0 0-.216-.534A.73.73 0 0 0 17 9.25H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 10.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 7a.73.73 0 0 0-.216-.535A.73.73 0 0 0 17 6.25H7a.73.73 0 0 0-.534.216A.73.73 0 0 0 6.25 7q0 .32.216.535A.73.73 0 0 0 7 7.75'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/CheckFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m9.551 15.516 8.639-8.639a.73.73 0 0 1 .522-.228q.299-.005.532.228a.74.74 0 0 1 .232.535q0 .302-.232.534l-9.06 9.075a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.27l-4.175-4.176a.71.71 0 0 1-.22-.53.75.75 0 0 1 .236-.539.74.74 0 0 1 .534-.233q.303 0 .535.233z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Close.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 13.117 6.895 18.22a.763.763 0 0 1-1.117-.004.766.766 0 0 1 .004-1.1l5.1-5.117-5.1-5.117a.74.74 0 0 1-.229-.544q0-.315.23-.556a.74.74 0 0 1 .55-.244.78.78 0 0 1 .562.232l5.104 5.112 5.112-5.112a.75.75 0 0 1 .555-.232.78.78 0 0 1 .562.244.76.76 0 0 1 .223.556.77.77 0 0 1-.235.544L13.116 12l5.1 5.117a.74.74 0 0 1 .229.543q0 .314-.23.557a.74.74 0 0 1-.55.243.74.74 0 0 1-.553-.24z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Delete.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.734 20.833q-.655 0-1.12-.464a1.53 1.53 0 0 1-.464-1.119V5.55h-.204a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.562q0-.33.232-.56a.77.77 0 0 1 .564-.23h3.89v-.012a.76.76 0 0 1 .23-.558.77.77 0 0 1 .564-.23h4.766q.324 0 .556.232.231.232.232.556v.013h3.9q.32 0 .552.232a.76.76 0 0 1 .231.558.76.76 0 0 1-.232.564.76.76 0 0 1-.555.229h-.213v13.7q0 .655-.464 1.119-.465.464-1.12.464zM17.284 5.55H6.734v13.7h10.55zm-7.29 11.62a.77.77 0 0 0 .559-.226.76.76 0 0 0 .23-.56V8.4a.77.77 0 0 0-.232-.565.76.76 0 0 0-.558-.23.76.76 0 0 0-.563.23.77.77 0 0 0-.23.565v7.983q0 .335.234.56a.78.78 0 0 0 .56.228m4.034 0a.77.77 0 0 0 .558-.226.76.76 0 0 0 .231-.56V8.4a.77.77 0 0 0-.233-.565.76.76 0 0 0-.557-.23.76.76 0 0 0-.564.23.77.77 0 0 0-.23.565v7.983q0 .335.235.56a.78.78 0 0 0 .56.228'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Flash.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ width = 24, height = 24, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M18 2H6V8L8 11V22H16V11L18 8V2ZM16 4V5H8V4H16ZM14 10.4V20H10V10.39L8 7.39V7H16V7.39L14 10.4Z\"\n      fill={color || \"#F9F8FD\"}\n    />\n    <Path\n      d=\"M12 15.5C12.8284 15.5 13.5 14.8284 13.5 14C13.5 13.1716 12.8284 12.5 12 12.5C11.1716 12.5 10.5 13.1716 10.5 14C10.5 14.8284 11.1716 15.5 12 15.5Z\"\n      fill={color || \"#F9F8FD\"}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Group.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M1.21 17.381q0-.85.431-1.54.43-.69 1.2-1.04 1.72-.776 3.2-1.137 1.478-.36 3.024-.36t3.013.36q1.468.361 3.186 1.136.771.35 1.211 1.04t.44 1.541v.819q0 .653-.465 1.118t-1.118.465H2.794q-.66 0-1.122-.465a1.53 1.53 0 0 1-.461-1.118zm20 2.402h-3.1q.159-.383.274-.77.114-.386.114-.813v-.817q0-1.41-.654-2.384-.654-.975-1.979-1.628 1.537.191 2.89.535 1.351.344 2.29.831.82.463 1.285 1.121.464.66.464 1.473v.865q0 .66-.465 1.123-.465.464-1.118.464M9.066 12q-1.62 0-2.677-1.056T5.332 8.267 6.388 5.59t2.677-1.057q1.62 0 2.677 1.057t1.056 2.677-1.056 2.677Q10.685 12 9.065 12m9.033-3.743q0 1.61-1.056 2.67t-2.677 1.06q-.255 0-.596-.042a3 3 0 0 1-.604-.13 4.4 4.4 0 0 0 .944-1.565 5.9 5.9 0 0 0 .323-1.986q0-1.08-.323-1.962a5.2 5.2 0 0 0-.944-1.602q.271-.084.596-.126a5 5 0 0 1 .604-.04q1.62 0 2.677 1.059t1.056 2.664M2.794 18.2h12.538v-.815q0-.364-.209-.69a1.3 1.3 0 0 0-.512-.474q-1.655-.755-2.9-1.044-1.246-.29-2.642-.29-1.4 0-2.662.29-1.262.289-2.909 1.044-.312.15-.508.476-.195.327-.196.686zm6.27-7.783q.922 0 1.536-.613.615-.614.615-1.536t-.613-1.537-1.535-.614-1.537.613-.615 1.535q0 .922.613 1.537t1.535.615'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/GroupAdd.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.596 11.642q.638-.694.944-1.594t.306-1.856-.306-1.856a4.4 4.4 0 0 0-.944-1.594q1.317.151 2.187 1.138t.87 2.312q0 1.327-.87 2.313a3.33 3.33 0 0 1-2.187 1.137m4.863 7.666q.188-.345.288-.733.099-.389.099-.787v-.827q0-.817-.333-1.556a3.6 3.6 0 0 0-.944-1.267q1.15.384 2.117 1.033.967.65.967 1.79v.827q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm2.194-8.558h-1.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.25V8q0-.319.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v1.25h1.25q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .319-.215.534a.73.73 0 0 1-.535.216h-1.25V12q0 .318-.215.534a.73.73 0 0 1-.535.216.72.72 0 0 1-.534-.216.73.73 0 0 1-.216-.534zm-11.307.942q-1.444 0-2.472-1.028T4.846 8.192 5.874 5.72t2.472-1.028 2.472 1.028 1.028 2.472-1.028 2.472-2.472 1.028m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.991-1.09 12.95 12.95 0 0 1 6.087 0q1.509.364 2.991 1.09.667.337 1.066.963.4.625.4 1.36v.704q0 .633-.444 1.076a1.47 1.47 0 0 1-1.075.444H2.364q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/GroupFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M1.797 17.085q0-.774.399-1.38a2.7 2.7 0 0 1 1.066-.944q1.426-.697 2.866-1.075t3.169-.378 3.168.377 2.867 1.076q.666.338 1.066.944.399.606.399 1.38v.703q0 .605-.444 1.062a1.44 1.44 0 0 1-1.076.458H3.316q-.633 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076zm16.613 2.223q.189-.345.288-.733t.099-.787v-.826q0-.985-.482-1.877a4.54 4.54 0 0 0-1.368-1.531q1.005.15 1.91.464.903.315 1.724.743.775.414 1.197.975t.422 1.226v.826q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm-9.113-7.616q-1.444 0-2.472-1.028T5.797 8.192q0-1.443 1.028-2.471t2.472-1.029 2.472 1.029 1.028 2.471-1.028 2.472q-1.029 1.028-2.472 1.028m8.634-3.5q0 1.444-1.028 2.472t-2.472 1.028a3 3 0 0 1-.43-.038 4 4 0 0 1-.431-.085q.59-.711.91-1.578a5.2 5.2 0 0 0-.007-3.593 5.8 5.8 0 0 0-.903-1.582q.215-.078.43-.1.216-.024.431-.024 1.444 0 2.472 1.029t1.028 2.471'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Info.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/InfoIcon.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Logout.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h5.952q.318 0 .534.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.952q.318 0 .534.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215zm12.31-7.75H9.845a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h7.771l-1.923-1.923a.7.7 0 0 1-.212-.507.74.74 0 0 1 .212-.531.72.72 0 0 1 .527-.241.72.72 0 0 1 .543.225l3.094 3.094q.27.271.27.633 0 .361-.27.633l-3.094 3.094a.71.71 0 0 1-.53.22.75.75 0 0 1-.54-.236.73.73 0 0 1-.21-.534.74.74 0 0 1 .226-.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Person.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06q1.628 0 2.689 1.06 1.06 1.06 1.06 2.69t-1.06 2.69-2.69 1.06m-7.85 6.2v-.817q0-.9.451-1.568a2.9 2.9 0 0 1 1.185-1.018 18 18 0 0 1 3.14-1.116 12.8 12.8 0 0 1 3.073-.377q1.545 0 3.065.381a18.4 18.4 0 0 1 3.134 1.118q.75.348 1.201 1.012.45.664.45 1.568v.821q0 .648-.465 1.114-.465.465-1.118.465H5.732q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.533v-.814q0-.366-.208-.686a1.36 1.36 0 0 0-.513-.48q-1.512-.728-2.831-1.03A12 12 0 0 0 12 14.887q-1.403 0-2.732.303-1.328.302-2.825 1.03-.312.16-.512.481-.2.322-.2.686zm6.266-7.783q.925 0 1.546-.621t.621-1.546-.62-1.546-1.547-.62-1.545.62-.621 1.546.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/PersonAdd.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.13 10.817h-2.345a.75.75 0 0 1-.56-.234.78.78 0 0 1-.228-.561q0-.327.227-.558a.76.76 0 0 1 .56-.23h2.347V6.887q0-.325.227-.556a.76.76 0 0 1 .563-.232q.334 0 .564.232t.229.556v2.345h2.337q.333 0 .565.233a.76.76 0 0 1 .231.558q0 .334-.231.564a.77.77 0 0 1-.565.229h-2.337v2.337q0 .333-.232.565a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.565zM9.019 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06 2.69 1.06 1.06 2.69-1.06 2.69T9.018 12m-7.85 6.2v-.818q0-.85.427-1.539a2.67 2.67 0 0 1 1.21-1.04q1.746-.795 3.22-1.147a12.8 12.8 0 0 1 5.973 0q1.467.352 3.22 1.144.78.363 1.215 1.046a2.8 2.8 0 0 1 .435 1.537v.821q0 .648-.465 1.114-.465.465-1.118.465H2.75q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.534v-.813q0-.364-.196-.686a1.23 1.23 0 0 0-.525-.48q-1.63-.763-2.882-1.048a12 12 0 0 0-2.662-.286q-1.419 0-2.677.286t-2.88 1.048q-.337.158-.524.479a1.33 1.33 0 0 0-.188.683zm6.267-7.783q.925 0 1.546-.621.62-.621.62-1.546t-.62-1.546q-.622-.62-1.546-.62-.925 0-1.546.62-.62.621-.62 1.546t.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/PersonFill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 11.692q-1.448 0-2.474-1.026A3.37 3.37 0 0 1 8.5 8.192q0-1.448 1.026-2.474A3.37 3.37 0 0 1 12 4.692q1.448 0 2.474 1.026A3.37 3.37 0 0 1 15.5 8.192q0 1.449-1.026 2.474A3.37 3.37 0 0 1 12 11.692m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.992-1.09 12.95 12.95 0 0 1 6.086 0q1.509.364 2.992 1.09.667.337 1.066.963t.399 1.36v.704q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444H6.019q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/PersonOff.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill={color ?? \"#none\"}\n    viewBox=\"0 0 24 24\"\n  >\n    <Path\n    fill={color ?? \"#000\"}\n      d=\"m18.777 20.6-2.108-2.092H6.622q-.552 0-.937-.385a1.27 1.27 0 0 1-.385-.935v-.404q0-.529.274-.982a2.1 2.1 0 0 1 .766-.74q1.171-.68 2.449-1.07 1.275-.39 2.61-.448h.158q.081 0 .159.01L3.385 5.223a.6.6 0 0 1-.195-.45.66.66 0 0 1 .21-.47.64.64 0 0 1 .464-.207q.255 0 .455.208l15.375 15.39a.65.65 0 0 1 .205.454.6.6 0 0 1-.203.452.62.62 0 0 1-.455.208.64.64 0 0 1-.464-.208M6.6 17.208h8.77l-2.328-2.352q-.27-.02-.52-.034a9.93 9.93 0 0 0-3.109.326 9.5 9.5 0 0 0-2.384 1.002.9.9 0 0 0-.315.278.6.6 0 0 0-.114.356zm11.296-2.04q.222.14.358.34.136.202.177.46l-1.187-1.187.33.18q.165.09.322.206m-4.18-4.009-.975-.95q.481-.22.77-.662.29-.44.289-.964 0-.741-.526-1.266a1.73 1.73 0 0 0-1.264-.525q-.522 0-.963.29-.442.288-.664.77l-.95-.975a2.85 2.85 0 0 1 1.112-1.027q.69-.358 1.455-.358 1.298 0 2.199.901.9.9.901 2.2 0 .764-.358 1.454-.357.69-1.027 1.112\"\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/Reset.tsx",
    "content": "import React from \"react\";\nimport Svg, { Path, SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 90 90\"\n    fill=\"none\"\n  >\n    <Path\n      d=\"M75.702 53.014c-2.142 7.995-7.27 14.678-14.439 18.816-7.168 4.138-15.519 5.239-23.514 3.095-16.505-4.423-26.335-21.448-21.913-37.953C20.258 20.467 37.286 10.64 53.79 15.06c4.213 1.129 8.076 3.118 11.413 5.809l-8.349 8.35h26.654V2.565l-8.354 8.354c-5.1-4.405-11.133-7.61-17.74-9.381C33.451-4.882 8.735 9.389 2.314 33.35c-6.42 23.961 7.851 48.678 31.811 55.098C38.001 89.486 41.934 90 45.842 90c7.795 0 15.488-2.044 22.42-6.046 10.407-6.008 17.851-15.709 20.962-27.317L75.702 53.014z\"\n      fill={color || \"#000\"}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/UserEmptyIcon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Mask, Path, Rect } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 120 120'>\n    <Mask\n      id='mask0_5082_129886'\n      style={{ maskType: \"alpha\" }}\n      maskUnits='userSpaceOnUse'\n      x='-20'\n      y='-20'\n      width='160'\n      height='160'\n    >\n      <Rect x='-20' y='-20' width='160' height='160' fill='#D9D9D9' />\n    </Mask>\n    <G mask='url(#mask0_5082_129886)'>\n      <Path\n        d='M60 57.9479C53.5644 57.9479 48.0667 55.6679 43.5067 51.1079C38.9467 46.549 36.6667 41.0512 36.6667 34.6146C36.6667 28.179 38.9467 22.6825 43.5067 18.1235C48.0667 13.5644 53.5644 11.2844 60 11.2844C66.4356 11.2844 71.9333 13.5644 76.4933 18.1235C81.0533 22.6825 83.3333 28.179 83.3333 34.6146C83.3333 41.0512 81.0533 46.549 76.4933 51.1079C71.9333 55.6679 66.4356 57.9479 60 57.9479ZM19.3166 105.007C16.1811 105.007 13.4568 103.856 11.1437 101.555C8.8306 99.2539 7.67344 96.5196 7.67344 93.3513V91.0288C7.67344 86.7545 8.70044 82.9018 10.7544 79.4706C12.8084 76.0385 15.6466 73.4095 19.268 71.5826C24.3797 69.1016 29.6071 67.2153 34.95 65.9235C40.2929 64.6317 46.0113 63.9858 52.105 63.9858H67.8949C73.987 63.9858 79.7055 64.6317 85.0491 65.9235C90.3928 67.2153 95.6215 69.1016 100.735 71.5826C104.357 73.4095 107.195 76.0385 109.249 79.4706C111.303 82.9018 112.33 86.7545 112.33 91.0288V93.3513C112.33 96.5196 111.173 99.2539 108.86 101.555C106.547 103.856 103.823 105.007 100.687 105.007H19.3166Z'\n        fill={color}\n      />\n    </G>\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/assets/icons/VideoCam.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.779 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h12.537q.627 0 1.104.471.475.471.475 1.112v5.1L21.13 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.104.47zm0-1.583h12.533V5.733H3.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/AIAgent/AIAgents.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUsers, useTheme} from '@cometchat/chat-uikit-react-native';\nimport React, {useCallback, useLayoutEffect} from 'react';\nimport {SafeAreaView} from 'react-native';\nimport {useFocusEffect, useNavigation} from '@react-navigation/native';\nimport {RootStackParamList} from '../../navigation/types';\nimport {StackNavigationProp} from '@react-navigation/stack';\n\ntype AIAgentNavigationProp = StackNavigationProp<RootStackParamList, 'AIAgents'>;\n\nconst AIAgents: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<AIAgentNavigationProp>();\n  const [shouldHide, setShouldHide] = React.useState(false);\n\n  // Focus effect to manage component visibility\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  // Configure header with back button\n  useLayoutEffect(() => {\n    navigation.setOptions({\n      headerShown: false,\n      title: 'AI Assistants',\n      headerStyle: {\n        backgroundColor: theme.color.background1,\n      },\n      headerTitleStyle: {\n        color: theme.color.textPrimary,\n      },\n    });\n  }, [navigation, theme]);\n\n  const handleUserPress = (user: CometChat.User) => {\n    navigation.navigate('Messages', { user });\n  };\n\n  return shouldHide ? null : (\n    <SafeAreaView style={{flex: 1, backgroundColor: theme.color.background1}}>\n      <CometChatUsers\n        onItemPress={handleUserPress}\n        title='Assistants'\n        showBackButton={true}\n        onBack={() => navigation.goBack()}\n        usersRequestBuilder={new CometChat.UsersRequestBuilder()\n          .setLimit(30)\n          .hideBlockedUsers(false)\n          .setRoles(['@agentic'])\n          .friendsOnly(false)\n          .setStatus('')\n          .setTags([])\n          .sortBy('name')\n          .setUIDs([])}\n      />\n    </SafeAreaView>\n  );\n};\n\nexport default AIAgents;"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/CallDetailHelper.tsx",
    "content": "import {CometChatTheme, CometChatUIKit} from '@cometchat/chat-uikit-react-native';\nimport {CallMade, CallMissedOutgoingFill, CallReceived} from './icons';\nimport {JSX} from 'react';\nimport { getCometChatTranslation } from '@cometchat/chat-uikit-react-native';\nconst t = getCometChatTranslation();\n\ntype CallDirection = 'incoming' | 'outgoing';\n\nexport type CallStatus =\n  | 'incoming'\n  | 'outgoing'\n  | 'incomingCallEnded'\n  | 'outgoingCallEnded'\n  | 'cancelledByMe'\n  | 'cancelledByThem'\n  | 'incomingRejected'\n  | 'outgoingRejected'\n  | 'incomingBusy'\n  | 'outgoingBusy'\n  | 'unansweredByMe'\n  | 'unansweredByThem';\n\nexport class CallDetailHelper {\n  static getFormattedInitiatedAt = (call: any): string => {\n    const date = new Date(call.getInitiatedAt() * 1000);\n    const now = new Date();\n\n    // Extracting parts\n    const day = date.getDate();\n    const month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(\n      date,\n    );\n    const year = date.getFullYear();\n    const time = date.toLocaleTimeString('en-US', {\n      hour: 'numeric',\n      minute: '2-digit',\n      hour12: true,\n    });\n\n    // Determine if the year should be included\n    const includeYear = now.getFullYear() !== year;\n\n    return `${day} ${month}${includeYear ? `, ${year}` : ''}, ${time}`;\n  };\n\n  /** Returns the UI-facing callStatus plus the direction */\n  static getCallType = (\n    call: any,\n  ): {type: CallDirection; callStatus: CallStatus} => {\n    const myUid = CometChatUIKit.loggedInUser?.getUid();\n    const type: CallDirection =\n      call.getInitiator().getUid() === myUid ? 'outgoing' : 'incoming';\n\n    const statusMap: Record<\n      string,\n      {incoming: CallStatus; outgoing: CallStatus}\n    > = {\n      ended: {\n        incoming: 'incomingCallEnded',\n        outgoing: 'outgoingCallEnded',\n      },\n      rejected: {\n        incoming: 'incomingRejected',\n        outgoing: 'outgoingRejected',\n      },\n      cancelled: {\n        incoming: 'unansweredByMe',\n        outgoing: 'cancelledByMe',\n      },\n      unanswered: {\n        incoming: 'unansweredByMe',\n        outgoing: 'unansweredByThem',\n      },\n      initiated: {\n        incoming: 'incoming',\n        outgoing: 'outgoing',\n      },\n      busy: {\n        incoming: 'incomingBusy',\n        outgoing: 'outgoingBusy',\n      },\n    };\n\n    return {\n      type,\n      callStatus:\n        statusMap[call.getStatus() as keyof typeof statusMap]?.[type] ??\n        (type === 'incoming' ? 'incoming' : 'outgoing'),\n    };\n  };\n\n  /** Which SVG to render for a given callStatus */\n  static getCallStatusDisplayIcon = (\n    callStatus: CallStatus,\n    theme: CometChatTheme,\n  ): JSX.Element | undefined => {\n    const icons: Record<CallStatus, JSX.Element> = {\n      outgoing: <CallMade height={24} width={24} color={theme.color.success} />,\n      outgoingCallEnded: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      cancelledByMe: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      outgoingRejected: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      outgoingBusy: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n      unansweredByThem: (\n        <CallMade height={24} width={24} color={theme.color.success} />\n      ),\n\n      incoming: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      incomingCallEnded: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      cancelledByThem: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n      incomingRejected: (\n        <CallReceived height={24} width={24} color={theme.color.success} />\n      ),\n      incomingBusy: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n      unansweredByMe: (\n        <CallMissedOutgoingFill\n          height={24}\n          width={24}\n          color={theme.color.error}\n        />\n      ),\n    };\n\n    return icons[callStatus];\n  };\n\n  static getCallStatusDisplayText = (callStatus: CallStatus): string => {\n    const labels: Record<CallStatus, string> = {\n      outgoing: t('OUTGOING_CALL'),\n      outgoingCallEnded: t('OUTGOING_CALL'),\n      cancelledByMe: t('OUTGOING_CALL'),\n      outgoingRejected: t('OUTGOING_CALL'),\n      outgoingBusy: t('OUTGOING_CALL'),\n      unansweredByThem: t('OUTGOING_CALL'),\n\n      incoming: t('INCOMING_CALL'),\n      incomingCallEnded: t('INCOMING_CALL'),\n      cancelledByThem: t('MISSED_CALL'),\n      incomingRejected: t('INCOMING_CALL'),\n      incomingBusy: t('MISSED_CALL'),\n      unansweredByMe: t('MISSED_CALL'),\n    };\n\n    return labels[callStatus];\n  };\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/CallDetails.tsx",
    "content": "import React, {\n  JSX,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {View, TouchableOpacity, Text, TextStyle, ViewStyle} from 'react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatListItem,\n  useCometChatTranslation,\n  useTheme,\n  useLocalizedDate,\n  LocalizedDateHelper\n} from '@cometchat/chat-uikit-react-native';\nimport {CallHistory} from './CallHistory';\nimport {CallLogDetailHeader} from './CallLogDetailHeader';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CallParticipants} from './CallParticipants';\nimport {CallDetailHelper, CallStatus} from './CallDetailHelper';\nimport {CallRecordings} from './CallRecordings';\nimport {StackScreenProps} from '@react-navigation/stack';\nimport {ICONS} from '@cometchat/chat-uikit-react-native/src/shared/icons/icon-mapping';\nimport {RootStackParamList} from '../../navigation/types';\n\n\nconst listenerId = 'userListener_' + new Date().getTime();\nconst TABS = {\n  DETAILS: 'Details',\n  PARTICIPANTS: 'Participants',\n  RECORDINGS: 'Recordings',\n  HISTORY: 'History',\n};\n\ntype Props = StackScreenProps<RootStackParamList, 'CallDetails'>;\n\nexport const CallDetails: React.FC<Props> = ({route, navigation}) => {\n  const {call} = route.params;\n\n  const theme = useTheme();\n  const {t, language} = useCometChatTranslation()\n  const {formatDate}= useLocalizedDate()\n  const [group, setGroup] = useState<CometChat.Group | null>(null);\n  const [user, setUser] = useState<CometChat.User | null>(null);\n  const loggedInUser = useRef<CometChat.User | any>(null);\n  const [selectedTab, setSelectedTab] = useState(TABS.PARTICIPANTS);\n  const [tabStyle, setTableStyle] = useState<{\n    containerStyle: ViewStyle;\n    itemStyle: ViewStyle;\n    selectedItemStyle: ViewStyle;\n    itemEmojiStyle: TextStyle;\n    selectedItemEmojiStyle: TextStyle;\n    itemTextStyle: TextStyle;\n    selectedItemTextStyle: TextStyle;\n  }>();\n  const BackIcon = ICONS['arrow-back'];\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  useEffect(() => {\n    console.log('CALL RECEIVER: ', call);\n    CometChat.getLoggedinUser().then((loggedUser: CometChat.User | any) => {\n      loggedInUser.current = loggedUser;\n      let user =\n        call?.getReceiverType() == 'user'\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      let group =\n        call?.getReceiverType() == 'group'\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      if (user) {\n        CometChat.getUser(user.getUid()).then((userObject: CometChat.User) => {\n          setUser(userObject);\n        });\n        return;\n      }\n\n      CometChat.getGroup(group.getGuid()).then(\n        (groupObject: CometChat.Group) => {\n          setGroup(groupObject);\n        },\n      );\n    });\n  }, [call]);\n\n  useEffect(() => {\n    CometChat.addUserListener(\n      listenerId,\n      new CometChat.UserListener({\n        onUserOnline: (userDetails: any) => {\n          if (user?.getUid() === userDetails?.getUid()) {\n            setUser(userDetails);\n          }\n        },\n        onUserOffline: (userDetails: any) => {\n          if (user?.getUid() === userDetails?.getUid()) {\n            setUser(userDetails);\n          }\n        },\n      }),\n    );\n    return () => {\n      CometChat.removeUserListener(listenerId);\n    };\n  }, [user]);\n\n  useEffect(() => {\n    setTableStyle({\n      containerStyle: {\n        //flex: 1,\n        backgroundColor: theme.color.background1,\n        flexDirection: 'row',\n        //justifyContent: 'space-evenly', // ✅ Ensures even spacing\n        alignItems: 'center', // Optional, ensures vertical alignment\n        width: '100%', // ✅ Ensures full width for proper spacing\n        borderBottomWidth: 1,\n        borderColor: theme.color.borderDefault,\n        justifyContent: 'space-evenly',\n      },\n      itemStyle: {\n        // paddingHorizontal: theme.spacing.padding.p4,\n        paddingVertical: theme.spacing.padding.p2,\n        flexDirection: 'row',\n        borderBottomWidth: theme.spacing.spacing.s0_5,\n        borderBottomColor: 'transparent',\n        alignItems: 'center',\n        justifyContent: 'center',\n        flex: 1,\n      },\n      selectedItemStyle: {\n        // paddingHorizontal: theme.spacing.padding.p4,\n        paddingVertical: theme.spacing.padding.p2,\n        flexDirection: 'row',\n        borderBottomWidth: theme.spacing.spacing.s0_5,\n        borderBottomColor: theme.color.primary,\n        alignItems: 'center',\n        justifyContent: 'center',\n        flex: 1,\n      },\n      itemEmojiStyle: {\n        color: theme.color.textSecondary,\n        borderColor: 'transparent',\n        ...theme.typography.body.medium,\n      },\n      selectedItemEmojiStyle: {\n        color: theme.color.textSecondary,\n        borderColor: 'transparent',\n        ...theme.typography.body.medium,\n      },\n      itemTextStyle: {\n        color: theme.color.textSecondary,\n        marginLeft: theme.spacing.margin.m1,\n        ...theme.typography.body.medium,\n      },\n      selectedItemTextStyle: {\n        color: theme.color.primary,\n        marginLeft: theme.spacing.margin.m1,\n        ...theme.typography.body.medium,\n      },\n    });\n  }, [theme]);\n\n  const getUserToFetchHistory = useCallback(() => {\n    if (call?.getInitiator().getUid() === loggedInUser.current.getUid()) {\n      return call?.getReceiver();\n    }\n    call?.getInitiator();\n  }, [call]);\n\n \n  const callTypeAndStatus = useMemo(\n    (): {\n      type: 'incoming' | 'outgoing';\n      callStatus: CallStatus;\n    } => CallDetailHelper.getCallType(call),\n    [call],\n  );\n\n  /** Busy call toast: only when attempting a call and target is busy */\n  useEffect(() => {\n    const callListener = new CometChat.CallListener({\n      onOutgoingCallRejected: (rejectedCall: any) => {\n        try {\n          const status = rejectedCall?.getStatus?.() || rejectedCall?.status;\n          if (\n            status &&\n            status.toLowerCase() === CometChat.CALL_STATUS.BUSY.toLowerCase()\n          ) {\n            setToastMessage(t('CALL_BUSY'));\n            setTimeout(() => setToastMessage(null), 3000);\n          }\n        } catch {}\n      },\n    });\n    CometChat.addCallListener(listenerId, callListener);\n    return () => {\n      CometChat.removeCallListener(listenerId);\n    };\n  }, [t]);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background2,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingRight: theme.spacing.padding.p4,\n          paddingLeft: theme.spacing.padding.p2,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          flex: 1,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.heading4.bold,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.caption1.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.bold,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n  const getFormattedInitiatedAt = useCallback(() => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [call]);\n\n  const callStatusDisplayString = useMemo(() => {\n    return CallDetailHelper.getCallStatusDisplayText(\n      callTypeAndStatus.callStatus,\n    );\n  }, [callTypeAndStatus]);\n\n  const CallStatusIcon = useMemo<JSX.Element | undefined>(\n    () =>\n      CallDetailHelper.getCallStatusDisplayIcon(\n        callTypeAndStatus.callStatus,\n        theme,\n      ),\n    [callTypeAndStatus, theme],\n  );\n\n  /** Local toast view component (shows only when toastMessage set; auto hides after 3s from effect) */\n  const ToastView = () => {\n    if (!toastMessage) return null;\n    return (\n      <View\n        style={{\n          alignSelf: 'center',\n          flexDirection: 'row',\n          alignItems: 'center',\n          borderRadius: 24,\n          backgroundColor: 'rgba(255,59,48,0.10)',\n          paddingVertical: theme.spacing.padding.p1,\n          paddingHorizontal: theme.spacing.padding.p2,\n          marginBottom: theme.spacing.margin.m3,\n          maxWidth: '80%',\n        }}>\n        <Icon name=\"info\" color={theme.color.error} size={18} />\n        <Text\n          style={[\n            theme.typography.caption1.bold,\n            {color: theme.color.error, marginLeft: theme.spacing.margin.m1},\n          ]}\n          accessibilityRole=\"alert\">\n          {toastMessage}\n        </Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      <View style={{flex: 1, paddingBottom: theme.spacing.padding.p3}}>\n        <View\n          style={{\n            flexDirection: 'row',\n            justifyContent: 'flex-start',\n            alignItems: 'center',\n            gap: theme.spacing.padding.p2,\n            paddingVertical: theme.spacing.padding.p2,\n            paddingHorizontal: theme.spacing.padding.p5,\n            minHeight: 64,\n          }}>\n          <TouchableOpacity onPress={() => navigation.goBack()}>\n            <Icon\n              imageStyle={{\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconPrimary,\n              }}\n              icon={\n                <BackIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.iconPrimary}></BackIcon>\n              }\n            />\n          </TouchableOpacity>\n          <Text\n            style={{\n              color: theme.color.textPrimary,\n              ...theme.typography.heading1.bold,\n              paddingTop: 35 - (35 * 0.75),\n            }}>\n            {t('CALL_DETAILS')}\n          </Text>\n        </View>\n\n        {(user || group) && (\n          <View style={{flexDirection: 'column'}}>\n            <CallLogDetailHeader\n              {...(user && {user})}\n              {...(group && {group})}></CallLogDetailHeader>\n            <View\n              style={{\n                flexDirection: 'row',\n                width: '100%',\n                alignItems: 'center',\n                backgroundColor: theme.color.background2,\n              }}>\n              <Icon\n                icon={CallStatusIcon}\n                containerStyle={{marginLeft: theme.spacing.margin.m4}}></Icon>\n              <CometChatListItem\n                id={call.sessionId}\n                avatarStyle={_style.itemStyle.avatarStyle}\n                containerStyle={_style.itemStyle.containerStyle}\n                headViewContainerStyle={{flexDirection: 'row'}}\n                titleStyle={_style.itemStyle.titleStyle}\n                title={callStatusDisplayString}\n                trailingViewContainerStyle={{\n                  alignSelf: 'center',\n                }}\n                SubtitleView={\n                  <Text style={_style.itemStyle.subtitleStyle}>\n                    {getFormattedInitiatedAt()}\n                  </Text>\n                }\n                TrailingView={\n                  <Text style={_style.itemStyle.tailViewTextStyle}>\n                    {convertMinutesToTime(call.getTotalDurationInMinutes())}\n                  </Text>\n                }\n              />\n            </View>\n\n            <View\n              style={{\n                flexDirection: 'row',\n                alignItems: 'center',\n                width: '100%',\n                borderBottomWidth: 1,\n                borderColor: theme.color.borderDefault,\n                justifyContent: 'space-evenly',\n                paddingHorizontal: 0,\n              }}>\n              {[\n                { key: TABS.PARTICIPANTS, title: t('PARTICIPANT') },\n                { key: TABS.RECORDINGS, title: t('RECORDING') },\n                { key: TABS.HISTORY, title: t('HISTORY') }\n              ].map((tab) => (\n                <TouchableOpacity\n                  key={tab.key}\n                  style={\n                    selectedTab === tab.key\n                      ? tabStyle!.selectedItemStyle\n                      : tabStyle!.itemStyle\n                  }\n                  onPress={() => setSelectedTab(tab.key)}>\n                  <Text\n                    style={\n                      selectedTab === tab.key\n                        ? tabStyle!.selectedItemTextStyle\n                        : tabStyle!.itemTextStyle\n                    }>\n                    {tab.title}\n                  </Text>\n                </TouchableOpacity>\n              ))}\n            </View>\n            {selectedTab === TABS.PARTICIPANTS && (\n              <CallParticipants\n                call={call}\n                data={call?.getParticipants()}></CallParticipants>\n            )}\n            {selectedTab === TABS.HISTORY && (\n              <CallHistory user={getUserToFetchHistory()}></CallHistory>\n            )}\n            {selectedTab === TABS.RECORDINGS &&\n              call.getRecordings()?.length && (\n                <CallRecordings call={call}></CallRecordings>\n              )}\n          </View>\n        )}\n      </View>\n      <View style={{width: '100%', paddingBottom: theme.spacing.padding.p3}}>\n        <ToastView />\n      </View>\n    </View>\n  );\n};\n\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/CallHistory.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CallingPackage,\n  CometChatListItem,\n  useTheme,\n  useCometChatTranslation,\n  useLocalizedDate,\n  LocalizedDateHelper\n} from '@cometchat/chat-uikit-react-native';\nimport React, { JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, FlatList, Text, View } from 'react-native';\nimport { CallDetailHelper } from './CallDetailHelper';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\nexport const CallHistory = (props: { user?: any; group?: any }) => {\n  const { user, group } = props;\n\n  const theme = useTheme();\n  const { language, t } = useCometChatTranslation();\n  const { formatDate } = useLocalizedDate();\n\n  const [list, setList] = useState<any[]>([]);\n  const [loading, setLoading] = useState<boolean>(true);\n  const [isFetchingMore, setIsFetchingMore] = useState<boolean>(false);\n\n  const loggedInUser = useRef<CometChat.User | any>(null);\n  const callRequestBuilderRef = useRef<any>(null);\n\n  function setRequestBuilder() {\n    callRequestBuilderRef.current;\n    let builder = new CometChatCalls.CallLogRequestBuilder()\n      .setLimit(30)\n      .setAuthToken(loggedInUser.current?.getAuthToken() || '')\n      .setCallCategory('call');\n    if (user) {\n      builder = builder.setUid(user?.getUid());\n    } else if (group) {\n      builder = builder.setGuid(group?.getGuid());\n    }\n    callRequestBuilderRef.current = builder.build();\n  }\n\n  const fetchCallLogHistory = () => {\n    if (!callRequestBuilderRef.current || isFetchingMore) {\n      return;\n    }\n    setIsFetchingMore(true);\n    callRequestBuilderRef.current\n      .fetchNext()\n      .then((CallLogHistory: any) => {\n        if (CallLogHistory.length > 0) {\n          setList(prev => [...prev, ...CallLogHistory]);\n        }\n      })\n      .catch((err: any) => {\n        // onError && onError(err);\n      })\n      .finally(() => {\n        setLoading(false);\n        setIsFetchingMore(false);\n      });\n  };\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: any) => {\n        loggedInUser.current = u;\n        setRequestBuilder();\n        fetchCallLogHistory();\n      })\n      .catch((e: any) => {\n        // onError && onError(e);\n        setLoading(false);\n      });\n  }, []);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          marginHorizontal: theme.spacing.margin.m4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          flex: 1,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.bold,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.caption1.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.bold,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  // Updated to use proper localization\n  const getFormattedInitiatedAt = useCallback((call: any) => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    // Use localizedDateHelper to format the date with proper localization\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [formatDate]);\n\n  const getCallType = useCallback((call: any) => {\n    return CallDetailHelper.getCallType(call);\n  }, []);\n\n  const getCallStatusIcon = useCallback((item: any): JSX.Element => {\n    const CallStatusIcon = CallDetailHelper.getCallStatusDisplayIcon(\n      getCallType(item).callStatus,\n      theme,\n    );\n    return CallStatusIcon || <View />;\n  }, []);\n\n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n  \n\n  // Added to get localized call status text\n  const getCallStatusText = useCallback((callStatus: any) => {\n    // Get translation key for this status\n    const translationKeys: Record<string, string> = {\n      outgoing: 'OUTGOING_CALL',\n      outgoingCallEnded: 'OUTGOING_CALL',\n      cancelledByMe: 'OUTGOING_CALL',\n      outgoingRejected: 'OUTGOING_CALL',\n      outgoingBusy: 'OUTGOING_CALL',\n      unansweredByThem: 'OUTGOING_CALL',\n      incoming: 'INCOMING_CALL',\n      incomingCallEnded: 'INCOMING_CALL',\n      cancelledByThem: 'MISSED_CALL',\n      incomingRejected: 'INCOMING_CALL',\n      incomingBusy: 'MISSED_CALL',\n      unansweredByMe: 'MISSED_CALL',\n    };\n\n    const key = translationKeys[callStatus] || 'UNKNOWN_CALL';\n    return t(key);\n  }, [t]);\n\n  const _render = ({ item, index }: any) => {\n    return (\n      <React.Fragment key={index}>\n        <View\n          style={{\n            flexDirection: 'row',\n            width: '100%',\n            alignItems: 'center',\n          }}>\n          <Icon\n            icon={getCallStatusIcon(item)}\n            containerStyle={{ marginLeft: theme.spacing.margin.m4 }}></Icon>\n          <CometChatListItem\n            id={item.sessionId}\n            containerStyle={_style.itemStyle.containerStyle}\n            headViewContainerStyle={{ flexDirection: 'row' }}\n            trailingViewContainerStyle={{\n              alignSelf: 'center',\n            }}\n            titleStyle={_style.itemStyle.titleStyle}\n           title={CallDetailHelper.getCallStatusDisplayText(\n              getCallType(item).callStatus,\n            )}\n            SubtitleView={\n              <Text\n                style={{\n                  ...theme.typography.body.regular,\n                  color: theme.color.textSecondary,\n                }}>\n                {getFormattedInitiatedAt(item)}\n              </Text>\n            }\n            TrailingView={\n              <Text style={_style.itemStyle.tailViewTextStyle}>\n              {convertMinutesToTime(item.getTotalDurationInMinutes())}\n              </Text>\n            }\n          />\n        </View>\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <FlatList\n      data={list}\n      keyExtractor={(item, index) => item.sessionId + '_' + index}\n      renderItem={_render}\n      onEndReached={fetchCallLogHistory}\n      onEndReachedThreshold={0.5}\n      ListEmptyComponent={\n        loading ? (\n          <View\n            style={{\n              flex: 1,\n              height: 300,\n              justifyContent: 'center',\n              alignItems: 'center',\n            }}\n          >\n            <ActivityIndicator size=\"large\" color={theme.color.primary} />\n          </View>\n        ) : null\n      }\n    />\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/CallLogDetailHeader.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {\n  useTheme,\n  CometChatAvatar,\n  CometChatCallButtons,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  GroupTypeConstants,\n  UserStatusConstants,\n} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport {CometChatCompThemeProvider} from '@cometchat/chat-uikit-react-native/src/theme/provider';\nimport React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';\nimport {Text, View} from 'react-native';\nimport { useConfig } from '../../config/store';\nexport type CallLogDetailHeaderInterface = {\n  user?: CometChat.User;\n  /**\n   *\n   * @type {CometChat.Group}\n   *   To pass group object\n   */\n  group?: CometChat.Group;\n};\n\nexport const CallLogDetailHeader = (props: CallLogDetailHeaderInterface) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const {user, group} = props;\n\n  const [groupObj, setGroupObj] = useState(group);\n  const [userStatus, setUserStatus] = useState(\n    user &&\n      !(user.getBlockedByMe() || user.getHasBlockedMe()) &&\n      user?.getStatus\n      ? user?.getStatus()\n      : '',\n  );\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n  const receiverTypeRef = useRef(\n    user\n      ? CometChat.RECEIVER_TYPE.USER\n      : group\n        ? CometChat.RECEIVER_TYPE.GROUP\n        : null,\n  );\n\n  useEffect(() => {\n    setGroupObj(group);\n  }, [group]);\n\n  useEffect(() => {\n    setUserStatus(\n      user && !(user.getBlockedByMe() || user.getHasBlockedMe())\n        ? user?.getStatus()\n        : '',\n    );\n  }, [user]);\n\n  const messageHeaderStyles = useMemo(() => {\n    return theme.messageHeaderStyles;\n  }, [theme.messageHeaderStyles]);\n\n  const statusIndicatorType = useMemo(() => {\n    if (groupObj?.getType() === GroupTypeConstants.password) {\n      return 'protected';\n    } else if (groupObj?.getType() === GroupTypeConstants.private) {\n      return 'private';\n    } else if (userStatus === 'online') {\n      return 'online';\n    }\n    return '';\n  }, [userStatus, groupObj]);\n\n  const AvatarWithStatusView = useCallback(() => {\n    return (\n      <View>\n        <CometChatAvatar\n          image={\n            user\n              ? user.getAvatar()\n                ? {uri: user.getAvatar()}\n                : undefined\n              : groupObj\n                ? groupObj.getIcon()\n                  ? {uri: groupObj.getIcon()}\n                  : undefined\n                : undefined\n          }\n          name={user?.getName() || groupObj?.getName() || ''}\n        />\n      </View>\n    );\n  }, [user, groupObj, statusIndicatorType]);\n\n  const SubtitleViewFnc = () => {\n    const statusTytle =\n      receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&\n      (groupObj?.['membersCount'] || groupObj?.['membersCount'] === 0)\n        ? `${groupObj['membersCount']} ${t('MEMBERS')}`\n        : receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER\n          ? userStatus === UserStatusConstants.online\n            ? t('ONLINE')\n            : userStatus === UserStatusConstants.offline\n              ? t('OFFLINE')\n              : ''\n          : '';\n\n    if(!statusTytle) return <></>;\n    return (\n      <Text\n        style={{\n          ...theme.typography.caption1.regular,\n          color: theme.color.textSecondary,\n        }}>\n        {statusTytle}\n      </Text>\n    );\n  };\n\n  return (\n    <CometChatCompThemeProvider\n      theme={{\n        callButtonStyles: messageHeaderStyles.callButtonStyle,\n        avatarStyle: messageHeaderStyles.avatarStyle,\n        statusIndicatorStyle: messageHeaderStyles.statusIndicatorStyle,\n      }}>\n      <View\n        style={{\n          paddingVertical: theme.spacing.padding.p5,\n          paddingHorizontal: theme.spacing.padding.p4,\n          backgroundColor: theme.color.background1,\n          flexDirection: 'row',\n          gap: theme.spacing.padding.p3,\n          alignItems: 'center',\n          borderTopWidth: 1,\n          borderBottomWidth: 1,\n          borderColor: theme.color.borderLight,\n        }}>\n        <AvatarWithStatusView />\n        <View style={{flex: 1}}>\n          <Text\n            numberOfLines={1}\n            ellipsizeMode=\"tail\"\n            style={{\n              ...theme.typography.heading4.medium,\n              color: theme.color.textPrimary,\n            }}>\n            {user ? user.getName() : groupObj ? groupObj.getName() : ''}\n          </Text>\n          {<SubtitleViewFnc />}\n        </View>\n        <View style={{marginLeft: 'auto'}}>\n          <CometChatCallButtons\n            user={user}\n            group={group}\n            hideVoiceCallButton={\n              (user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)\n            }\n            hideVideoCallButton={\n              (user && !oneOnOneVideoCalling) || (group && !groupVideoConference)\n            }\n            style={{\n              containerStyle: {\n                gap: 10,\n              },\n              audioCallButtonIconStyle: {\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconHighlight,\n              },\n              videoCallButtonIconStyle: {\n                height: 24,\n                width: 24,\n                tintColor: theme.color.iconHighlight,\n              },\n              audioCallButtonIconContainerStyle: {\n                height: 40,\n                width: 64,\n                paddingVertical: theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p5,\n                borderWidth: 1,\n                borderRadius: theme.spacing.radius.r2,\n                borderColor: theme.color.borderDefault,\n              },\n              videoCallButtonIconContainerStyle: {\n                height: 40,\n                width: 64,\n                paddingVertical: theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p5,\n                borderWidth: 1,\n                borderRadius: theme.spacing.radius.r2,\n                borderColor: theme.color.borderDefault,\n              },\n            }}\n          />\n        </View>\n      </View>\n    </CometChatCompThemeProvider>\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/CallParticipants.tsx",
    "content": "import { CometChatListItem, useTheme, useCometChatTranslation, localizedDateHelperInstance, useLocalizedDate, LocalizedDateHelper } from '@cometchat/chat-uikit-react-native';\nimport React, { useCallback, useMemo } from 'react';\nimport { View, FlatList, Text } from 'react-native';\nimport { CallDetailHelper } from './CallDetailHelper';\n\nexport const CallParticipants = (props: {\n  /**\n   * Participant list\n   */\n  data: any[];\n  call: any;\n}) => {\n  const { call, data } = props;\n\n  const theme = useTheme();\n  const { language, t } = useCometChatTranslation();\n  const { formatDate } = useLocalizedDate();\n\n  const getCallDetails = (item: any) => {\n    return {\n      title: item['name'],\n      avatarUrl: item['avatar'],\n    };\n  };\n\n  // Updated to use proper localization\n  const formattedInitiatedAt = useMemo(() => {\n    if (!call || !call.getInitiatedAt()) return '';\n\n    // Use formatDate for proper localization of date/time\n    return formatDate(\n      call.getInitiatedAt() * 1000,\n      LocalizedDateHelper.patterns.dayDateTimeFormat\n    );\n  }, [call, formatDate]);\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingHorizontal: theme.spacing.padding.p4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.body.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.medium,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n  \n  const convertMinutesToTime = useCallback((decimalMinutes: number) => {\n    const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds\n    const minutes = Math.floor(totalSeconds / 60); // Get whole minutes\n    const seconds = totalSeconds % 60; // Get remaining seconds\n\n    return `${minutes} min  ${seconds} sec`;\n  }, []);\n\n \n\n  const _render = ({ item, index }: any) => {\n    const { title, avatarUrl } = getCallDetails(item);\n\n    return (\n      <React.Fragment key={index}>\n        <CometChatListItem\n          id={item.sessionId}\n          avatarStyle={_style.itemStyle.avatarStyle}\n          containerStyle={_style.itemStyle.containerStyle}\n          headViewContainerStyle={{ flexDirection: 'row' }}\n          titleStyle={_style.itemStyle.titleStyle}\n          trailingViewContainerStyle={{\n            alignSelf: 'center',\n          }}\n          SubtitleView={\n            <Text\n              style={{\n                ...theme.typography.body.regular,\n                color: theme.color.textSecondary,\n              }}>\n              {formattedInitiatedAt}\n            </Text>\n          }\n          // avatarName={title}\n          title={title}\n          // subtitle={'8 August, 8:14 pm'}\n          avatarURL={avatarUrl}\n          TrailingView={\n            <Text style={_style.itemStyle.tailViewTextStyle}>\n              {convertMinutesToTime(item.getTotalDurationInMinutes())}\n            </Text>\n          }\n        />\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <View style={{ backgroundColor: theme.color.background1 }}>\n      {data.length && (\n        <FlatList\n          data={data}\n          keyExtractor={(item, index) => item.sessionId + '_' + index}\n          renderItem={_render}\n        />\n      )}\n    </View>\n  );\n};"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/CallRecordings.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {useTheme, CometChatListItem} from '@cometchat/chat-uikit-react-native';\nimport React, {useRef, useEffect, useMemo, useState} from 'react';\nimport {\n  View,\n  FlatList,\n  Text,\n  Pressable,\n  NativeEventEmitter,\n  NativeModules,\n  Platform,\n} from 'react-native';\nimport {PlayArrow} from './icons';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CallDetailHelper} from './CallDetailHelper';\n\nconst {FileManager} = NativeModules;\nconst eventEmitter = new NativeEventEmitter(FileManager);\n\nexport const CallRecordings = (props: {call: any}) => {\n  const {call} = props;\n  const [data, setData] = useState(call.getRecordings());\n\n  const theme = useTheme();\n\n  const loggedInUser = useRef(null);\n  const downloadIdRef = useRef(0);\n  const [processing, setProcessing] = React.useState(false);\n  const [fileExists, setFileExists] = React.useState(false); // State to track if the file exists\n  let listener: any = useRef(undefined);\n  const [fileUrl, setFileUrl] = useState();\n  const [reOpenCount, setReopenCount] = useState(0);\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: any) => {\n        loggedInUser.current = u;\n      })\n      .catch((e: any) => {\n        // onError && onError(e);\n      });\n  }, []);\n\n  useEffect(() => {\n    if (Platform.OS == 'android') {\n      listener.current = eventEmitter.addListener(\n        'downloadComplete',\n        (data: {downloadId: number}) => {\n          if (\n            data.downloadId &&\n            downloadIdRef.current &&\n            data.downloadId == downloadIdRef.current\n          ) {\n            setProcessing(false);\n            setFileExists(true);\n            openFile(fileUrl);\n          }\n        },\n      );\n    }\n    return () => {\n      if (Platform.OS == 'android') {\n        listener.current.remove();\n      }\n    };\n  }, []);\n\n  useEffect(() => {\n    if (!fileUrl) return;\n\n    const fileName = getFileName(fileUrl);\n    setProcessing(true);\n    FileManager.doesFileExist(fileName, (result: string) => {\n      setProcessing(false);\n      if (JSON.parse(result).exists) {\n        setFileExists(true);\n        openFile(fileUrl);\n      } else {\n        downloadFile(fileUrl);\n      }\n    });\n  }, [fileUrl, reOpenCount]);\n\n  const downloadFile = (fileUrl: any) => {\n    if (processing || fileExists) return; // Do not process if file already exists\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.checkAndDownload(\n      fileUrl,\n      getFileName(fileUrl),\n      (result: string) => {\n        if (Platform.OS == 'ios') {\n          let parsedResult = JSON.parse(result);\n          if (parsedResult.success == true) {\n            setProcessing(false);\n            setFileExists(true);\n          }\n          openFile(fileUrl);\n        } else if (Platform.OS == 'android') {\n          downloadIdRef.current = JSON.parse(result).downloadId;\n        }\n      },\n    );\n  };\n\n  const openFile = (fileUrl: any) => {\n    if (processing) return;\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.openFileWithOption(getFileName(fileUrl), (isOpened: string) => {\n      setProcessing(false);\n    });\n  };\n\n  const getFileName = (fileUrl: any) => {\n    return fileUrl.substring(fileUrl.lastIndexOf('/') + 1).replace(' ', '_');\n  };\n\n  const _style = useMemo(() => {\n    return {\n      headerContainerStyle: {\n        alignItems: 'flex-start',\n        justifyContent: 'center',\n        width: '100%',\n        borderRadius: 0,\n        paddingHorizontal: 0,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: theme.color.borderLight,\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n      },\n      containerStyle: {\n        backgroundColor: theme.color.background1,\n        flex: 1,\n      },\n      itemStyle: {\n        containerStyle: {\n          flexDirection: 'row' as const,\n          paddingHorizontal: theme.spacing.padding.p4,\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s3,\n          minHeight: 72,\n        },\n        titleStyle: {\n          color: theme.color.textPrimary,\n          flex: 1,\n          ...theme.typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: theme.color.textSecondary,\n          ...theme.typography.body.regular,\n        },\n        tailViewTextStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.caption1.medium,\n        },\n        avatarStyle: {\n          containerStyle: {},\n          textStyle: {},\n          imageStyle: {\n            height: 48,\n            width: 48,\n          },\n        },\n      },\n    };\n  }, [theme]);\n\n  const formattedInitiatedAt = useMemo(() => {\n    return CallDetailHelper.getFormattedInitiatedAt(call);\n  }, [call]);\n\n  const _render = ({item, index}: any) => {\n    const title = item['rid'];\n\n    return (\n      <React.Fragment key={index}>\n        <CometChatListItem\n          id={item.sessionId}\n          title={title}\n          SubtitleView={\n            <Text\n              style={{\n                ...theme.typography.body.regular,\n                color: theme.color.textSecondary,\n              }}>\n              {formattedInitiatedAt}\n            </Text>\n          }\n          titleStyle={_style.itemStyle.titleStyle}\n          containerStyle={_style.itemStyle.containerStyle}\n          trailingViewContainerStyle={{\n            alignSelf: 'center',\n          }}\n          TrailingView={\n            <Pressable\n              onPress={() => {\n                setFileUrl(item.getRecordingURL());\n                setReopenCount(reOpenCount + 1);\n              }}>\n              <Icon\n                icon={\n                  <PlayArrow\n                    height={24}\n                    width={24}\n                    color={theme.color.iconHighlight}></PlayArrow>\n                }\n                containerStyle={{marginLeft: theme.spacing.margin.m4}}></Icon>\n            </Pressable>\n          }\n        />\n      </React.Fragment>\n    );\n  };\n\n  return (\n    <View style={{backgroundColor: theme.color.background1}}>\n      {data.length && (\n        <FlatList\n          data={data}\n          keyExtractor={(item, index) => item.sessionId + '_' + index}\n          renderItem={_render}\n        />\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/Calls.tsx",
    "content": "import {CometChatCallLogs, useTheme, Icon} from '@cometchat/chat-uikit-react-native';\nimport {useFocusEffect, useNavigation} from '@react-navigation/native';\nimport React, {useCallback} from 'react';\nimport {View, TouchableOpacity} from 'react-native';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../navigation/types';\nimport { useConfig } from '../../config/store';\n\ntype CallNavigationProp = StackNavigationProp<RootStackParamList, 'CallLogs'>;\n\nconst Calls: React.FC = () => {\n  const [shouldHide, setShouldHide] = React.useState(false);\n  const navigation = useNavigation<CallNavigationProp>();\n\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  const theme = useTheme();\n  const onItemPress = (item: any) => {\n    navigation.navigate('CallDetails', {\n      call: item,\n    });\n  };\n  // Create TrailingView that respects visibility configuration\n  const TrailingView = useCallback((call?: any, onPress?: (call: any) => void) => {\n    if (!call || !onPress) return (<></>);\n    const receiverType = call?.getReceiverType?.();\n    const callType = call?.getType?.();\n    const isUser = receiverType === 'user';\n    const isAudio = callType === 'audio';\n\n    // Check if button should be hidden based on configuration\n    const shouldHide =\n      (isUser && isAudio && !oneOnOneVoiceCalling) ||\n      (isUser && !isAudio && !oneOnOneVideoCalling) ||\n      (!isUser && isAudio && !groupVoiceConference) ||\n      (!isUser && !isAudio && !groupVideoConference);\n\n    if (shouldHide) {\n      return <View />;\n    }\n\n    // Return call button - styling matches CometChatCallLogs default button\n    return (\n      <TouchableOpacity\n        onPress={() => onPress(call)}\n        style={{\n          marginLeft: \"auto\",\n        }}\n      >\n        <Icon\n          name={call.type === \"audio\" ? \"call\" : \"videocam\"}\n          size={24}\n          color={theme.callLogsStyles.itemStyle.callIconStyle.tintColor}\n          imageStyle={theme.callLogsStyles.itemStyle.callIconStyle}\n        />\n      </TouchableOpacity>\n    );\n  }, [oneOnOneVoiceCalling, oneOnOneVideoCalling, groupVoiceConference, groupVideoConference, theme]);\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {!shouldHide && (\n        <CometChatCallLogs \n          onItemPress={onItemPress}\n          TrailingView={TrailingView}\n        />\n      )}\n    </View>\n  );\n};\n\nexport default Calls;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-end-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 8.25q2.864 0 5.649 1.149a14.15 14.15 0 0 1 4.876 3.332q.308.31.314.724a.95.95 0 0 1-.305.714l-1.905 1.856q-.305.294-.681.329a1 1 0 0 1-.696-.2l-2.516-1.912a1.3 1.3 0 0 1-.367-.417 1.1 1.1 0 0 1-.12-.517v-2.822a15 15 0 0 0-2.113-.552A12 12 0 0 0 12 9.75q-1.107 0-2.137.184-1.029.186-2.113.553v2.82q0 .289-.12.518-.118.229-.367.417l-2.515 1.912a1 1 0 0 1-.696.2 1.1 1.1 0 0 1-.681-.329l-1.906-1.856a.95.95 0 0 1-.305-.714 1 1 0 0 1 .315-.724 14 14 0 0 1 4.868-3.332Q9.136 8.25 12 8.25'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-end.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.517 16.3 1.2 14.001a.74.74 0 0 1-.234-.551.84.84 0 0 1 .233-.567 13.8 13.8 0 0 1 4.917-3.587A14.7 14.7 0 0 1 12 8.066q3.045 0 5.88 1.23 2.833 1.23 4.92 3.587.229.255.233.567a.74.74 0 0 1-.235.551L20.5 16.3a.86.86 0 0 1-.573.24.86.86 0 0 1-.595-.158L16.5 14.254a.8.8 0 0 1-.237-.275.8.8 0 0 1-.08-.358v-3.27a11.3 11.3 0 0 0-2.077-.534 13.4 13.4 0 0 0-2.09-.167q-1.05 0-2.11.167-1.059.165-2.073.533v3.267a.8.8 0 0 1-.077.352.74.74 0 0 1-.24.281l-2.852 2.137q-.286.21-.598.183a.84.84 0 0 1-.55-.27m2.65-5.3q-.887.442-1.715 1.063-.826.62-1.602 1.32l1.317 1.35 2-1.5zm11.6-.05v2.183l2.066 1.584 1.334-1.317a12 12 0 0 0-1.619-1.367 17 17 0 0 0-1.781-1.083'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-log-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.44 21.5q-2.827 0-5.68-1.314t-5.242-3.709-3.703-5.242T2.5 5.56A1.03 1.03 0 0 1 3.55 4.5h3.262q.378 0 .668.247.289.247.368.61L8.421 8.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M12.75 4a.73.73 0 0 1-.534-.216A.73.73 0 0 1 12 3.25q0-.32.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 20.75 4zm0 3.692a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.319.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215zm0 3.692a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .319-.216.534a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-log.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.75 21.833q-2.893 0-5.863-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.457.31-.768.31-.31.773-.31h3.567q.35 0 .598.235T7.749 5l.666 3.194q.043.335-.026.616a1 1 0 0 1-.26.477L5.704 11.75q.603 1.041 1.304 1.953.702.91 1.546 1.735a17 17 0 0 0 1.818 1.626A16 16 0 0 0 12.4 18.4l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M4.92 10.283l1.9-1.916-.537-2.617H3.77q.059 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521zm-.842-15.4a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.234.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-made-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17 8.054 6.083 18.973a.72.72 0 0 1-.522.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.217-.527L15.947 7H9.751a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.535a.73.73 0 0 1 .535-.215h7.846q.383 0 .644.26.26.26.26.644v7.846q0 .319-.216.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-made.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.245 7.854 5.83 19.284a.73.73 0 0 1-.554.235.8.8 0 0 1-.559-.244.75.75 0 0 1 .002-1.106L16.133 6.75H9.766a.75.75 0 0 1-.56-.234.78.78 0 0 1-.227-.56q0-.327.227-.558a.76.76 0 0 1 .56-.231h8.267q.333 0 .565.232a.76.76 0 0 1 .23.555v8.267q0 .333-.231.565a.77.77 0 0 1-.567.23.75.75 0 0 1-.56-.23.78.78 0 0 1-.225-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-missed-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5 10.021v4.181a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V8.356q0-.387.259-.645a.88.88 0 0 1 .645-.259h5.846q.32 0 .534.215a.73.73 0 0 1 .216.535.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215H6.054l5.773 5.773a.3.3 0 0 0 .221.087.3.3 0 0 0 .221-.087l6.66-6.66a.7.7 0 0 1 .522-.22q.3.003.532.236.217.232.225.527a.7.7 0 0 1-.225.527l-6.654 6.654q-.27.27-.608.401a1.9 1.9 0 0 1-.673.131q-.337 0-.673-.13a1.75 1.75 0 0 1-.608-.402z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-missed-outgoing-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m19 10.021-5.767 5.768q-.272.27-.608.401a1.8 1.8 0 0 1-.673.131q-.336 0-.673-.13a1.75 1.75 0 0 1-.608-.402L4.017 9.135a.72.72 0 0 1-.212-.515.75.75 0 0 1 .212-.54.74.74 0 0 1 .535-.232q.302 0 .535.233l6.644 6.644a.3.3 0 0 0 .221.087q.134 0 .221-.087l5.773-5.773H13.75a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.32.216-.535a.73.73 0 0 1 .534-.215h5.846q.387 0 .645.259a.88.88 0 0 1 .259.645v5.846a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-missed-outgoing.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m19.25 9.942-6.224 6.224a1.5 1.5 0 0 1-.52.345q-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.522-.346L3.702 9.06a.78.78 0 0 1-.237-.552.78.78 0 0 1 .222-.565.82.82 0 0 1 1.151.003l7.08 7.087L18.2 8.75H13.93a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.562q0-.33.231-.56a.77.77 0 0 1 .565-.23h6.116q.324 0 .556.233.231.232.232.555v6.234q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.23-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-missed.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.751 9.942v4.246q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.229-.565V7.954a.76.76 0 0 1 .231-.555.77.77 0 0 1 .565-.232h6.117q.323 0 .555.233.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.555.23H5.8l6.284 6.282 7.11-7.11a.72.72 0 0 1 .557-.227.86.86 0 0 1 .568.25.8.8 0 0 1 .219.562.77.77 0 0 1-.238.55l-7.107 7.107q-.238.24-.52.346-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.521-.345z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-received-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.404 18.5a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V9.75q0-.318.216-.534A.73.73 0 0 1 6.25 9q.32 0 .535.216A.73.73 0 0 1 7 9.75v6.196L17.92 5.027a.72.72 0 0 1 .521-.212.7.7 0 0 1 .532.212q.217.217.217.527a.72.72 0 0 1-.217.527L8.053 17h6.197q.319 0 .534.215a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call-received.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.967 18.833a.76.76 0 0 1-.56-.231.78.78 0 0 1-.227-.565V9.771q0-.325.227-.557a.76.76 0 0 1 .563-.23q.334 0 .564.23.23.232.229.557v6.366L18.18 4.717a.7.7 0 0 1 .545-.236.8.8 0 0 1 .559.24.76.76 0 0 1 .241.554q0 .312-.241.546L7.867 17.25h6.367q.333 0 .564.232a.76.76 0 0 1 .232.558q0 .334-.232.564a.77.77 0 0 1-.564.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/cancel-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 13.054 3.073 3.073q.208.208.522.213a.7.7 0 0 0 .532-.213.72.72 0 0 0 .217-.527.72.72 0 0 0-.217-.527L13.054 12l3.073-3.073a.73.73 0 0 0 .213-.522.7.7 0 0 0-.213-.532.72.72 0 0 0-.527-.217.72.72 0 0 0-.527.217L12 10.946 8.927 7.873a.73.73 0 0 0-.522-.213.7.7 0 0 0-.532.213.72.72 0 0 0-.217.527q0 .31.217.527L10.946 12l-3.073 3.073a.73.73 0 0 0-.213.522.7.7 0 0 0 .213.532q.217.217.527.217a.72.72 0 0 0 .527-.217zm.002 8.446a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/index.tsx",
    "content": "export {default as Call} from './call';\nexport {default as CallEnd} from './call-end';\nexport {default as CallEndFill} from './call-end-fill';\nexport {default as CallFill} from './call-fill';\nexport {default as CallLog} from './call-log';\nexport {default as CallLogFill} from './call-log-fill';\nexport {default as CallMade} from './call-made';\nexport {default as CallMadeFill} from './call-made-fill';\nexport {default as CallMissed} from './call-missed';\nexport {default as CallMissedFill} from './call-missed-fill';\nexport {default as CallMissedOutgoing} from './call-missed-outgoing';\nexport {default as CallMissedOutgoingFill} from './call-missed-outgoing-fill';\nexport {default as CallReceived} from './call-received';\nexport {default as CallReceivedFill} from './call-received-fill';\nexport {default as CancelFill} from './cancel-fill';\nexport {default as PlayArrow} from './play-arrow';\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/calls/icons/play-arrow.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M8.117 17.313V6.578a.73.73 0 0 1 .24-.57.8.8 0 0 1 .56-.216q.096 0 .206.022a.7.7 0 0 1 .21.08l8.438 5.393q.18.133.273.298t.094.365a.7.7 0 0 1-.094.365.9.9 0 0 1-.273.29l-8.437 5.393a.6.6 0 0 1-.212.087 1 1 0 0 1-.201.023.81.81 0 0 1-.564-.216.74.74 0 0 1-.24-.579m1.583-1.43 6.184-3.933L9.7 8.017z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/helper/GroupListeners.ts",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {CometChatUIKit} from '@cometchat/chat-uikit-react-native';\n\nexport const listners = {\n  addListener: {\n    groupListener: ({groupListenerId, handleGroupListener}: any) =>\n      CometChat.addGroupListener(\n        groupListenerId,\n        new CometChat.GroupListener({\n          onGroupMemberKicked: (\n            message: any,\n            kickedUser: any,\n            kickedBy: any,\n            kickedFrom: any,\n          ) => {\n            handleGroupListener(kickedFrom);\n          },\n          onGroupMemberBanned: (\n            message: any,\n            bannedUser: any,\n            bannedBy: any,\n            bannedFrom: any,\n          ) => {\n            handleGroupListener(bannedFrom);\n          },\n          onMemberAddedToGroup: (\n            message: any,\n            userAdded: any,\n            userAddedBy: any,\n            userAddedIn: any,\n          ) => {\n            handleGroupListener(userAddedIn);\n          },\n          onGroupMemberLeft: (message: any, leavingUser: any, group: any) => {\n            handleGroupListener(group);\n          },\n          onGroupMemberScopeChanged: (\n            message: any,\n            changedUser: CometChat.User,\n            newScope: any,\n            oldScope: any,\n            changedGroup: CometChat.Group,\n          ) => {\n            console.log('changedGroup: ', message, changedGroup);\n            if (changedUser.getUid() == CometChatUIKit.loggedInUser!.getUid()) {\n              changedGroup.setScope(newScope);\n              handleGroupListener(changedGroup);\n            }\n          },\n        }),\n      ),\n  },\n  removeListner: {\n    removeUserListener: ({userStatusListenerId}: any) =>\n      CometChat.removeUserListener(userStatusListenerId),\n\n    removeGroupListener: ({groupListenerId}: any) =>\n      CometChat.removeGroupListener(groupListenerId),\n  },\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/AddMember.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  NativeModules,\n  Platform,\n  KeyboardAvoidingView,\n  BackHandler,\n} from 'react-native';\nimport {\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  CometChatUsers,\n  CometChatUsersActionsInterface,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport {\n  useRoute,\n  useNavigation,\n  RouteProp,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport { styles } from './AddMemberStyles';\nimport { commonVars } from '@cometchat/chat-uikit-react-native/src/shared/base/vars';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { useConfig } from '../../../config/store';\nconst { CommonUtil } = NativeModules;\n\nconst AddMember: React.FC = () => {\n  const route = useRoute<RouteProp<RootStackParamList, 'AddMember'>>();\n  const navigation = useNavigation();\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const userRef = useRef<CometChatUsersActionsInterface>(null);\n  const [selectedUsers, setSelectedUsers] = useState<CometChat.User[]>([]);\n  const [errorToastVisible, setErrorToastVisible] = useState(false);\n  const [errorToastMessage, setErrorToastMessage] = useState('');\n  const errorTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const [kbOffset, setKbOffset] = React.useState(900);\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n    );\n\n  useEffect(() => {\n    return () => {\n      if (errorTimeoutRef.current) {\n        clearTimeout(errorTimeoutRef.current);\n      }\n    };\n  }, []);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  useLayoutEffect(() => {\n    if (Platform.OS === 'ios') {\n      if (Number.isInteger(commonVars.safeAreaInsets.top)) {\n        setKbOffset(commonVars.safeAreaInsets.top ?? 0);\n        return;\n      }\n\n      CommonUtil.getSafeAreaInsets().then((res: any) => {\n        if (Number.isInteger(res.top)) {\n          commonVars.safeAreaInsets.top = res.top;\n          commonVars.safeAreaInsets.bottom = res.bottom;\n          setKbOffset(res.top);\n        }\n      });\n    }\n  }, []);\n\n  const addMembersToGroup = useCallback(\n    async (users: Array<CometChat.User>) => {\n      try {\n        const membersList = users.map((item: CometChat.User) => {\n          const groupMember = new CometChat.GroupMember(\n            item.getUid(),\n            CometChat.GROUP_MEMBER_SCOPE.PARTICIPANT,\n          );\n          groupMember.setName(item.getName());\n          return groupMember;\n        });\n\n        const guid = group.getGuid();\n        const response = await CometChat.addMembersToGroup(\n          guid,\n          membersList,\n          [],\n        );\n\n        const addedUIDs = Object.entries(response)\n          .filter(([_, status]) => status === 'success')\n          .map(([uid]) => uid);\n\n        const addedMembers = membersList.filter(member =>\n          addedUIDs.includes(member.getUid()),\n        );\n\n        if (addedUIDs.length > 0) {\n          navigation.goBack();\n        } else {\n          setErrorToastMessage('Error, Unable to add members');\n          setErrorToastVisible(true);\n          errorTimeoutRef.current = setTimeout(() => {\n            setErrorToastVisible(false);\n          }, 3000);\n        }\n\n        // If all succeeded, emit individual events for each member\n        if (addedMembers.length > 0) {\n          const groupInfo  = await CometChat.getGroup(group.getGuid());\n          group.setMembersCount(groupInfo.getMembersCount());\n          // Create separate action for each added member\n          addedMembers.forEach(member => {\n            const action: CometChat.Action = new CometChat.Action(\n              guid,\n              CometChatUiKitConstants.MessageTypeConstants.groupMember,\n              CometChat.RECEIVER_TYPE.GROUP,\n              CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n            );\n            action.setConversationId(guid);\n            action.setActionBy(CometChatUIKit.loggedInUser!);\n            action.setActionFor(group);\n            action.setSender(CometChatUIKit.loggedInUser!);\n            // Initialize data to prevent crash when SDK accesses getData().metadata during render\n            action.setData({ metadata: {} });\n\n            // Emit individual event for each member added\n            CometChatUIEventHandler.emitGroupEvent(\n              CometChatGroupsEvents.ccGroupMemberAdded,\n              {\n                addedBy: CometChatUIKit.loggedInUser,\n                message: action,\n                usersAdded: [member],\n                userAddedIn: group,\n              },\n            );\n          });\n        }\n      } catch (error) {\n        console.error('Something went wrong', error);\n        setErrorToastMessage('Error, Unable to add members');\n        setErrorToastVisible(true);\n        errorTimeoutRef.current = setTimeout(() => {\n          setErrorToastVisible(false);\n        }, 2000);\n      }\n    },\n    [group, navigation],\n  );\n\n  const handleUserSelection = useCallback((users: CometChat.User[]) => {\n    setSelectedUsers(users);\n  }, []);\n\n  return (\n    <KeyboardAvoidingView\n      behavior=\"padding\"\n      enabled={Platform.OS === 'ios' ? true : false}\n      keyboardVerticalOffset={kbOffset}\n      style={{ flex: 1, backgroundColor: theme.color.background1 }}\n    >\n      {/* Header */}\n      <View style={{ flex: 1 }}>\n        <View\n          style={[\n            styles.addMemberContainer,\n            { borderBottomColor: theme.color.borderLight },\n          ]}\n        >\n          <TouchableOpacity\n            style={styles.iconContainer}\n            onPress={() => navigation.goBack()}\n          >\n            <Icon\n              icon={\n                <ArrowBack\n                  color={theme.color.iconPrimary}\n                  height={24}\n                  width={24}\n                />\n              }\n            />\n          </TouchableOpacity>\n          <Text\n            style={[\n              theme.typography.heading1.bold,\n              styles.addMemberText,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            {t('ADD_MEMBERS')}\n          </Text>\n        </View>\n\n        {/* Users List */}\n        <CometChatUsers\n          hideHeader={true}\n          ref={userRef}\n          usersRequestBuilder={new CometChat.UsersRequestBuilder()\n            .setLimit(30)\n            .hideBlockedUsers(false)\n            .setRoles([])\n            .friendsOnly(false)\n            .setStatus('')\n            .setTags([])\n            .sortBy('name')\n            .setUIDs([])}\n          selectionMode=\"multiple\"\n          onSelection={handleUserSelection}\n          showBackButton={true}\n          onBack={() => navigation.goBack()}\n          usersStatusVisibility={userAndFriendsPresence}\n        />\n\n        {/* Add Members Button */}\n        <TouchableOpacity\n          onPress={() => addMembersToGroup(selectedUsers)}\n          style={styles.addMembersButton}\n        >\n          <View\n            style={[\n              styles.addMembersButtonContainer,\n              { backgroundColor: theme.color.primaryButtonBackground },\n            ]}\n          >\n            <Text\n              style={[\n                theme.typography.heading4.medium,\n                { color: theme.color.primaryButtonText, alignSelf: 'center' },\n              ]}\n            >\n              {t('ADD_MEMBERS')}\n            </Text>\n          </View>\n        </TouchableOpacity>\n\n        {/* Error Toast */}\n        {errorToastVisible && (\n          <View\n            style={[\n              styles.toastContainer,\n              { backgroundColor: theme.color.error },\n            ]}\n          >\n            <Text style={styles.toastTextStyle}>{errorToastMessage}</Text>\n          </View>\n        )}\n      </View>\n    </KeyboardAvoidingView>\n  );\n};\n\nexport default AddMember;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/AddMemberStyles.tsx",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n    toastTextStyle: {\n      color: '#fff',\n      fontSize: 16,\n    },\n    toastContainer: {\n      position: 'absolute',\n      bottom: 20,\n      left: 20,\n      right: 20,\n      paddingVertical: 10,\n      borderRadius: 5,\n      alignItems: 'center',\n    },\n    addMemberContainer: {\n      paddingTop: 15,\n      paddingLeft: 10,\n      flexDirection: 'row',\n      paddingBottom: 15,\n      borderBottomWidth: 1,\n    },\n    addMemberText: {\n      paddingLeft: 10,\n    },\n    iconContainer: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    addMembersButton: {\n      alignContent: 'center',\n      justifyContent: 'center',\n      paddingVertical: 1,\n      height: 50,\n      width: '100%',\n      alignSelf: 'center',\n    },\n    addMembersButtonContainer: {\n      marginHorizontal: 20,\n      alignSelf: 'center',\n      justifyContent: 'center',\n      borderRadius: 6,\n      height: '75%',\n      width: '95%',\n    },\n  });\n  "
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/BannedMember.tsx",
    "content": "import React, { useCallback, useRef, useState } from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  Modal,\n  TouchableWithoutFeedback,\n  BackHandler,\n} from 'react-native';\nimport {\n  CometChatConfirmDialog,\n  CometChatList,\n  CometChatListActionsInterface,\n} from '@cometchat/chat-uikit-react-native/src/shared';\nimport { Skeleton } from '@cometchat/chat-uikit-react-native/src/CometChatUsers/Skeleton';\nimport {\n  Icon,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { ErrorEmptyView } from '@cometchat/chat-uikit-react-native/src/shared/views/ErrorEmptyView/ErrorEmptyView';\nimport { RouteProp, useFocusEffect } from '@react-navigation/native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  getBannedMemberStyleLight,\n  styles,\n  getBannedMemberStyleDark,\n} from './BannedMemberStyles';\nimport UserEmptyIcon from '../../../assets/icons/UserEmptyIcon';\nimport Close from '../../../assets/icons/Close';\nimport Block from '../../../assets/icons/Block';\n\ntype BannedMembersRouteProp = {\n  route: RouteProp<RootStackParamList, 'BannedMember'>;\n  navigation: StackNavigationProp<RootStackParamList, 'BannedMember'>;\n};\n\nconst BannedMember: React.FC<BannedMembersRouteProp> = ({\n  route,\n  navigation,\n}) => {\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const bannedListRef = useRef<CometChatListActionsInterface>(null);\n\n  const [isModalVisible, setModalVisible] = useState(false);\n  const [selectedUser, setSelectedUser] = useState<CometChat.User | null>(null);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  /**\n   * --- Callbacks / Handlers ---\n   */\n\n  const openUnbanModal = (user: CometChat.User) => {\n    setSelectedUser(user);\n    setModalVisible(true);\n  };\n\n  const closeUnbanModal = () => {\n    setModalVisible(false);\n    setSelectedUser(null);\n  };\n\n  const handleUnbanUser = async () => {\n    if (!group || !selectedUser) return;\n    try {\n      const guid = group.getGuid();\n      const uid = selectedUser.getUid();\n\n      // Unban the user\n      await CometChat.unbanGroupMember(guid, uid);\n\n      // Create and dispatch an Action message for the unban event\n      const actionMessage = new CometChat.Action(\n        guid,\n        CometChatUiKitConstants.MessageTypeConstants.groupMember,\n        CometChat.RECEIVER_TYPE.GROUP,\n        CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n      );\n      actionMessage.setConversationId(guid);\n      actionMessage.setActionFor(group);\n      actionMessage.setActionOn(selectedUser);\n      actionMessage.setActionBy(CometChatUIKit.loggedInUser!);\n      actionMessage.setSender(CometChatUIKit.loggedInUser!);\n      // Initialize data to prevent crash when SDK accesses getData().metadata during render\n      actionMessage.setData({ metadata: {} });\n      actionMessage.setMessage(\n        `${CometChatUIKit.loggedInUser?.getName()} ${t(\n          'UNBANNED',\n        )} ${selectedUser.getName()}`,\n      );\n      CometChatUIEventHandler.emitGroupEvent(\n        CometChatGroupsEvents.ccGroupMemberUnBanned,\n        {\n          unbannedBy: CometChatUIKit.loggedInUser,\n          userUnbanned: selectedUser,\n          group,\n          message: actionMessage,\n        },\n      );\n\n      // Remove from the banned list\n      bannedListRef.current?.removeItemFromList(uid);\n\n      // Close modal\n      closeUnbanModal();\n    } catch (error) {\n      console.error('Error unbanning user:', error);\n    }\n  };\n\n  /**\n   * --- Render Helpers ---\n   */\n\n  const renderEmptyView = useCallback(() => {\n    return (\n      <View style={styles.flexContainer}>\n        <ErrorEmptyView\n          title={t('NO_BANNED_MEMBERS_FOUND')}\n          Icon={\n            <Icon\n              icon={\n                <UserEmptyIcon\n                  color={theme.color.neutral300}\n                  height={100}\n                  width={100}\n                />\n              }\n              size={theme.spacing.spacing.s20}\n              containerStyle={{ marginBottom: theme.spacing.spacing.s5 }}\n            />\n          }\n          containerStyle={styles.emptyViewContainer}\n          titleStyle={[\n            theme.userStyles.emptyStateStyle.titleStyle,\n            { color: theme.color.textPrimary },\n          ]}\n        />\n      </View>\n    );\n  }, [theme]);\n\n  const renderLoadingView = () => <Skeleton />;\n\n  const renderTailView = (user: CometChat.User) => (\n    <TouchableOpacity onPress={() => openUnbanModal(user)}>\n      <Icon\n        icon={<Close height={24} width={24} color={theme.color.iconTertiary} />}\n      />\n    </TouchableOpacity>\n  );\n\n  const renderUnbanModal = () => {\n    if (!selectedUser) return null;\n\n    return (\n      <CometChatConfirmDialog\n        titleText={`${t('UNBAN')} ${selectedUser.getName()}`}\n        icon={<Block color={theme.color.error} height={40} width={40} />}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('UNBAN')}\n        messageText={t('UNBAN_SURE') + ' ' + selectedUser.getName()}\n        isOpen={true}\n        onCancel={closeUnbanModal}\n        onConfirm={handleUnbanUser}\n      />\n    );\n  };\n\n  /**\n   * --- Main JSX ---\n   */\n  return (\n    <>\n      <View\n        style={[\n          styles.flexContainer,\n          { backgroundColor: theme.color.background1 },\n        ]}\n      >\n        <CometChatList\n          title={t('BANNED_MEMBERS')}\n          hideStickyHeader={true}\n          ref={bannedListRef}\n          onBack={() => navigation.goBack()}\n          listItemKey=\"uid\"\n          hideBackButton={false}\n          LoadingView={renderLoadingView}\n          EmptyView={renderEmptyView}\n          TrailingView={renderTailView}\n          listStyle={\n            theme.mode === 'light'\n              ? getBannedMemberStyleLight(theme)\n              : getBannedMemberStyleDark(theme)\n          }\n          requestBuilder={new CometChat.BannedMembersRequestBuilder(\n            group.getGuid(),\n          ).setLimit(30)}\n        />\n      </View>\n      {renderUnbanModal()}\n    </>\n  );\n};\n\nexport default BannedMember;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/BannedMemberStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\nimport {\n  CometChatListStylesInterface,\n  CometChatTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {deepMerge} from '@cometchat/chat-uikit-react-native/src/shared/helper/helperFunctions';\nimport {DeepPartial} from '@cometchat/chat-uikit-react-native/src/shared/helper/types';\nimport {ColorValue, ViewStyle} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  flexContainer: {\n    flex: 1,\n  },\n  emptyViewContainer: {\n    flex: 1,\n    justifyContent: 'center',\n    alignItems: 'center',\n    paddingHorizontal: '10%',\n  },\n\n  /* Modal Styles */\n  modalOverlay: {\n    flex: 1,\n    backgroundColor: 'rgba(0, 0, 0, 0.5)',\n  },\n  modalContainer: {\n    position: 'absolute',\n    top: '30%',\n    left: '2%',\n    right: '2%',\n    borderRadius: 10,\n    padding: 20,\n    elevation: 5,\n  },\n  modalIconContainer: {\n    alignSelf: 'center',\n    marginBottom: 10,\n    width: 64,\n    height: 64,\n    borderRadius: 32,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  modalContentContainer: {\n    alignItems: 'center',\n  },\n  modalTitle: {\n    marginBottom: 15,\n  },\n  modalDesc: {\n    textAlign: 'center',\n    marginBottom: 20,\n  },\n  buttonContainer: {\n    flexDirection: 'row',\n  },\n  cancelButton: {\n    flex: 1,\n    paddingVertical: 10,\n    marginHorizontal: 5,\n    borderRadius: 5,\n    borderWidth: 1,\n    alignItems: 'center',\n  },\n  unbanButton: {\n    flex: 1,\n    paddingVertical: 10,\n    marginHorizontal: 5,\n    borderRadius: 5,\n    alignItems: 'center',\n  },\n});\n\nexport type BannedMemberStyle = CometChatListStylesInterface & {\n  skeletonStyle: {\n    backgroundColor: ColorValue;\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n  };\n  headerContainerStyle: ViewStyle;\n};\n\nexport const getBannedMemberStyleLight = (\n  theme: CometChatTheme,\n): DeepPartial<BannedMemberStyle> => {\n  const {color, spacing, typography} = theme;\n  return {\n    headerContainerStyle: {\n      alignItems: 'flex-start',\n      justifyContent: 'center',\n      width: '100%',\n      borderRadius: 0,\n      paddingHorizontal: 0,\n    },\n    titleSeparatorStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n      flexDirection: 'row',\n      justifyContent: 'space-between',\n      alignItems: 'center',\n      width: '100%',\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      flex: 1,\n    },\n    itemStyle: {\n      containerStyle: {\n        flexDirection: 'row',\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        gap: spacing.spacing.s3,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.body.regular,\n      },\n      statusIndicatorStyle: {},\n      avatarStyle: {\n        containerStyle: {},\n        textStyle: {},\n        imageStyle: {},\n      },\n      headViewContainerStyle: {},\n      titleSubtitleContainerStyle: {\n        alignSelf: 'center',\n      },\n      trailingViewContainerStyle: {\n        alignSelf: 'center',\n      },\n    },\n    confirmSelectionStyle: {},\n    selectionCancelStyle: {},\n    loadingIconTint: color.primary,\n    sectionHeaderTextStyle: {\n      marginHorizontal: spacing.spacing.s5,\n      color: color.primary,\n      ...typography.heading4.medium,\n    },\n    onlineStatusColor: color.success,\n    titleViewStyle: {\n      paddingVertical: spacing.spacing.s3,\n      paddingLeft: spacing.spacing.s3,\n      margin: spacing.spacing.s0,\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      ...typography.heading1.bold,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    searchStyle: {\n      textStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.regular,\n        textAlignVertical: 'center',\n        paddingVertical: 0,\n        height: spacing.spacing.s7,\n      },\n      containerStyle: {\n        backgroundColor: color.background3,\n        paddingVertical: spacing.spacing.s3,\n        marginTop: spacing.spacing.s3,\n        width: '95%',\n        gap: spacing.spacing.s1,\n        alignContent: 'space-around',\n        alignSelf: 'center',\n        flexDirection: 'row',\n        alignItems: 'center',\n      },\n      icon: undefined,\n      iconStyle: {\n        tintColor: color.iconSecondary,\n      },\n      placehodlerTextStyle: {\n        color: color.textTertiary,\n      },\n    },\n    emptyStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: 'center' as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: 'center',\n        display: 'none',\n        alignItems: 'center',\n        padding: spacing.padding.p3,\n      },\n    },\n    errorStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: 'center' as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: 'center',\n        alignItems: 'center',\n        padding: spacing.padding.p3,\n      },\n    },\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: ['#E8E8E8', '#F5F5F5'] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  };\n};\n\nexport const getBannedMemberStyleDark = (\n  theme: CometChatTheme,\n): DeepPartial<BannedMemberStyle> => {\n  const {color, spacing, typography} = theme;\n  return deepMerge(getBannedMemberStyleLight(theme), {\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: ['#383838', '#272727'] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  });\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/Conversations.tsx",
    "content": "import {CometChat} from '@cometchat/chat-sdk-react-native';\nimport React, {useCallback, useContext, useRef, useState} from 'react';\nimport {TouchableOpacity, View, Platform} from 'react-native';\nimport PushNotificationIOS from '@react-native-community/push-notification-ios';\nimport notifee from '@notifee/react-native';\nimport {\n  CometChatAvatar,\n  CometChatConversations,\n  CometChatUIKit,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {AuthContext} from '../../../navigation/AuthContext';\nimport {\n  useFocusEffect,\n  useNavigation,\n  CommonActions,\n} from '@react-navigation/native';\nimport {TooltipMenu} from '../../../utils/TooltipMenu';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../../navigation/types';\nimport {unregisterPushToken} from '../../../utils/PushNotification';\nimport AccountCircle from '../../../assets/icons/AccountCircle';\nimport AddComment from '../../../assets/icons/AddComment';\nimport InfoIcon from '../../../assets/icons/InfoIcon';\nimport Logout from '../../../assets/icons/Logout';\nimport {navigate, navigationRef} from '../../../navigation/NavigationService';\nimport {AppConstants, SCREEN_CONSTANTS} from '../../../utils/AppConstants';\nimport Builder from '../../../assets/icons/Builder';\nimport { useConfig, useConfigStore } from '../../../config/store'; // adjust import if needed\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport Reset from '../../../assets/icons/Reset';\nimport AiIcon from '../../../assets/icons/AiIcon';\n\ntype ChatNavigationProp = StackNavigationProp<\n  RootStackParamList,\n  'Conversation'\n>;\n\nconst Conversations: React.FC<{}> = ({}) => {\n  const theme = useTheme();\n  const {setIsLoggedIn: setLogout} = useContext(AuthContext);\n  const [isLoggingOut, setIsLoggingOut] = useState(false);\n  const tooltipPositon = React.useRef({pageX: 0, pageY: 0});\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const selectedConversation = useRef<CometChat.Conversation | null>(null);\n  const navigation = useNavigation<ChatNavigationProp>();\n  const avatarContainerRef = useRef<View>(null);\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser,\n  ).current;\n  const { t } = useCometChatTranslation();\n\n  const [isConfigUpdated, setIsConfigUpdated] = useState(false);\n  const messageDeliveryAndReadReceipts = useConfig(\n        (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n\n  useFocusEffect(\n    useCallback(() => {\n      // Check config updated flag\n      AsyncStorage.getItem('@config_updated').then(val => {\n        setIsConfigUpdated(val === 'true');\n      });\n      return () => {\n        setTooltipVisible(false);\n      };\n    }, []),\n  );\n\n  const handleResetConfig = async () => {\n    useConfigStore.getState().resetConfig();\n    await AsyncStorage.removeItem('@config_updated');\n    setIsConfigUpdated(false);\n  };\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n    );\n\n  const openMessagesFor = (item: CometChat.Conversation) => {\n    // Determine if it's a user or group conversation\n    const isUser = item.getConversationType() === 'user';\n    const isGroup = item.getConversationType() === 'group';\n\n    // Navigate to Messages with appropriate params\n    navigation.navigate('Messages', {\n      user: isUser ? (item.getConversationWith() as CometChat.User) : undefined,\n      group: isGroup\n        ? (item.getConversationWith() as CometChat.Group)\n        : undefined,\n    });\n  };\n\n  const _conversationsConfig = {\n    onItemPress: openMessagesFor,\n    onError: (err: any) => {\n      console.log('ERROR IN CONVO: ', err);\n    },\n  };\n\n  const handleAvatarPress = () => {\n    try {\n      if (avatarContainerRef.current) {\n        avatarContainerRef.current.measureInWindow((x, y, height) => {\n          // Set tooltip position 10px below the avatar\n          tooltipPositon.current = {pageX: x, pageY: y + height};\n        });\n        selectedConversation.current = null;\n        setTooltipVisible(true);\n      }\n    } catch (error) {\n      console.error('Error while handling avatar press:', error);\n    }\n  };\n\n  const handleLogout = async () => {\n    if (isLoggingOut) return;\n    setIsLoggingOut(true);\n\n    // Step 1: Unregister push token\n    try {\n      await unregisterPushToken();\n    } catch (error) {\n      console.error('Failed to unregister push token:', error);\n      setIsLoggingOut(false);\n      return;\n    }\n\n    // Step 2: Logout from CometChat\n    try {\n      await CometChat.logout();\n    } catch (error) {\n      console.error('CometChat logout failed:', error);\n      setIsLoggingOut(false);\n      return; // Exit if CometChat logout fails\n    }\n\n    // Step 3: Clear badge count on logout\n    try {\n      if (Platform.OS === 'ios') {\n        PushNotificationIOS.setApplicationIconBadgeNumber(0);\n      } else if (Platform.OS === 'android') {\n        await notifee.cancelAllNotifications();\n      }\n    } catch (error) {\n      console.error('Error :', error);\n    }\n\n    // If all operations succeed, navigate to the LoginScreen\n    setIsLoggingOut(false);\n    setLogout(false);\n    navigationRef.dispatch(\n      CommonActions.reset({\n        index: 0,\n        routes: [{ name: 'SampleUser' }],\n      }),\n    );\n  };\n\n  const NewConversation = () => {\n    return (\n      <View ref={avatarContainerRef}>\n        <TouchableOpacity\n          onPress={() => {\n            handleAvatarPress();\n          }}>\n          <CometChatAvatar\n            style={{\n              containerStyle: {\n                height: 40,\n                width: 40,\n                justifyContent: 'center',\n                alignItems: 'center',\n                overflow: 'hidden',\n              },\n              textStyle: {\n                fontSize: 22,\n                lineHeight: 28,\n                textAlign: 'center',\n              },\n            }}\n            image={\n              loggedInUser?.getAvatar()\n                ? {uri: loggedInUser?.getAvatar()}\n                : undefined\n            }\n            name={loggedInUser?.getName() ?? ''}\n          />\n        </TouchableOpacity>\n      </View>\n    );\n  };\n\n  return (\n    <View style={{flex: 1}}>\n      <View style={{flex: 1}}>\n        <CometChatConversations\n          {..._conversationsConfig}\n          AppBarOptions={NewConversation}\n          selectionMode=\"none\"\n          showSearchBar={true}\n          onSearchBarClicked={() => {\n            navigate(SCREEN_CONSTANTS.SEARCH_MESSAGES);\n          }}\n          usersStatusVisibility={userAndFriendsPresence}\n          receiptsVisibility={messageDeliveryAndReadReceipts}\n        />\n      </View>\n\n      <View\n        style={{\n          position: 'absolute',\n          top: tooltipPositon.current.pageY,\n          left: tooltipPositon.current.pageX,\n          zIndex: 9999,\n        }}>\n        <TooltipMenu\n          visible={tooltipVisible}\n          onClose={() => {\n            setTooltipVisible(false);\n          }}\n          onDismiss={() => {\n            setTooltipVisible(false);\n          }}\n          event={{\n            nativeEvent: tooltipPositon.current,\n          }}\n          menuItems={[\n            {\n              text: t('CREATE_CONVERSATION'),\n              onPress: () => {\n                navigation.navigate('CreateConversation');\n              },\n              icon: (\n                <AddComment\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AddComment>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: t('AI_ASSISTANTS'),\n              onPress: () => {\n                navigation.navigate(SCREEN_CONSTANTS.AI_AGENTS);\n              },\n              icon: (\n                <AiIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AiIcon>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: loggedInUser?.getName() || 'User',\n              onPress: () => {\n                setTooltipVisible(false);\n              },\n              icon: (\n                <AccountCircle\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></AccountCircle>\n              ),\n              textColor: theme.color.textPrimary,\n              iconColor: theme.color.textPrimary,\n            },\n            {\n              text: t('LOGOUT'),\n              onPress: () => {\n                handleLogout();\n              },\n              icon: (\n                <Logout\n                  height={24}\n                  width={24}\n                  color={theme.color.error}></Logout>\n              ),\n              textColor: theme.color.error,\n              iconColor: theme.color.error,\n            },\n            {\n              text: AppConstants.versionNumber,\n              onPress: () => {},\n              icon: (\n                <InfoIcon\n                  height={24}\n                  width={24}\n                  color={theme.color.textPrimary}></InfoIcon>\n              ),\n            },\n            isConfigUpdated\n              ? {\n                text: 'Reset to Default',\n                onPress: handleResetConfig,\n                icon: (\n                  <Reset\n                    height={24}\n                    width={24}\n                    color={theme.color.textPrimary}\n                  />\n                ),\n                textColor: theme.color.textPrimary,\n                iconColor: theme.color.textPrimary,\n              }\n              : {\n                text: 'Builder Live Preview',\n                onPress: () => {\n                  navigation.navigate(SCREEN_CONSTANTS.QR_SCREEN);\n                },\n                icon: (\n                  <Builder\n                    height={24}\n                    width={24}\n                    color={theme.color.textPrimary}\n                  />\n                ),\n                textColor: theme.color.textPrimary,\n                iconColor: theme.color.textPrimary,\n              },\n          ]}\n        />\n      </View>\n    </View>\n  );\n};\n\nexport default Conversations;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/CreateConversation.tsx",
    "content": "import React, { useState } from 'react';\nimport { View, Text, TouchableOpacity, StyleSheet } from 'react-native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RouteProp } from '@react-navigation/native';\nimport {\n  useTheme,\n  CometChatUsers,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport Groups from '../../groups/Groups';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\n\n// Define prop types for the component using React Navigation's types\ntype Props = {\n  route: RouteProp<RootStackParamList, 'CreateConversation'>;\n  navigation: StackNavigationProp<RootStackParamList, 'CreateConversation'>;\n};\n\nconst CreateConversation: React.FC<Props> = ({ route, navigation }) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const {\n    background1,\n    background3,\n    iconPrimary,\n    textPrimary,\n    textSecondary,\n    primary,\n  } = theme.color;\n  const { heading1 } = theme.typography;\n  const [selectedTab, setSelectedTab] = useState<'Users' | 'Groups'>('Users');\n\n  return (\n    <View style={[styles.container, { backgroundColor: background1 }]}>\n      {/* Header */}\n      <View style={styles.header}>\n        <TouchableOpacity style={styles.rowCenter} onPress={navigation.goBack}>\n          <Icon\n            icon={<ArrowBack color={iconPrimary} height={24} width={24} />}\n          />\n        </TouchableOpacity>\n        <Text\n          style={[heading1.bold, styles.headerText, { color: textPrimary }]}\n        >\n          {t('NEW_CHAT')}\n        </Text>\n      </View>\n\n      {/* Tab Bar */}\n      <View style={[styles.tabContainer, { backgroundColor: background3 }]}>\n        {['Users', 'Groups'].map(tab => (\n          <TouchableOpacity\n            key={tab}\n            style={[\n              styles.tabButton,\n              selectedTab === tab && styles.activeTab,\n              selectedTab === tab && { backgroundColor: background1 },\n            ]}\n            onPress={() => setSelectedTab(tab as 'Users' | 'Groups')}\n          >\n            <Text\n              style={[\n                styles.tabText,\n                { color: selectedTab === tab ? primary : textSecondary },\n              ]}\n            >\n              {t(tab.toUpperCase())}\n            </Text>\n          </TouchableOpacity>\n        ))}\n      </View>\n\n      {/* Content */}\n      <View style={styles.content}>\n        {selectedTab === 'Users' ? (\n          <CometChatUsers\n            usersRequestBuilder={new CometChat.UsersRequestBuilder()\n              .setLimit(30)\n              .hideBlockedUsers(false)\n              .setRoles([])\n              .friendsOnly(false)\n              .setStatus('')\n              .setTags([])\n              .sortBy('name')\n              .setUIDs([])}\n            hideHeader\n            onItemPress={(user: CometChat.User) =>\n              navigation.navigate('Messages', { user })\n            }\n          />\n        ) : (\n          <Groups hideHeader />\n        )}\n      </View>\n    </View>\n  );\n};\n\nexport default CreateConversation;\n\n// Style definitions for the component\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  header: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  rowCenter: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  headerText: {\n    paddingLeft: 5,\n  },\n  tabContainer: {\n    marginTop: 20,\n    flexDirection: 'row',\n    marginHorizontal: 20,\n    borderRadius: 30,\n    padding: 5,\n  },\n  tabButton: {\n    flex: 1,\n    paddingVertical: 10,\n    alignItems: 'center',\n    borderRadius: 30,\n  },\n  activeTab: {\n    borderRadius: 30,\n  },\n  tabText: {\n    fontSize: 16,\n    fontWeight: 'bold',\n  },\n  content: {\n    flex: 1,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/GroupInfo.tsx",
    "content": "import React, { useEffect, useRef, useState } from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  useWindowDimensions,\n  BackHandler,\n} from 'react-native';\nimport { RouteProp, useFocusEffect } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  CometChatAvatar,\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatConfirmDialog,\n  useTheme,\n  CometChatConversationEvents,\n  CometChatUIEvents,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatUIKit,\n  CometChatUiKitConstants,\n} from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { listners } from '../helper/GroupListeners';\nimport { styles } from './GroupInfoStyles';\nimport { leaveGroup } from '../../../utils/helper';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport Group from '../../../assets/icons/Group';\nimport PersonAdd from '../../../assets/icons/PersonAdd';\nimport PersonOff from '../../../assets/icons/PersonOff';\nimport Block from '../../../assets/icons/Block';\nimport Delete from '../../../assets/icons/Delete';\nimport { useConfig } from '../../../config/store';\n\ntype GroupInfoProps = {\n  route: RouteProp<RootStackParamList, 'GroupInfo'>;\n  navigation: StackNavigationProp<RootStackParamList, 'GroupInfo'>;\n};\n\nconst GroupInfo: React.FC<GroupInfoProps> = ({route, navigation}) => {\n  const addMembersToGroups = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.addMembersToGroups\n  );\n  const joinLeaveGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.joinLeaveGroup\n  );\n  const deleteGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.deleteGroup\n  );\n  const viewGroupMembers = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.viewGroupMembers\n  );\n  const { group } = route.params;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const groupListenerId = useRef('groupListener' + new Date().getTime());\n\n  const [data, setData] = useState({ groupDetails: group });\n  const [userScope, setUserScope] = useState(\n    group?.getOwner() === CometChatUIKit.loggedInUser?.getUid()\n      ? CometChatUiKitConstants.GroupMemberScope.owner\n      : group?.getScope(),\n  );\n\n  // Separate states for each type of modal\n  const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false);\n  const [isOwnerLeaveModalOpen, setIsOwnerLeaveModalOpen] = useState(false);\n  const [isDeleteExitModalOpen, setIsDeleteExitModalOpen] = useState(false);\n  const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);\n\n  const { width } = useWindowDimensions();\n  const isSmallDevice = width < 360;\n\n  useEffect(() => {\n    // Update group details in state whenever group changes\n    const handleGroupListener = (updatedGroup: CometChat.Group) => {\n      if (updatedGroup.getGuid() === route.params.group.getGuid()) {\n        setData({ groupDetails: updatedGroup });\n        setUserScope(\n          updatedGroup?.getOwner() === CometChatUIKit.loggedInUser?.getUid()\n            ? CometChatUiKitConstants.GroupMemberScope.owner\n            : (updatedGroup?.getScope() ?? userScope),\n        );\n      }\n    };\n\n    const handleGroupMemberKicked = ({ kickedFrom }: any) => {\n      handleGroupListener(CommonUtils.clone(kickedFrom));\n    };\n    const handleGroupMemberBanned = ({ kickedFrom }: any) => {\n      handleGroupListener(CommonUtils.clone(kickedFrom));\n    };\n    const handleGroupMemberAdded = ({ userAddedIn }: any) => {\n      handleGroupListener(CommonUtils.clone(userAddedIn));\n    };\n    const handleOwnershipChanged = ({ group }: any) => {\n      handleGroupListener(group);\n    };\n\n    // Add group listeners\n    listners.addListener.groupListener({\n      groupListenerId: groupListenerId.current,\n      handleGroupListener,\n    });\n\n    CometChatUIEventHandler.addGroupListener(groupListenerId.current, {\n      ccGroupMemberKicked: (item: any) => handleGroupMemberKicked(item),\n      ccGroupMemberBanned: (item: any) => handleGroupMemberBanned(item),\n      ccGroupMemberAdded: (item: any) => handleGroupMemberAdded(item),\n      ccOwnershipChanged: (item: any) => handleOwnershipChanged(item),\n    });\n\n    return () => {\n      // Cleanup\n      listners.removeListner.removeGroupListener({\n        groupListenerId: groupListenerId.current,\n      });\n      CometChatUIEventHandler.removeGroupListener(groupListenerId.current);\n      CometChat.removeGroupListener(groupListenerId.current);\n    };\n  }, [group, userScope]);\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  const getLabel = (key: string) => {\n    const label = t(key);\n    // Split into two words if device is small\n    if (isSmallDevice && label.split(' ').length === 2) {\n      return label.split(' ').join('\\n');\n    }\n    return label;\n  };\n\n  /**\n   * Handlers for each modal's confirm action\n   */\n\n  // 1) Normal \"Leave Group\" confirm\n  const handleLeaveConfirm = () => {\n    setIsLeaveModalOpen(false);\n    if (data.groupDetails) {\n      leaveGroup(data.groupDetails, navigation, 2);\n    }\n  };\n\n  // 2) \"Owner => Transfer Ownership\" confirm\n  const handleOwnerLeaveConfirm = () => {\n    if (!data.groupDetails) return;\n\n    setIsOwnerLeaveModalOpen(false);\n    navigation.navigate('TransferOwnershipSection', {\n      group: data.groupDetails,\n    });\n  };\n\n  // 3) \"Delete and Exit\" confirm\n  const handleDeleteExitConfirm = () => {\n    setIsDeleteExitModalOpen(false);\n    if (!data.groupDetails) return;\n    // Delete group\n    CometChat.deleteGroup(data.groupDetails.getGuid())\n      .then(() => {\n        navigation.pop(2);\n      })\n      .catch(error => {\n        console.log('Group deletion failed:', error);\n      });\n    // Emit group deleted event\n    CometChatUIEventHandler.emitGroupEvent(\n      CometChatGroupsEvents.ccGroupDeleted,\n      {\n        group: data.groupDetails,\n      },\n    );\n  };\n\n  /** DELETE CONVERSATION LOGIC **/\n  const handleDeleteConversationConfirm = () => {\n    setDeleteModalOpen(false); // close the dialog\n    if (group) {\n      CometChat.getConversation(group.getGuid(), 'group')\n        .then(conversation => {\n          CometChat.deleteConversation(group.getGuid(), 'group')\n            .then(deletedConversation => {\n              console.log(deletedConversation);\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccConversationDeleted,\n                { conversation: conversation },\n              );\n              navigation.pop(2);\n            })\n            .catch(error => {\n              console.log('Error while deleting conversation:', error);\n            });\n        })\n        .catch(error => {\n          console.log('Error while deleting conversation:', error);\n        });\n    }\n  };\n\n  return (\n    <View\n      style={[styles.flexOne, { backgroundColor: theme.color.background1 }]}\n    >\n      {/* Header */}\n      <View style={styles.headerContainer}>\n        <TouchableOpacity\n          style={styles.iconContainer}\n          onPress={() => navigation.goBack()}\n        >\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.pL5,\n            { color: theme.color.textPrimary },\n          ]}\n        >\n          {t('GROUP_INFO')}\n        </Text>\n      </View>\n\n      {/* Main Group Info Container */}\n      <View\n        style={[\n          styles.groupInfoSection,\n          { borderColor: theme.color.borderLight },\n        ]}\n      >\n        <View style={styles.infoTitleContainer}>\n          <CometChatAvatar\n            style={{\n              containerStyle: styles.avatarContainer,\n              textStyle: styles.avatarText,\n              imageStyle: styles.avatarImage,\n            }}\n            image={\n              data.groupDetails?.getIcon()\n                ? { uri: data.groupDetails?.getIcon() }\n                : undefined\n            }\n            name={data.groupDetails?.getName() ?? ''}\n          />\n          <View style={styles.ellipseTail}>\n            <Text\n              style={[\n                theme.typography.heading3.medium,\n                styles.titleName,\n                { color: theme.color.textPrimary },\n              ]}\n              numberOfLines={1}\n              ellipsizeMode=\"tail\"\n            >\n              {data.groupDetails?.getName()}\n            </Text>\n          </View>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.boxLabel,\n              { color: theme.color.textSecondary },\n            ]}\n          >\n            {data.groupDetails?.getMembersCount() +\n              ' ' +\n              t(\n                data.groupDetails?.getMembersCount() === 1\n                  ? 'MEMBER'\n                  : 'MEMBERS',\n              )}\n          </Text>\n        </View>\n\n        {/* Action Boxes: Add Members / View Members / Banned Members */}\n        <View style={styles.boxContainerRow}>\n          {/* Add Members */}\n          {addMembersToGroups &&\n            [CometChatUiKitConstants.GroupMemberScope.owner,\n            CometChatUiKitConstants.GroupMemberScope.admin\n            ].includes(userScope) && (\n              <TouchableOpacity\n                onPress={() => {\n                  navigation.navigate('AddMember', { group });\n                }}\n                style={[\n                  styles.buttonContainer,\n                  { borderColor: theme.color.borderDefault },\n                ]}\n              >\n                <Icon\n                  icon={\n                    <PersonAdd color={theme.color.primary} height={24} width={24} />\n                  }\n                  containerStyle={styles.buttonIcon}\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.boxLabel,\n                    { color: theme.color.textSecondary },\n                  ]}\n                >\n                  {getLabel('ADD_MEMBERS')}\n                </Text>\n              </TouchableOpacity>\n            )}\n\n          {/* View Members */}\n          {viewGroupMembers && (\n          <TouchableOpacity\n            onPress={() => {\n              navigation.navigate('ViewMembers', { group });\n            }}\n            style={[\n              styles.buttonContainer,\n              { borderColor: theme.color.borderDefault },\n            ]}\n          >\n            <Icon\n              icon={\n                <Group color={theme.color.primary} height={24} width={24} />\n              }\n              containerStyle={styles.buttonIcon}\n            />\n            <Text\n              style={[\n                theme.typography.caption1.regular,\n                styles.boxLabel,\n                { color: theme.color.textSecondary },\n              ]}\n            >\n              {getLabel('VIEW_MEMBERS')}\n            </Text>\n          </TouchableOpacity>\n          )}\n          {/* Banned Members */}\n          {(userScope === CometChatUiKitConstants.GroupMemberScope.owner ||\n            userScope === CometChatUiKitConstants.GroupMemberScope.admin ||\n            userScope ===\n              CometChatUiKitConstants.GroupMemberScope.moderator) && (\n            <TouchableOpacity\n              onPress={() => {\n                navigation.navigate('BannedMember', { group });\n              }}\n              style={[\n                styles.buttonContainer,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            >\n              <Icon\n                icon={\n                  <PersonOff\n                    color={theme.color.primary}\n                    height={24}\n                    width={24}\n                  />\n                }\n                containerStyle={styles.buttonIcon}\n              />\n              <Text\n                style={[\n                  theme.typography.caption1.regular,\n                  styles.boxLabel,\n                  { color: theme.color.textSecondary },\n                ]}\n              >\n                {getLabel('BANNED_MEMBERS')}\n              </Text>\n            </TouchableOpacity>\n          )}\n        </View>\n      </View>\n\n      {/* Actions */}\n      <View style={styles.actionContainer}>\n        <View style={styles.actionButtons}>\n          <TouchableOpacity\n            onPress={() => setDeleteModalOpen(true)}\n            style={styles.iconContainer}\n          >\n            <Icon\n              icon={<Delete color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.mL5,\n                { color: theme.color.error },\n              ]}\n            >\n              {t('DELETE_CHAT_TEXT')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n        {/* If user is owner but group has multiple members => must TRANSFER ownership.\n           Otherwise => normal leave.  */}\n        {joinLeaveGroup &&\n          (data.groupDetails.getMembersCount() > 1 ||\n            userScope !== CometChatUiKitConstants.GroupMemberScope.owner) && (\n            <View style={styles.actionButtons}>\n              <TouchableOpacity\n                onPress={() => {\n                  if (userScope === CometChatUiKitConstants.GroupMemberScope.owner) {\n                    setIsOwnerLeaveModalOpen(true);\n                  } else {\n                    setIsLeaveModalOpen(true);\n                  }\n                }}\n                style={styles.iconContainer}\n              >\n                <Icon icon={<Block color={theme.color.error} height={24} width={24} />} />\n                <Text\n                  style={[\n                    theme.typography.heading4.regular,\n                    styles.mL5,\n                    { color: theme.color.error },\n                  ]}\n                >\n                  {t('LEAVE')}\n                </Text>\n              </TouchableOpacity>\n            </View>\n          )}\n\n        {/* Delete and Exit (Group owner only) */}\n        {deleteGroup && \n        [\n          CometChatUiKitConstants.GroupMemberScope.owner,\n          CometChatUiKitConstants.GroupMemberScope.admin,\n        ].includes(userScope) && (\n          <View style={styles.actionButtons}>\n            <TouchableOpacity\n              onPress={() => setIsDeleteExitModalOpen(true)}\n              style={styles.iconContainer}\n            >\n              <Icon\n                icon={\n                  <Delete color={theme.color.error} height={24} width={24} />\n                }\n              />\n              <Text\n                style={[\n                  theme.typography.heading4.regular,\n                  styles.mL5,\n                  { color: theme.color.error },\n                ]}\n              >\n                {t('DELETE_AND_EXIT')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        )}\n      </View>\n\n      {/* ============== LEAVE GROUP (Regular) Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isLeaveModalOpen}\n        onCancel={() => setIsLeaveModalOpen(false)}\n        onConfirm={handleLeaveConfirm}\n        titleText={t('LEAVE_GROUP_TEXT')}\n        messageText={t('LEAVE_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('LEAVE')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* ============== TRANSFER OWNERSHIP Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isOwnerLeaveModalOpen}\n        onCancel={() => setIsOwnerLeaveModalOpen(false)}\n        onConfirm={handleOwnerLeaveConfirm}\n        titleText={t('TRANSFER_OWNERSHIP')}\n        messageText={t('TRANSFER_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('TRANSFER')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* ============== DELETE AND EXIT Dialog ============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteExitModalOpen}\n        onCancel={() => setIsDeleteExitModalOpen(false)}\n        onConfirm={handleDeleteExitConfirm}\n        titleText={`${t('DELETE_AND_EXIT')}?`}\n        messageText={t('DELETE_AND_EXIT_SURE')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE_AND_EXIT')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* =============== DELETE CHAT MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteModalOpen}\n        onCancel={() => setDeleteModalOpen(false)}\n        onConfirm={handleDeleteConversationConfirm}\n        onDismiss={() => console.log('Delete Modal dismissed')}\n        titleText={t('DELETE_CHAT')}\n        messageText={t('SURE_TO_DELETE_CHAT')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n    </View>\n  );\n};\n\nexport default GroupInfo;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/GroupInfoStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  headerContainer: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  ellipseTail: {paddingHorizontal: 10, width: '80%'},\n  iconContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  pL5: {\n    paddingLeft: 5,\n  },\n  groupInfoSection: {\n    marginTop: 12,\n    borderTopWidth: 1,\n    borderBottomWidth: 1,\n    paddingVertical: 10,\n  },\n  infoTitleContainer: {\n    alignSelf: 'center',\n    alignItems: 'center',\n    marginTop: 20,\n  },\n  avatarContainer: {\n    height: 120,\n    width: 120,\n  },\n  avatarText: {\n    fontSize: 28,\n    lineHeight: 55,\n  },\n  avatarImage: {\n    height: '100%',\n    width: '100%',\n  },\n  titleName: {\n    marginTop: 10,\n    // width:'20%',\n    alignSelf: 'center',\n  },\n  boxLabel: {\n    marginTop: 5,\n    textAlign: 'center',\n    alignSelf: 'center',\n  },\n  boxContainerRow: {\n    flexDirection: 'row',\n    paddingHorizontal: 20,\n    justifyContent: 'space-between',\n    marginVertical: 20,\n  },\n  buttonContainer: {\n    flex: 1,\n    paddingVertical: 10,\n    borderWidth: 1,\n    borderRadius: 8,\n    justifyContent: 'center',\n    alignItems: 'center',\n    marginHorizontal: 5,\n  },\n  buttonIcon: {\n    marginBottom: 5,\n  },\n  actionContainer: {\n    paddingTop: 10,\n    gap: 4,\n  },\n  actionButtons: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 8,\n    paddingLeft: 20,\n    width: '100%',\n  },\n  mL5: {\n    marginLeft: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/Messages.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  TouchableOpacity,\n  View,\n  StyleSheet,\n  Text,\n  BackHandler,\n  Platform,\n  Modal,\n  Animated,\n  Dimensions,\n  AppState,\n  AppStateStatus,\n} from 'react-native';\nimport {\n  CometChatUIKit,\n  CometChatMessageHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  CometChatMessageComposer,\n  useTheme,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  ChatConfigurator,\n  useCometChatTranslation,\n  Icon,\n  CometChatAIAssistantChatHistory,\n  CometChatAIAssistantTools,\n  CometChatThemeProvider,\n  stopStreamingForRunId,\n} from '@cometchat/chat-uikit-react-native';\nimport { StackScreenProps } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport InfoIcon from '../../../assets/icons/InfoIcon';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport Info from '../../../assets/icons/Info';\nimport {useActiveChat} from '../../../utils/ActiveChatContext';\nimport { useConfig } from '../../../config/store';\nimport { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\n\nconst { width } = Dimensions.get('window');\n\ntype Props = StackScreenProps<RootStackParamList, 'Messages'>;\n\nconst Messages: React.FC<Props> = ({ route, navigation }) => {\n  const {\n    user,\n    group,\n    fromMention = false,\n    fromMessagePrivately = false,\n    parentMessageId: routeParentMessageId,\n    messageId,\n    searchKeyword,\n    navigatedFromSearch\n  } = route.params;\n  const typingIndicator = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.typingIndicator\n  );\n  const threadConversationAndReplies = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.threadConversationAndReplies\n  );\n  const photosSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing\n  );\n  const videoSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing\n  );\n  const audioSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing\n  );\n  const fileSharing = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing\n  );\n  const editMessage = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.editMessage\n  );\n  const deleteMessage = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.deleteMessage\n  );\n  const messageDeliveryAndReadReceipts = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n  const userAndFriendsPresence = useConfig(\n    (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n  );\n  const mentions = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.mentions\n  );\n  const reactions = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.reactions\n  );\n  const messageTranslation = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.messageTranslation\n  );\n  const polls = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.polls\n  );\n  const collaborativeWhiteboard = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeWhiteboard\n  );\n  const collaborativeDocument = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeDocument\n  );\n  const voiceNotes = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes\n  );\n  const stickers = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.stickers\n  );\n  const userInfo = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.userInfo\n  );\n  const groupInfo = useConfig(\n    (state) => state.settings.chatFeatures.deeperUserEngagement.groupInfo\n  );\n  const sendPrivateMessageToGroupMembers = useConfig(\n    (state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers\n  );\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n  const groupVideoConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference\n  );\n  const groupVoiceConference = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference\n  );\n\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const themeRef = useRef(theme);\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n  const userListenerId = 'app_messages' + new Date().getTime();\n  // Stable listener id for the lifetime of this component (prevents multiple listeners)\n  const openmessageListenerIdRef = useRef('message_' + new Date().getTime());\n  const lastOpenChatRef = useRef<{ uid: string; time: number } | null>(null);\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(user);\n  const [messageListKey, setMessageListKey] = useState(0);\n  const [messageComposerKey, setMessageComposerKey] = useState(0);\n  const [showHistoryModal, setShowHistoryModal] = useState(false);\n\n  // Manage parentMessageId in parent component\n  const [parentMessageId, setParentMessageId] = useState<string | undefined>(routeParentMessageId);\n\n  const { setActiveChat } = useActiveChat();\n  const insets = useSafeAreaInsets();\n\n  // Add ref to track streaming state\n  const messageComposerRef = useRef<any>(null);\n\n  /** Animation state for drawer */\n  const slideAnim = useRef(new Animated.Value(width)).current;\n\n  useEffect(() => {\n    if (showHistoryModal) {\n      Animated.timing(slideAnim, {\n        toValue: 0,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    } else {\n      Animated.timing(slideAnim, {\n        toValue: width,\n        duration: 300,\n        useNativeDriver: true,\n      }).start();\n    }\n  }, [showHistoryModal, slideAnim]);\n\n  /** Agentic user check */\n  const isAgenticUser = useCallback((): boolean => {\n    if (localUser) {\n      return localUser.getRole?.() === '@agentic';\n    }\n    return false;\n  }, [localUser]);\n  const agentic = isAgenticUser();\n\n  // ============================================\n  // Group Kicked/Banned Detection (real-time)\n  // ============================================\n  const isNoLongerMember = useGroupMemberStatus(group);\n\n  // Stop streaming when app goes to background for agentic users\n  useEffect(() => {\n    if (!agentic) return;\n\n    const handleAppStateChange = (nextAppState: AppStateStatus) => {\n      if (nextAppState === 'background' || nextAppState === 'inactive') {\n        stopStreamingForRunId();\n        if (messageComposerRef.current?.resetStreaming) {\n          messageComposerRef.current.resetStreaming();\n        }\n      }\n    };\n\n    const subscription = AppState.addEventListener('change', handleAppStateChange);\n    return () => subscription.remove();\n  }, [agentic]);\n\n  useEffect(() => {\n    // if it’s a user chat\n    if (user) {\n      setActiveChat({ type: 'user', id: user.getUid() });\n    } else if (group) {\n      setActiveChat({ type: 'group', id: group.getGuid() });\n    }\n\n    // Cleanup on unmount => setActiveChat(null)\n    return () => {\n      setActiveChat(null);\n      // Reset streaming state when leaving chat\n      if (messageComposerRef.current?.resetStreaming) {\n        messageComposerRef.current.resetStreaming();\n      }\n    };\n  }, [user, group, setActiveChat]);\n\n  useEffect(() => {\n    const backAction = () => {\n      if (fromMention || fromMessagePrivately) {\n        navigation.goBack();\n      } else {\n        navigation.popToTop();\n      }\n      return true;\n    };\n    // Add event listener for hardware back press\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n    return () => backHandler.remove();\n  }, [navigation, fromMention, fromMessagePrivately]);\n\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (item: { user: CometChat.User }) =>\n        handleccUserBlocked(item),\n      ccUserUnBlocked: (item: { user: CometChat.User }) =>\n        handleccUserUnBlocked(item),\n    });\n    const statusListenerId = 'user_status_messages_' + new Date().getTime();\n    if (localUser) {\n\n      CometChat.addUserListener(\n        statusListenerId,\n        new CometChat.UserListener({\n          onUserOnline: (onlineUser: CometChat.User) => {\n            if (onlineUser.getUid() === localUser.getUid()) {\n              console.log('🚀 ~ onUserOnline ~ onlineUser:', onlineUser);\n              setLocalUser(onlineUser);\n            }\n          },\n          onUserOffline: (offlineUser: CometChat.User) => {\n            console.log('🚀 ~ onUserOffline ~ offlineUser:', offlineUser);\n            if (offlineUser.getUid() === localUser.getUid()) {\n              setLocalUser(offlineUser);\n            }\n          },\n        }),\n      );\n    }\n\n    // Only attach the openChat listener when we are in a group context.\n    // This prevents stacking duplicate private chat screens because the group\n    // screen remains mounted underneath the user chat.\n    const currentListenerId = openmessageListenerIdRef.current;\n    if (group) {\n      CometChatUIEventHandler.addUIListener(currentListenerId, {\n        openChat: ({ user: chatUser }) => {\n          if (!chatUser) return;\n\n          try {\n            const uid = chatUser.getUid();\n\n            // 1. Debounce rapid duplicate events (within 800ms for the same UID)\n            const now = Date.now();\n            if (\n              lastOpenChatRef.current &&\n              lastOpenChatRef.current.uid === uid &&\n              now - lastOpenChatRef.current.time < 800\n            ) {\n              return; // ignore duplicate\n            }\n\n            const state = navigation.getState();\n            const routes = state?.routes || [];\n            const topRoute = routes[routes.length - 1];\n\n            // If the top route already represents this user chat, skip.\n            if (\n              topRoute?.name === 'Messages' &&\n              (topRoute as any)?.params?.user?.getUid &&\n              (topRoute as any).params.user.getUid() === uid\n            ) {\n              return;\n            }\n\n            // If any existing route (beneath) already has this user chat, pop back to it instead of pushing another.\n            const existingIndex = routes.findIndex(\n              r =>\n                r.name === 'Messages' &&\n                (r as any)?.params?.user?.getUid &&\n                (r as any).params.user.getUid() === uid,\n            );\n            if (existingIndex !== -1) {\n              const popCount = routes.length - existingIndex - 1;\n              if (popCount > 0) {\n                navigation.pop(popCount);\n              }\n              return; // we're now at the existing user chat\n            }\n\n            lastOpenChatRef.current = { uid, time: now };\n            navigation.push('Messages', { user: chatUser, fromMessagePrivately: true });\n          } catch (e) {\n            console.warn('openChat navigation prevented due to error', e);\n          }\n        },\n      });\n    }\n\n    // Close sticker panel when screen loses focus\n    const blurSub = navigation.addListener('blur', () => {\n      CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n        alignment: 'composerBottom',\n        child: () => null,\n        panelId: 'sticker',\n      });\n    });\n    const focusSub = navigation.addListener('focus', () => {\n      // Force re-mount composer so auxiliary options (StickerButton) reset correctly\n      setMessageComposerKey(prev => prev + 1);\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n      CometChat.removeUserListener(statusListenerId);\n      if (group) {\n        CometChatUIEventHandler.removeUIListener(currentListenerId);\n      }\n      blurSub();\n\n      focusSub();\n    };\n    // Listener re-attached only when group context changes\n  }, [navigation, group, userListenerId]);\n\n  const handleccUserBlocked = ({ user: blockedUser }: { user: CometChat.User }) => {\n    setLocalUser(CommonUtils.clone(blockedUser));\n  };\n\n  const handleccUserUnBlocked = ({ user: unblockedUser }: { user: CometChat.User }) => {\n    setLocalUser(CommonUtils.clone(unblockedUser));\n  };\n\n  /** Reset messages */\n  const handleNewChatClick = useCallback(() => {\n    if (messageComposerRef.current?.resetStreaming) {\n      messageComposerRef.current.resetStreaming();\n    }\n    setParentMessageId(undefined);\n    setMessageListKey(prev => prev + 1);\n    setMessageComposerKey(prev => prev + 1);\n    setShowHistoryModal(false);\n    navigation.replace('Messages', {\n      user,\n      group,\n    });\n  }, [navigation, user, group]);\n\n  /** Open chat history modal */\n  const handleChatHistoryClick = useCallback(() => {\n    setShowHistoryModal(true);\n  }, []);\n\n  /** Handle history message click */\n  const handleHistoryMessageClick = useCallback(\n    (message: CometChat.BaseMessage) => {\n      if (messageComposerRef.current && messageComposerRef.current.stopStreaming) {\n        messageComposerRef.current.stopStreaming();\n      }\n      setShowHistoryModal(false);\n      setParentMessageId(message.getId().toString());\n      setMessageListKey(prev => prev + 1);\n      setMessageComposerKey(prev => prev + 1);\n    },\n    [],\n  );\n\n  /** Handle history error */\n  const handleChatHistoryError = useCallback(\n    (_error: CometChat.CometChatException) => {},\n    [],\n  );\n\n  const unblock = async (userToUnblock: CometChat.User) => {\n    let uid = userToUnblock.getUid();\n    try {\n      const response = await CometChat.unblockUsers([uid]);\n      const unBlockedUser = await CometChat.getUser(uid);\n      if (response) {\n        setLocalUser(unBlockedUser);\n\n        // Optionally emit an event or let the server call do the job\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: unBlockedUser,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user:', error);\n    }\n  };\n\n  // Options for the header menu\n  const options = useMemo(() => {\n    return () => {\n      // For agentic users, don't show any options menu\n      if (agentic) {\n        return [];\n      }\n\n      const menuOptions = [];\n\n      // Add info option first\n      if (group && loggedInUser) {\n        menuOptions.push({\n          text: 'Group Info',\n          onPress: () => {\n            navigation.navigate('GroupInfo', { group });\n          },\n          icon: <Icon name=\"info\" width={20} height={20} color={theme.color.iconSecondary} />,\n        });\n      } else if (localUser && !localUser.getBlockedByMe()) {\n        menuOptions.push({\n          text: 'User Info',\n          onPress: () => {\n            navigation.navigate('UserInfo', { user: localUser });\n          },\n          icon: <Icon name=\"info\" width={20} height={20} color={theme.color.iconSecondary} />,\n        });\n      }\n\n      // Then add search option\n      menuOptions.push({\n        text: 'Search',\n        onPress: () => {\n          if (group) {\n            navigation.navigate('SearchMessages', { group });\n          } else if (localUser) {\n            navigation.navigate('SearchMessages', { user: localUser });\n          }\n        },\n        icon: <Icon name=\"search\" width={20} height={20} color={theme.color.iconSecondary} />,\n      });\n\n      return menuOptions;\n    };\n  }, [navigation, group, localUser, theme, agentic, loggedInUser]);\n\n\n  const getMentionsTap = useCallback(() => {\n    const mentionsFormatter =\n      ChatConfigurator.getDataSource().getMentionsFormatter(\n        loggedInUser,\n        theme,\n      );\n    if (user) mentionsFormatter.setUser(user);\n    if (group) mentionsFormatter.setGroup(group);\n\n    mentionsFormatter.setOnMentionClick(\n      (_message: CometChat.BaseMessage, uid: string) => {\n        if (uid !== loggedInUser.getUid()) {\n          // Get the user by UID and navigate to Messages\n          CometChat.getUser(uid)\n            .then((mentionedUser: CometChat.User) => {\n              navigation.push('Messages', {\n                user: mentionedUser,\n                fromMention: true,\n              });\n            })\n            .catch((error: any) => {\n              console.error('Error fetching mentioned user:', error);\n            });\n        }\n      },\n    );\n    return mentionsFormatter;\n  }, [user, group, loggedInUser, navigation, theme]);\n\n  /** Theme override for agentic outgoing bubble */\n  const providerTheme = useMemo(() => {\n    const defaultOutgoingBg =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.containerStyle?.backgroundColor;\n    const defaultTextColor =\n      theme?.messageListStyles?.outgoingMessageBubbleStyles?.textBubbleStyles?.textStyle?.color;\n\n    const outgoingOverride = {\n      messageComposerStyles: {\n        containerStyle: {\n          backgroundColor: agentic\n            ? theme.color.background3\n            : theme?.messageComposerStyles?.containerStyle?.backgroundColor,\n        },\n      },\n      messageListStyles: {\n        outgoingMessageBubbleStyles: {\n          containerStyle: {\n            backgroundColor: agentic\n              ? theme.color.background4\n              : defaultOutgoingBg,\n          },\n          textBubbleStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textPrimary\n                : defaultTextColor,\n            },\n          },\n          dateStyles: {\n            textStyle: {\n              color: agentic\n                ? theme.color.textSecondary\n                : defaultTextColor,\n            },\n          },\n        },\n      },\n    };\n\n    return {\n      light: outgoingOverride,\n      dark: outgoingOverride,\n      mode: \"auto\" as \"auto\",\n    };\n  }, [agentic, theme]);\n\n  return (\n    <CometChatThemeProvider theme={providerTheme}>\n      <View style={styles.flexOne}>\n        <CometChatMessageHeader\n          user={localUser}\n          group={group}\n          onBack={() => {\n            if (fromMention || fromMessagePrivately) {\n              navigation.goBack();\n            } else {\n              navigation.popToTop();\n            }\n          }}\n          showBackButton={true}\n          usersStatusVisibility={userAndFriendsPresence}\n          hideVoiceCallButton={\n            (user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)\n          }\n          hideVideoCallButton={\n            (user && !oneOnOneVideoCalling) || (group && !groupVideoConference)\n          }\n          hideChatHistoryButton={false}\n          hideNewChatButton={false}\n          onChatHistoryButtonClick={handleChatHistoryClick}\n          onNewChatButtonClick={handleNewChatClick}\n          options={options}\n        />\n        <View style={styles.flexOne}>\n          <CometChatMessageList\n            key={messageListKey}\n            textFormatters={[getMentionsTap()]}\n            user={user}\n            group={group}\n            parentMessageId={parentMessageId}\n            // callback signature expects (messageObject, messageBubbleView)\n            onThreadRepliesPress={(messageObject, _messageBubbleView) => {\n              CometChatUIEventHandler.emitUIEvent?.(\n                CometChatUIEvents.hidePanel,\n                {\n                  alignment: 'composerBottom',\n                  child: () => null,\n                },\n              );\n              navigation.navigate('ThreadView', { message: messageObject, user, group });\n            }}\n            hideReplyInThreadOption={!threadConversationAndReplies}\n            hideEditMessageOption={!editMessage}\n            hideDeleteMessageOption={!deleteMessage}\n            receiptsVisibility={messageDeliveryAndReadReceipts}\n            hideTranslateMessageOption={!messageTranslation}\n            hideReactionOption={!reactions}\n            hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}\n            aiAssistantTools={new CometChatAIAssistantTools({\n              getCurrentWeather: (args: any) => console.log('Weather args', args),\n            })}\n            streamingSpeed={10}\n            goToMessageId={messageId}\n            searchKeyword={searchKeyword}\n            navigatedFromSearch={navigatedFromSearch}\n            showMarkAsUnreadOption={true}\n            startFromUnreadMessages={true}\n          />\n        </View>\n\n        {/* Chat History Drawer */}\n        {agentic && (\n          <Modal visible={showHistoryModal} transparent animationType=\"none\" onRequestClose={() => setShowHistoryModal(false)}>\n            <View style={drawerStyles.backdrop}>\n              <Animated.View\n                style={[\n                  drawerStyles.drawer,\n                  {\n                    backgroundColor: theme.color.background1,\n                    paddingTop: Platform.OS === 'ios' ? insets.top : 0,\n                  },\n                  { transform: [{ translateX: slideAnim }] },\n                ]}\n              >\n                <CometChatAIAssistantChatHistory\n                  user={localUser}\n                  group={group}\n                  onClose={() => setShowHistoryModal(false)}\n                  onMessageClicked={handleHistoryMessageClick}\n                  onError={handleChatHistoryError}\n                  onNewChatButtonClick={handleNewChatClick}\n                />\n              </Animated.View>\n            </View>\n          </Modal>\n        )}\n\n        {isNoLongerMember ? (\n          <View\n            style={{\n              paddingVertical: 12,\n              paddingHorizontal: 16,\n              backgroundColor: theme.color.background1 as string,\n            }}\n          >\n            <Text\n              style={[\n                theme.typography.body.regular,\n                {\n                  color: theme.color.textPrimary as string,\n                  textAlign: 'center',\n                },\n              ]}\n            >\n              {t('GROUP_NO_LONGER_MEMBER')}\n            </Text>\n          </View>\n        ) : localUser?.getBlockedByMe() ? (\n          <View\n            style={[\n              styles.blockedContainer,\n              { backgroundColor: theme.color.background3 },\n            ]}\n          >\n            <Text\n              style={[\n                theme.typography.button.regular,\n                {\n                  color: theme.color.textSecondary,\n                  textAlign: 'center',\n                  paddingBottom: 10,\n                },\n              ]}\n            >\n              {t('BLOCKED_USER_DESC')}\n            </Text>\n            <TouchableOpacity\n              onPress={() => unblock(localUser)}\n              style={[styles.button, { borderColor: theme.color.borderDefault }]}\n            >\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.buttontext,\n                  {\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n              >\n                {t('UNBLOCK')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        ) : (\n          <CometChatCompactMessageComposer\n            key={messageComposerKey}\n            ref={messageComposerRef}\n            parentMessageId={parentMessageId}\n            user={localUser}\n            group={group}\n            keyboardAvoidingViewProps={{\n              ...(Platform.OS === 'android'\n                ? {}\n                : {\n                  behavior: 'padding',\n                }),\n            }}\n            disableTypingEvents={!typingIndicator}\n            hideImageAttachmentOption={!photosSharing}\n            hideVideoAttachmentOption={!videoSharing}\n            hideAudioAttachmentOption={!audioSharing}\n            hideFileAttachmentOption={!fileSharing}\n            hideCameraOption={!photosSharing}\n            disableMentions={!mentions}\n            hideStickersButton={!stickers}\n            hideCollaborativeDocumentOption={!collaborativeDocument}\n            hideCollaborativeWhiteboardOption={!collaborativeWhiteboard}\n            hidePollsAttachmentOption={!polls}\n            hideVoiceRecordingButton={!voiceNotes}\n          />\n        )}\n      </View>\n    </CometChatThemeProvider>\n  );\n};\n\nconst styles = StyleSheet.create({\n  flexOne: {\n    flex: 1,\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n\n});\n\nconst drawerStyles = StyleSheet.create({\n  backdrop: {\n    justifyContent: 'flex-start',\n    alignItems: 'flex-end',\n  },\n  drawer: {\n    width: '100%',\n    height: '100%',\n    overflow: 'hidden',\n  },\n});\n\nexport default Messages;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/OngoingCallScreen.tsx",
    "content": "import React, { useMemo, useRef } from 'react';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { navigate, navigationRef } from '../../../navigation/NavigationService';\nimport { CometChatCalls } from '@cometchat/calls-sdk-react-native';\nimport { CometChatOngoingCall } from '@cometchat/chat-uikit-react-native';\nimport { voipHandler } from '../../../utils/VoipNotificationHandler';\nimport { SCREEN_CONSTANTS } from '../../../utils/AppConstants';\nimport type { RouteProp } from '@react-navigation/native';\nimport { CallType, RootStackParamList } from '../../../navigation/types';\n\ntype Props = {\n  navigation: any;\n  route: RouteProp<RootStackParamList, 'OngoingCallScreen'>;\n};\n\nconst OngoingCallScreen = ({ navigation, route }: Props) => {\n  const params = route.params;\n\n  // 1) Normalize sessionId\n  const sessionID: string | undefined = useMemo(() => {\n    if ('sessionId' in params) return params.sessionId;\n    const c = (params as { call: any }).call;\n    return c?.sessionId ?? c?.getSessionId?.();\n  }, [params]);\n\n  // 2) Normalize/derive callType\n  const callType: CallType | undefined = useMemo(() => {\n    if ('callType' in params) return params.callType;\n\n    const c = (params as { call: any }).call;\n    if (!c) return undefined;\n\n    // Prefer public getter if available\n    if (typeof c?.getType === 'function') {\n      const t = c.getType();\n      if (t === CometChat.CALL_TYPE.AUDIO) return 'audio';\n      if (t === CometChat.CALL_TYPE.VIDEO) return 'video';\n    }\n\n    // Fallbacks for raw payloads\n    if (c?.callType === 'audio') return 'audio';\n    if (c?.data?.type === CometChat.CALL_TYPE.AUDIO) return 'audio';\n    if (c?.data?.type === CometChat.CALL_TYPE.VIDEO) return 'video';\n\n    return undefined;\n  }, [params]);\n\n  const isAudioOnly = callType === 'audio';\n\n  const callListener = useRef<any>(\n    new CometChatCalls.OngoingCallListener({\n      onCallEnded: () => {\n        CometChat.clearActiveCall();\n        CometChatCalls.endSession();\n        navigate('BottomTabNavigator', undefined as any);\n        navigationRef.reset({\n          index: 0,\n          routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n        });\n        voipHandler?.removeCallDialer();\n      },\n      onCallEndButtonPressed: () => {\n        voipHandler?.removeCallDialer();\n        if (sessionID) CometChat.endCall(sessionID);\n        navigate('BottomTabNavigator', undefined as any);\n        navigationRef.reset({\n          index: 0,\n          routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n        });\n      },\n    }),\n  );\n\n  const callSettings = useMemo(() => {\n    return new CometChatCalls.CallSettingsBuilder()\n      .enableDefaultLayout(true)\n      .setCallEventListener(callListener.current)\n      .setIsAudioOnlyCall(!!isAudioOnly);\n  }, [isAudioOnly]);\n\n  if (!sessionID) {\n    // Belt & suspenders: try to recover from active call if param missing\n    const active: any = CometChat.getActiveCall?.();\n    const recovered =\n      typeof active?.getSessionId === 'function'\n        ? active.getSessionId()\n        : undefined;\n    if (!recovered) return null; // or render a fallback/loading\n  }\n\n  return (\n    <CometChatOngoingCall\n      sessionID={sessionID!}\n      callSettingsBuilder={callSettings}\n    />\n  );\n};\n\nexport default OngoingCallScreen;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/SearchMessages.tsx",
    "content": "import React, { useCallback, useEffect, useRef } from 'react';\nimport {\n  View,\n  StyleSheet,\n  BackHandler,\n  Platform,\n  StatusBar,\n} from 'react-native';\nimport {\n  CometChatSearch,\n  useTheme,\n  useCometChatTranslation,\n} from '@cometchat/chat-uikit-react-native';\nimport { StackScreenProps } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { navigate } from '../../../navigation/NavigationService';\nimport { SCREEN_CONSTANTS } from '../../../utils/AppConstants';\n\ntype Props = StackScreenProps<RootStackParamList, 'SearchMessages'>;\n\nconst SearchMessages: React.FC<Props> = ({ route, navigation }) => {\n  const { user, group } = route.params || {};\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const insets = useSafeAreaInsets();\n\n  const navigationRef = useRef(navigation);\n  const routeRef = useRef(route);\n\n  // Update refs when navigation/route changes\n  useEffect(() => {\n    navigationRef.current = navigation;\n    routeRef.current = route;\n  }, [navigation, route]);\n\n  // Handle back button on Android\n  useEffect(() => {\n    const backAction = () => {\n      navigation.goBack();\n      return true;\n    };\n\n    const backHandler = BackHandler.addEventListener(\n      'hardwareBackPress',\n      backAction,\n    );\n\n    return () => backHandler.remove();\n  }, [navigation]);\n\n  const handleBack = useCallback(() => {\n    navigation.goBack();\n  }, [navigation]);\n\n  const handleConversationClicked = useCallback((conversation: CometChat.Conversation, searchKeyword?: string) => {\n    // Navigate to messages screen with the selected conversation\n    const conversationWith = conversation.getConversationWith();\n\n    if (conversationWith instanceof CometChat.User) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        user: conversationWith,\n        searchKeyword,\n      });\n    } else if (conversationWith instanceof CometChat.Group) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        group: conversationWith,\n        searchKeyword,\n      });\n    }\n  }, []);\n\n  const handleMessageClicked = useCallback(async (message: CometChat.BaseMessage, searchKeyword?: string) => {\n    // Navigate to messages screen and highlight the specific message\n    const messageReceiver = message.getReceiver();\n    const parentMessageId = message.getParentMessageId();\n\n    let targetUser: CometChat.User | undefined;\n    let targetGroup: CometChat.Group | undefined;\n\n    if (messageReceiver instanceof CometChat.User) {\n      // For user messages, determine if it's a direct message to/from the user\n      const sender = message.getSender();\n      const loggedInUser = await CometChat.getLoggedinUser();\n\n      if (sender.getUid() === loggedInUser?.getUid()) {\n        // Message sent by logged-in user, target is receiver\n        targetUser = messageReceiver;\n      } else {\n        // Message received by logged-in user, target is sender\n        targetUser = sender;\n      }\n    } else if (messageReceiver instanceof CometChat.Group) {\n      targetGroup = messageReceiver;\n    }\n\n    if (parentMessageId) {\n      try {\n        const parentMessage = await CometChat.getMessageDetails(parentMessageId);\n        if (parentMessage) {\n          navigate(SCREEN_CONSTANTS.THREAD_VIEW, {\n            message: parentMessage,\n            user: targetUser,\n            group: targetGroup,\n            highlightMessageId: String(message.getId()),\n          });\n          return;\n        }\n      } catch (e) {\n        console.error(\"Failed to fetch parent message\", e);\n      }\n    }\n\n    if (targetUser) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        user: targetUser,\n        messageId: String(message.getId()),\n        searchKeyword,\n        navigatedFromSearch: true,\n      });\n    } else if (targetGroup) {\n      navigate(SCREEN_CONSTANTS.MESSAGES, {\n        group: targetGroup,\n        messageId: String(message.getId()),\n        searchKeyword,\n        navigatedFromSearch: true,\n      });\n    }\n  }, []);\n\n\n  // Determine placeholder text\n  let searchPlaceholder = \"Search\";\n  if (user && user.getName()) {\n    searchPlaceholder = `Search in ${user.getName()}`;\n  } else if (group && group.getName()) {\n    searchPlaceholder = `Search in ${group.getName()}`;\n  }\n\n  return (\n    <View style={[styles.container, Platform.OS === 'android' && { paddingTop: insets.top }]}>\n      {Platform.OS === 'ios' && (\n        <StatusBar\n          barStyle={\n            theme.mode === 'light' ? 'dark-content' : 'light-content'\n          }\n          backgroundColor={theme.color.background1}\n        />\n      )}\n\n      <CometChatSearch\n        onBack={handleBack}\n        hideBackButton={false}\n        onConversationClicked={handleConversationClicked}\n        onMessageClicked={handleMessageClicked}\n        uid={user?.getUid()}\n        guid={group?.getGuid()}\n        searchPlaceholder={searchPlaceholder}\n        messagesRequestBuilder={\n          new CometChat.MessagesRequestBuilder().setLimit(30)\n        }\n        conversationsRequestBuilder={\n          new CometChat.ConversationsRequestBuilder().setLimit(30)\n        }\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\nexport default SearchMessages;"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/ThreadView.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  View,\n  Text,\n  TouchableOpacity,\n  StyleSheet,\n  BackHandler,\n  Platform,\n} from 'react-native';\nimport {\n  RouteProp,\n  useRoute,\n  useNavigation,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport {\n  CometChatThreadHeader,\n  CometChatMessageList,\n  CometChatCompactMessageComposer,\n  CometChatMessageComposer,\n  useCometChatTranslation,\n  CometChatUIKit,\n  ChatConfigurator,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '@cometchat/chat-uikit-react-native';\nimport { useTheme } from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CommonUtils } from '../../../utils/CommonUtils';\nimport { useConfig } from '../../../config/store';\nimport { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';\n\n\ntype ThreadViewRouteProp = RouteProp<RootStackParamList, 'ThreadView'>;\ntype ThreadViewNavProp = StackNavigationProp<RootStackParamList>;\n\nconst ThreadView = () => {\n  const { params } = useRoute<ThreadViewRouteProp>();\n  const navigation = useNavigation<ThreadViewNavProp>(); // <-- added navigation\n  const { goBack } = navigation;\n  const theme = useTheme();\n  const { message, user, group } = params || {};\n  const { t } = useCometChatTranslation();\n  const messageDeliveryAndReadReceipts = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts\n  );\n  const hideReactionOption = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.reactions\n  );\n  const photosSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing\n  );\n  const videoSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing\n  );\n  const audioSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing\n  );\n  const fileSharing = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing\n  );\n  const mentions = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.mentions\n  );\n  const voiceNotes = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes\n  );\n  const stickers = useConfig(\n      (state) => state.settings.chatFeatures.deeperUserEngagement.stickers\n  );\n  const sendPrivateMessageToGroupMembers = useConfig(\n      (state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers\n  );\n  const compactMessageComposer = useConfig(\n      (state) => state.settings.layout.compactMessageComposer\n  );\n\n  const loggedInUser = useRef<CometChat.User>(\n    CometChatUIKit.loggedInUser!,\n  ).current;\n\n  const [localUser, setLocalUser] = useState<CometChat.User | undefined>(\n    params?.user,\n  );\n\n  // ============================================\n  // Group Kicked/Banned Detection (real-time)\n  // ============================================\n  const isNoLongerMember = useGroupMemberStatus(group);\n\n  // keep listener ids unique\n  const userListenerId = 'thread_user_' + new Date().getTime();\n\n  // Fetch latest user on mount (if user present)\n  useEffect(() => {\n    let mounted = true;\n    const init = async () => {\n      try {\n        if (params?.user) {\n          const uid = params.user.getUid();\n          const fresh = await CometChat.getUser(uid);\n          if (mounted) setLocalUser(CommonUtils.clone(fresh));\n        }\n      } catch (err) {\n        console.error('Error fetching user in ThreadView:', err);\n      }\n    };\n    init();\n    return () => {\n      mounted = false;\n    };\n  }, [params?.user]);\n\n  // add UI listeners for block/unblock to update localUser\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (payload: { user: CometChat.User }) => {\n        setLocalUser(CommonUtils.clone(payload.user));\n      },\n      ccUserUnBlocked: (payload: { user: CometChat.User }) => {\n        setLocalUser(CommonUtils.clone(payload.user));\n      },\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n    };\n  }, []);\n\n  useEffect(() => {\n    CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n      alignment: 'composerBottom',\n      child: () => null,\n      panelId: 'sticker',\n    });\n  }, []);\n\n// B) Ensure back also asks to close (already using goBack())\nconst handleBack = useCallback(() => {\n  CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n    alignment: 'composerBottom',\n    child: () => null,\n    panelId: 'sticker',\n  });\n  navigation.goBack();\n  return true;\n}, [navigation]);\n\n  useFocusEffect(\n    useCallback(() => {\n      // Android hardware back -> just pop\n      const sub = BackHandler.addEventListener('hardwareBackPress', handleBack);\n\n      // iOS gesture/back button -> let default POP happen, but run side-effects\n      const unsub = navigation.addListener('beforeRemove', e => {\n        const type = e?.data?.action?.type;\n        if (type === 'GO_BACK' || type === 'POP') {\n          // IMPORTANT: do NOT e.preventDefault()\n          CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {\n            alignment: 'composerBottom',\n            child: () => null,\n            panelId: 'sticker',\n          });\n        }\n      });\n\n      return () => {\n        sub.remove();\n        unsub();\n      };\n    }, [navigation, handleBack]),\n  );\n\n  // Header back\n  <TouchableOpacity onPress={handleBack}>…</TouchableOpacity>;\n\n  // Keep gesture enabled\n  useLayoutEffect(() => {\n    navigation.setOptions?.({ gestureEnabled: true } as any);\n  }, [navigation]);\n  \n  const unblock = async (userToUnblock: CometChat.User) => {\n    try {\n      const uid = userToUnblock.getUid();\n      const response = await CometChat.unblockUsers([uid]);\n      if (response) {\n        const fresh = await CometChat.getUser(uid);\n        setLocalUser(CommonUtils.clone(fresh));\n        CometChatUIEventHandler.emitUserEvent(\n          CometChatUIEvents.ccUserUnBlocked,\n          {\n            user: fresh,\n          },\n        );\n      }\n    } catch (error) {\n      console.error('Error unblocking user from ThreadView:', error);\n    }\n  };\n\n  const getMentionsTap = useCallback(() => {\n    const mentionsFormatter =\n      ChatConfigurator.getDataSource().getMentionsFormatter(\n        loggedInUser,\n        theme,\n      );\n    if (user) mentionsFormatter.setUser(user);\n    if (group) mentionsFormatter.setGroup(group);\n\n    mentionsFormatter.setOnMentionClick(\n      (_message: CometChat.BaseMessage, uid: string) => {\n        if (uid !== loggedInUser.getUid()) {\n          CometChat.getUser(uid)\n            .then((mentionedUser: CometChat.User) => {\n              navigation.push('Messages', {\n                user: mentionedUser,\n                fromMention: true,\n              });\n            })\n            .catch((error: any) => {\n              console.error('Error fetching mentioned user:', error);\n            });\n        }\n      },\n    );\n    return mentionsFormatter;\n  }, [user, group, loggedInUser, navigation, theme]);\n\n  const threadHeaderMentionsFormatter = useMemo(\n    () => getMentionsTap(),\n    [getMentionsTap],\n  );\n\n  return (\n    <View style={{ backgroundColor: theme.color.background1, flex: 1 }}>\n      {/* Custom Header */}\n      <View style={styles.headerStyle}>\n        <TouchableOpacity style={styles.iconStyle} onPress={handleBack}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <View style={styles.textStyle}>\n          <Text\n            style={[\n              theme.typography.heading1.bold,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            {t('THREAD')}\n          </Text>\n          <Text\n            style={[\n              theme.typography.caption1.regular,\n              {\n                color: theme.color.textSecondary,\n                maxWidth: '90%',\n              },\n            ]}\n            numberOfLines={1}\n            ellipsizeMode=\"tail\"\n          >\n            {user ? user?.getName() : group?.getName()}\n          </Text>\n        </View>\n      </View>\n\n      {/* Thread Header */}\n      <CometChatThreadHeader\n        parentMessage={message}\n        receiptsVisibility={messageDeliveryAndReadReceipts}\n        textFormatters={[threadHeaderMentionsFormatter]}\n      />\n\n      {/* Threaded Message List */}\n      <View style={{ flex: 1 }}>\n        <CometChatMessageList\n          user={user}\n          group={group}\n          parentMessageId={message.getId().toString()}\n          textFormatters={[getMentionsTap()]}\n          receiptsVisibility={messageDeliveryAndReadReceipts}\n          hideReactionOption={!hideReactionOption}\n          hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}\n          goToMessageId={params?.highlightMessageId}\n        />\n      </View>\n\n      {/* Message Composer for Thread */}\n      {isNoLongerMember ? (\n        <View\n          style={{\n            paddingVertical: 12,\n            paddingHorizontal: 16,\n            backgroundColor: theme.color.background1 as string,\n          }}\n        >\n          <Text\n            style={[\n              theme.typography.body.regular,\n              {\n                color: theme.color.textPrimary as string,\n                textAlign: 'center',\n              },\n            ]}\n          >\n            {t('GROUP_NO_LONGER_MEMBER')}\n          </Text>\n        </View>\n      ) : localUser?.getBlockedByMe() ? (\n        <View\n          style={[\n            styles.blockedContainer,\n            { backgroundColor: theme.color.background3 },\n          ]}\n        >\n          <Text\n            style={[\n              theme.typography.button.regular,\n              {\n                color: theme.color.textSecondary,\n                textAlign: 'center',\n                paddingBottom: 10,\n              },\n            ]}\n          >\n            {t('BLOCKED_USER_DESC')}\n          </Text>\n          <TouchableOpacity\n            onPress={() => unblock(localUser)}\n            style={[styles.button, { borderColor: theme.color.borderDefault }]}\n          >\n            <Text\n              style={[\n                theme.typography.button.medium,\n                styles.buttontext,\n                {\n                  color: theme.color.textPrimary,\n                },\n              ]}\n            >\n              {t('UNBLOCK')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      ) : (\n        <CometChatCompactMessageComposer\n          user={localUser}\n          group={group}\n          parentMessageId={message.getId()}\n          onError={(error: any) => console.error('Composer Error:', error)}\n          keyboardAvoidingViewProps={\n            Platform.OS === 'android' ? {} : { behavior: 'padding' }\n          }\n          hideImageAttachmentOption={!photosSharing}\n          hideVideoAttachmentOption={!videoSharing}\n          hideAudioAttachmentOption={!audioSharing}\n          hideFileAttachmentOption={!fileSharing}\n          hideCameraOption={!photosSharing}\n          disableMentions={!mentions}\n          hideVoiceRecordingButton={!voiceNotes}\n          hideStickersButton={!stickers}\n        />\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  headerStyle: {\n    paddingVertical: 10,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  iconStyle: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  textStyle: {\n    paddingLeft: 10,\n    alignItems: 'flex-start',\n  },\n  blockedContainer: {\n    alignItems: 'center',\n    height: 90,\n    paddingVertical: 10,\n  },\n  button: {\n    flex: 1,\n    justifyContent: 'center',\n    borderWidth: 2,\n    width: '90%',\n    borderRadius: 8,\n  },\n  buttontext: {\n    paddingVertical: 5,\n    textAlign: 'center',\n    alignContent: 'center',\n  },\n});\n\nexport default ThreadView;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/TransferOwnership.tsx",
    "content": "import React, {useState} from 'react';\nimport {View, Text, TouchableOpacity} from 'react-native';\nimport {RouteProp} from '@react-navigation/native';\nimport {\n  CometChatGroupMembers,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {StackNavigationProp} from '@react-navigation/stack';\nimport {RootStackParamList} from '../../../navigation/types';\nimport {styles} from './TransferOwnershipStyles';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport {CommonUtils} from '../../../utils/CommonUtils';\n\ntype TransferOwnershipScreenProps = {\n  route: RouteProp<RootStackParamList, 'TransferOwnershipSection'>;\n  navigation: StackNavigationProp<\n    RootStackParamList,\n    'TransferOwnershipSection'\n  >;\n};\n\nconst TransferOwnership: React.FC<TransferOwnershipScreenProps> = ({\n  route,\n  navigation,\n}) => {\n  const {group} = route.params;\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [selectedOwnershipMember, setSelectedOwnershipMember] =\n    useState<CometChat.User | null>(null);\n\n  const handleBack = () => {\n    navigation.goBack();\n  };\n\n  // Function to leave the group after ownership transfer\n  const leaveGroup = (group: CometChat.Group) => {\n    CometChat.leaveGroup(group.getGuid())\n      .then(() => {\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          leftGroup: CommonUtils.clone(group),\n        });\n        navigation.pop(3);\n      })\n      .catch(error => {\n        console.log('Group leaving failed with exception:', error);\n      });\n  };\n\n  const handleTransferOwnership = async () => {\n    if (!selectedOwnershipMember || !group) return;\n    try {\n      await CometChat.transferGroupOwnership(\n        group.getGuid(),\n        selectedOwnershipMember.getUid(),\n      );\n      leaveGroup(group);\n    } catch (error) {\n      console.error('Ownership transfer failed:', error);\n    }\n  };\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {/* Header */}\n      <View style={styles.headerSection}>\n        <TouchableOpacity\n          style={styles.backButtonContainer}\n          onPress={handleBack}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.leftPaddingSmall,\n            {color: theme.color.textPrimary},\n          ]}>\n          {t('TRANSFER_OWNERSHIP')}\n        </Text>\n      </View>\n\n      {/* Group Members List */}\n      {group && (\n        <CometChatGroupMembers\n          group={group}\n          excludeOwner={true}\n          onBack={handleBack}\n          hideHeader={true}\n          selectionMode=\"single\"\n          onSelection={members => {\n            setSelectedOwnershipMember(\n              members && members.length > 0 ? members[0] : null,\n            );\n          }}\n        />\n      )}\n\n      {/* Transfer Ownership Button */}\n      <TouchableOpacity\n        onPress={handleTransferOwnership}\n        style={styles.transferButtonWrapper}>\n        <View\n          style={[\n            styles.transferButtonContent,\n            {backgroundColor: theme.color.primaryButtonBackground},\n          ]}>\n          <Text\n            style={[\n              theme.typography.heading4.medium,\n              styles.centerAligned,\n              {color: theme.color.primaryButtonText},\n            ]}>\n            {t('TRANSFER_OWNERSHIP')}\n          </Text>\n        </View>\n      </TouchableOpacity>\n    </View>\n  );\n};\n\nexport default TransferOwnership;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/TransferOwnershipStyles.tsx",
    "content": "import {StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  headerSection: {\n    paddingTop: 15,\n    paddingLeft: 10,\n    flexDirection: 'row',\n  },\n  backButtonContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  leftPaddingSmall: {\n    paddingLeft: 5,\n  },\n  transferButtonWrapper: {\n    alignContent: 'center',\n    justifyContent: 'center',\n    paddingVertical: 1,\n    height: '7%',\n    width: '100%',\n    alignSelf: 'center',\n  },\n  transferButtonContent: {\n    marginHorizontal: 20,\n    alignSelf: 'center',\n    justifyContent: 'center',\n    borderRadius: 6,\n    height: '75%',\n    width: '95%',\n  },\n  centerAligned: {\n    alignSelf: 'center',\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/UserInfo.tsx",
    "content": "import React, {useState, useEffect, useRef, FC} from 'react';\nimport {View, Text, TouchableOpacity, BackHandler} from 'react-native';\nimport {\n  CometChatAvatar,\n  useTheme,\n  CometChatUIEventHandler,\n  CallUIEvents,\n  CometChatConversationEvents,\n  CometChatConfirmDialog,\n  CometChatOutgoingCall,\n  useCometChatTranslation,\n  getLastSeenTime,\n} from '@cometchat/chat-uikit-react-native';\nimport {Icon} from '@cometchat/chat-uikit-react-native';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {permissionUtil} from '@cometchat/chat-uikit-react-native/src/shared/utils/PermissionUtil';\nimport {CallTypeConstants, UserStatusConstants} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport { blockUser, unblock } from '../../../utils/helper';\nimport {styles} from './UserInfoStyles';\nimport ArrowBack from '../../../assets/icons/ArrowBack';\nimport Block from '../../../assets/icons/Block';\nimport Delete from '../../../assets/icons/Delete';\nimport Videocam from '../../../assets/icons/VideoCam';\nimport Call from '../../../assets/icons/Call';\nimport {StackNavigationProp, StackScreenProps} from '@react-navigation/stack';\nimport {\n  RootStackParamList,\n} from '../../../navigation/types';\nimport {useFocusEffect} from '@react-navigation/native';\nimport { useConfig } from '../../../config/store';\n\n\ntype ScreenProps = StackScreenProps<RootStackParamList, 'UserInfo'>;\ntype NavigationProps = StackNavigationProp<RootStackParamList, 'UserInfo'>;\ntype Props = ScreenProps & {navigation: NavigationProps};\n\nconst UserInfo: FC<Props> = ({route, navigation}) => {\n  const {user} = route.params;\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [userObj, setUserObj] = useState<CometChat.User>(user);\n  /** STATES **/\n  const [disableButton, setDisableButton] = useState<boolean>(false);\n  const [blocked, setBlocked] = useState<boolean>(false);\n\n  // separate modal states\n  const [isBlockModalOpen, setBlockModalOpen] = useState(false);\n  const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);\n\n  const [userStatus, setUserStatus] = useState(userObj.getStatus());\n  const listenerId = useRef<string>('CallListener_' + Date.now());\n  const userStatusListenerId = 'user_status_' + new Date().getTime();\n\n  const [callObj, setCallObj] = useState<CometChat.Call>();\n\n  const callType = useRef<string | undefined>(undefined);\n  const oneOnOneVoiceCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling\n  );\n  const oneOnOneVideoCalling = useConfig(\n    (state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling\n  );\n\n  useEffect(() => {\n    setUserStatus(userObj.getStatus());\n    setBlocked(userObj.getBlockedByMe());\n\n    CometChat.addCallListener(\n      listenerId.current,\n      new CometChat.CallListener({\n        onIncomingCallReceived: () => {\n          setDisableButton(true);\n        },\n        onOutgoingCallAccepted: () => {\n          console.log('call accepted');\n        },\n        onOutgoingCallRejected: () => {\n          setDisableButton(false);\n          setCallObj(undefined);\n        },\n        onIncomingCallCancelled: () => {\n          setDisableButton(false);\n        },\n      }),\n    );\n    CometChatUIEventHandler.addCallListener(listenerId.current, {\n      ccCallRejected: () => {\n        setDisableButton(false);\n        setCallObj(undefined);\n      },\n      ccCallEnded: () => {\n        setDisableButton(false);\n        setCallObj(undefined);\n      },\n    });\n\n    CometChat.addUserListener(\n      userStatusListenerId,\n      new CometChat.UserListener({\n        onUserOnline: (onlineUser: CometChat.User) => {\n          if (onlineUser.getUid() === userObj.getUid()) {\n            setUserObj(onlineUser);\n            setUserStatus(onlineUser.getStatus());\n          }\n        },\n        onUserOffline: (offlineUser: CometChat.User) => {\n          if (offlineUser.getUid() === userObj.getUid()) {\n            setUserObj(offlineUser);\n            setUserStatus(offlineUser.getStatus());\n          }\n        },\n      }),\n    );\n\n    return () => {\n      CometChat.removeCallListener(listenerId.current);\n      CometChatUIEventHandler.removeCallListener(listenerId.current);\n      CometChat.removeUserListener(userStatusListenerId);\n    };\n  }, [userObj]);\n\n  const translations = {\n    lastSeen: 'Last seen',\n    minutesAgo: (minutes: number) =>\n      `${minutes} minute${minutes === 1 ? '' : 's'} ago`,\n    hoursAgo: (hours: number) => `${hours} hour${hours === 1 ? '' : 's'} ago`,\n  };\n\n  const makeVoiceCall = async (): Promise<void> => {\n    if (disableButton) return;\n    if (!(await permissionUtil.startResourceBasedTask(['mic']))) return;\n    callType.current = CallTypeConstants.audio;\n    makeCall(CallTypeConstants.audio);\n  };\n\n  const makeVideoCall = async (): Promise<void> => {\n    if (disableButton) return;\n    if (!(await permissionUtil.startResourceBasedTask(['mic', 'camera'])))\n      return;\n    callType.current = CallTypeConstants.video;\n    makeCall(CallTypeConstants.video);\n  };\n\n  const makeCall = (type: string): void => {\n    if (type === CallTypeConstants.audio || type === CallTypeConstants.video) {\n      const receiverID = userObj.getUid();\n      const callTypeValue = type;\n      const receiverType = CometChat.RECEIVER_TYPE.USER;\n      if (!receiverID || !receiverType) return;\n\n      const call = new CometChat.Call(\n        receiverID,\n        callTypeValue,\n        receiverType,\n        CometChat.CATEGORY_CALL,\n      );\n\n      CometChat.initiateCall(call).then(\n        initiatedCall => {\n          setCallObj(initiatedCall);\n          setDisableButton(true);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, {\n            call: initiatedCall,\n          });\n        },\n        error => {\n          console.log('Call initialization failed with exception:', error);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, {\n            call,\n          });\n        },\n      );\n    }\n  };\n\n  /** BLOCK/UNBLOCK LOGIC **/\n  const handleBlockUnblockConfirm = () => {\n    setBlockModalOpen(false); // close the dialog\n    if (blocked) {\n      // user is already blocked by me -> now unblocking\n      unblock(userObj.getUid(), userObj, setBlocked, setUserObj);\n    } else {\n      // user is not blocked -> blocking user\n      blockUser(userObj.getUid(), userObj, setBlocked);\n    }\n  };\n\n  /** DELETE CONVERSATION LOGIC **/\n  const handleDeleteConversationConfirm = () => {\n    setDeleteModalOpen(false); // close the dialog\n    if (userObj) {\n      CometChat.getConversation(userObj.getUid(), 'user')\n        .then(conversation => {\n          CometChat.deleteConversation(userObj.getUid(), 'user')\n            .then(deletedConversation => {\n              console.log(deletedConversation);\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccConversationDeleted,\n                {conversation: conversation},\n              );\n              navigation.pop(2);\n            })\n            .catch(error => {\n              console.log('Error while deleting conversation:', error);\n            });\n        })\n        .catch(error => {\n          console.log('Error while deleting conversation:', error);\n        });\n    }\n  };\n\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  return (\n    <View style={{flex: 1, backgroundColor: theme.color.background1}}>\n      {/* Header */}\n      <View style={styles.headerContainer}>\n        <TouchableOpacity\n          style={styles.backButtonContainer}\n          onPress={() => navigation.goBack()}>\n          <Icon\n            icon={\n              <ArrowBack\n                color={theme.color.iconPrimary}\n                height={24}\n                width={24}\n              />\n            }\n          />\n        </TouchableOpacity>\n        <Text\n          style={[\n            theme.typography.heading1.bold,\n            styles.smallPaddingLeft,\n            {color: theme.color.textPrimary},\n          ]}>\n          {t('USER_INFO')}\n        </Text>\n      </View>\n\n      {/* User Information Section */}\n      <View\n        style={[styles.profileCard, {borderColor: theme.color.borderLight}]}>\n        <View style={styles.profileInfo}>\n          <CometChatAvatar\n            style={{\n              containerStyle: styles.avatarContainer,\n              textStyle: styles.avatarText,\n              imageStyle: styles.avatarImage,\n            }}\n            image={\n              userObj?.getAvatar() ? {uri: userObj.getAvatar()} : undefined\n            }\n            name={userObj?.getName() ?? ''}\n          />\n          <Text\n            style={[\n              theme.typography.heading3.medium,\n              styles.mt10Centered,\n              {color: theme.color.textPrimary},\n            ]}>\n            {userObj?.getName()}\n          </Text>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.mt5Centered,\n              {color: theme.color.textSecondary},\n            ]}>\n            {userObj &&\n              !(userObj.getBlockedByMe() || userObj.getHasBlockedMe()) &&\n              (userStatus === UserStatusConstants.online\n                ? t('ONLINE')\n                : getLastSeenTime(userObj.getLastActiveAt()))}\n          </Text>\n        </View>\n\n        {/* Action Boxes */}\n        {(oneOnOneVoiceCalling || oneOnOneVideoCalling) && (\n          <View style={styles.actionsRow}>\n            {oneOnOneVoiceCalling && (\n              <TouchableOpacity\n                style={[\n                  styles.callActionButton,\n                  {borderColor: theme.color.borderDefault},\n                ]}\n                onPress={makeVoiceCall}>\n                <Icon\n                  icon={<Call color={theme.color.primary} height={24} width={24} />}\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.mt5Centered,\n                    { color: theme.color.textSecondary },\n                  ]}>\n                  {t('AUDIO_CALL')}\n                </Text>\n              </TouchableOpacity>\n            )}\n\n            {oneOnOneVideoCalling && (\n              <TouchableOpacity\n                style={[\n                  styles.callActionButton,\n                  {borderColor: theme.color.borderDefault},\n                ]}\n                onPress={makeVideoCall}>\n                <Icon\n                  icon={\n                    <Videocam color={theme.color.primary} height={24} width={24} />\n                  }\n                />\n                <Text\n                  style={[\n                    theme.typography.caption1.regular,\n                    styles.mt5Centered,\n                    { color: theme.color.textSecondary },\n                  ]}>\n                  {t('VIDEO_CALL')}\n                </Text>\n              </TouchableOpacity>\n            )}\n          </View>\n        )}\n      </View>\n\n      {/* Block/Unblock and Delete Chat Buttons */}\n      <View style={styles.optionsContainer}>\n        <View style={styles.optionRow}>\n          <TouchableOpacity\n            onPress={() => setBlockModalOpen(true)}\n            style={styles.backButtonContainer}>\n            <Icon\n              icon={<Block color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.ml5,\n                {color: theme.color.error},\n              ]}>\n              {blocked ? t('UNBLOCK') : t('BLOCK')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n\n        {/* DELETE CHAT */}\n        <View style={styles.optionRow}>\n          <TouchableOpacity\n            onPress={() => setDeleteModalOpen(true)}\n            style={styles.backButtonContainer}>\n            <Icon\n              icon={<Delete color={theme.color.error} height={24} width={24} />}\n            />\n            <Text\n              style={[\n                theme.typography.heading4.regular,\n                styles.ml5,\n                { color: theme.color.error },\n              ]}>\n              {t('DELETE_CHAT_TEXT')}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      </View>\n\n      {/* =============== BLOCK/UNBLOCK MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isBlockModalOpen}\n        onCancel={() => setBlockModalOpen(false)}\n        onConfirm={handleBlockUnblockConfirm}\n        onDismiss={() => console.log('Block/Unblock Modal dismissed')}\n        titleText={\n          blocked ? t('UNBLOCK_CONTACT') : t('BLOCK_USER')\n        }\n        messageText={\n          blocked ? t('UNBLOCK_SURE') : t('BLOCK_SURE')\n        }\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={blocked ? t('UNBLOCK') : t('BLOCK')}\n        icon={<Block color={theme.color.error} height={45} width={45} />}\n      />\n\n      {/* =============== DELETE CHAT MODAL =============== */}\n      <CometChatConfirmDialog\n        isOpen={isDeleteModalOpen}\n        onCancel={() => setDeleteModalOpen(false)}\n        onConfirm={handleDeleteConversationConfirm}\n        onDismiss={() => console.log('Delete Modal dismissed')}\n        titleText={t('DELETE_CHAT')}\n        messageText={t('SURE_TO_DELETE_CHAT')}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        icon={<Delete color={theme.color.error} height={45} width={45} />}\n      />\n\n      {callObj && (\n        <CometChatOutgoingCall\n          call={callObj}\n          onEndCallButtonPressed={call => {\n            CometChat.rejectCall(\n              call?.getSessionId(),\n              CometChat.CALL_STATUS.CANCELLED,\n            ).then(\n              rejectedCall => {\n                console.log('🚀 ~ rejectedCall:', rejectedCall);\n                CometChatUIEventHandler.emitCallEvent(\n                  CallUIEvents.ccCallRejected,\n                  {\n                    call: rejectedCall,\n                  },\n                );\n                setCallObj(undefined);\n              },\n              err => {\n                console.log('🚀 ~ err:', err);\n                setCallObj(undefined);\n                // onError && onError(err);\n              },\n            );\n          }}\n        />\n      )}\n    </View>\n  );\n};\n\nexport default UserInfo;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/UserInfoStyles.tsx",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n    headerContainer: {\n      paddingTop: 15,\n      paddingLeft: 10,\n      flexDirection: 'row',\n    },\n    backButtonContainer: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    smallPaddingLeft: {\n      paddingLeft: 5,\n    },\n    profileCard: {\n      marginTop: 12,\n      borderTopWidth: 1,\n      borderBottomWidth: 1,\n      paddingVertical: 10,\n    },\n    profileInfo: {\n      alignSelf: 'center',\n      alignItems: 'center',\n      marginTop: 20,\n    },\n    avatarContainer: {\n      height: 120,\n      width: 120,\n    },\n    avatarText: {\n      fontSize: 28,\n      lineHeight: 55,\n    },\n    avatarImage: {\n      height: '100%',\n      width: '100%',\n    },\n    mt10Centered: {\n      marginTop: 10,\n      alignSelf: 'center',\n    },\n    mt5Centered: {\n      marginTop: 5,\n      textAlign: 'center',\n      alignSelf: 'center',\n    },\n    actionsRow: {\n      flexDirection: 'row',\n      paddingHorizontal: 20,\n      justifyContent: 'space-between',\n      marginVertical: 20,\n    },\n    callActionButton: {\n      flex: 1,\n      paddingVertical: 10,\n      borderWidth: 1,\n      borderRadius: 8,\n      justifyContent: 'center',\n      alignItems: 'center',\n      marginHorizontal: 5,\n    },\n    optionsContainer: {\n      paddingTop: 10,\n      gap: 4,\n    },\n    optionRow: {\n      flexDirection: 'row',\n      alignItems: 'center',\n      paddingVertical: 8,\n      paddingLeft: 20,\n      width: '100%',\n    },\n    ml5: {\n      marginLeft: 5,\n    },\n  });\n  "
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/ViewMembers.tsx",
    "content": "import React from 'react';\nimport { BackHandler, View } from 'react-native';\nimport {\n  CometChatGroupMembers,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  useRoute,\n  useNavigation,\n  RouteProp,\n  useFocusEffect,\n} from '@react-navigation/native';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { useConfig } from '../../../config/store';\n\nconst ViewMembers: React.FC = () => {\n  const route = useRoute<RouteProp<RootStackParamList, 'ViewMembers'>>();\n  const navigation = useNavigation();\n  const { group } = route.params;\n  const theme = useTheme();\n  const kickUsers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.kickUsers\n  );\n  const banUsers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.banUsers\n  );\n  const promoteDemoteMembers =  useConfig(\n    (state) => state.settings.chatFeatures.moderatorControls.promoteDemoteMembers\n  );\n  const userAndFriendsPresence = useConfig(\n      (state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence\n   );\n\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        // Navigate back to message screen (same as your onPress handler)\n        navigation.goBack();\n        return true; // Prevent default back behavior\n      };\n\n      const subscription = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      return () => subscription.remove();\n    }, [navigation]),\n  );\n\n  return (\n    <View style={[{ flex: 1, backgroundColor: theme.color.background1 }]}>\n      <CometChatGroupMembers\n        group={group}\n        onBack={() => {\n          navigation.goBack();\n        }}\n        selectionMode=\"none\"\n        showBackButton={true}\n        hideKickMemberOption={!kickUsers}\n        hideBanMemberOption={!banUsers}\n        hideScopeChangeOption={!promoteDemoteMembers}\n        usersStatusVisibility={userAndFriendsPresence}\n      />\n    </View>\n  );\n};\n\nexport default ViewMembers;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/conversations/screens/qr_screen.tsx",
    "content": "import React, { useEffect, useState, useRef } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  Alert,\n  TouchableOpacity,\n  Image,\n} from 'react-native';\nimport { Camera, useCameraDevice, useCodeScanner } from 'react-native-vision-camera';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { useNavigation } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport { RootStackParamList } from '../../../navigation/types';\nimport { Animated, Easing } from 'react-native';\nimport Flash from '../../../assets/icons/Flash';\nimport sync from '../../../assets/icons/cometchat_sync_img.png';\n\ninterface ConfigData {\n  data: {\n    builderId: string;\n    settings: any;\n    name: string;\n    type: string;\n    createdAt: number;\n    updatedAt: number;\n    expiresAt: number;\n  };\n}\n\nconst CONFIG_STORAGE_KEY = '@app_config';\nconst MINIMUM_LOADING_TIME = 5000;\n\nconst QRScreen: React.FC = () => {\n  const device = useCameraDevice('back');\n  const [hasPermission, setHasPermission] = useState(false);\n  const [scannedCode, setScannedCode] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const [lineAnim] = useState(new Animated.Value(0));\n  const [isFlashOn, setIsFlashOn] = useState(false);\n  const [loadingProgress] = useState(new Animated.Value(0));\n  \n  const apiResponseReceived = useRef(false);\n  const loadingStartTime = useRef<number>(0);\n  \n  type ChatNavigationProp = StackNavigationProp<\n    RootStackParamList,\n    'Conversation'\n  >;\n  const navigation = useNavigation<ChatNavigationProp>();\n  \n  const codeScanner = useCodeScanner({\n    codeTypes: ['qr', 'ean-13'],\n    onCodeScanned: (codes) => {\n      if (codes.length > 0 && !isLoading && !scannedCode) {\n        const qrData = codes[0].value ?? '';\n        setScannedCode(qrData);\n        fetchDataFromAPI(qrData);\n      }\n    },\n  });\n\n  useEffect(() => {\n    Animated.loop(\n      Animated.sequence([\n        Animated.timing(lineAnim, {\n          toValue: 1,\n          duration: 2000,\n          easing: Easing.linear,\n          useNativeDriver: true,\n        }),\n        Animated.timing(lineAnim, {\n          toValue: 0,\n          duration: 2000,\n          easing: Easing.linear,\n          useNativeDriver: true,\n        }),\n      ])\n    ).start();\n  }, [lineAnim]);\n\n  const toggleFlash = () => {\n    setIsFlashOn(prev => !prev);\n  };\n\n  useEffect(() => {\n    const requestPermissions = async () => {\n      let cameraStatus = await Camera.getCameraPermissionStatus();\n\n      if (cameraStatus !== 'granted') {\n        cameraStatus = await Camera.requestCameraPermission();\n      }\n\n      if (cameraStatus === 'granted') {\n        setHasPermission(true);\n      } else {\n        Alert.alert(\n          'Camera permission denied',\n          'Please enable camera permission in your settings to scan QR codes'\n        );\n      }\n    };\n\n    requestPermissions();\n  }, []);\n\n  const saveConfigToStorage = async (configData: ConfigData) => {\n    try {\n      // Save to AsyncStorage\n      await AsyncStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(configData));\n      try {\n        const { useConfigStore } = await import('../../../config/store');\n        // Use the data property which contains the actual config structure\n        useConfigStore.setState({ config: configData.data });        \n        await AsyncStorage.setItem('@config_updated', 'true');\n      } catch (importError) {\n        console.log('Config store not available or failed to update:', importError);\n      }\n\n      return true;\n    } catch (error) {\n      console.error('Error saving config to AsyncStorage:', error);\n      throw error;\n    }\n  };\n\n  const finishLoading = () => {\n    setIsLoading(false);\n    navigation.goBack();\n  };\n\n  const fetchDataFromAPI = async (qrCodeData: string) => {\n    try {\n      setIsLoading(true);\n      apiResponseReceived.current = false;\n      loadingStartTime.current = Date.now();\n      \n      // Start progress bar animation (5 seconds to match minimum loading time)\n      loadingProgress.setValue(0);\n      Animated.timing(loadingProgress, {\n        toValue: 1,\n        duration: MINIMUM_LOADING_TIME,\n        easing: Easing.ease,\n        useNativeDriver: false,\n      }).start();\n      \n      // QR code contains the builder ID, construct the API URL\n      const builderId = qrCodeData.trim();\n      const apiUrl = `https://apivcb.cometchat.io/v1/builders/${builderId}`;\n      \n      const response = await fetch(apiUrl, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      });\n\n      if (!response.ok) {\n        throw new Error(`API request failed with status ${response.status}`);\n      }\n\n      const data: ConfigData = await response.json();\n      await saveConfigToStorage(data);\n      \n      apiResponseReceived.current = true;\n      \n      const elapsedTime = Date.now() - loadingStartTime.current;\n      const remainingTime = Math.max(0, MINIMUM_LOADING_TIME - elapsedTime);\n      \n      setTimeout(() => {\n        finishLoading();\n      }, remainingTime);\n      \n      return data;\n    } catch (error) {\n      console.error('Error fetching data from API:', error);\n      Alert.alert(\n        'Error',\n        `Failed to update configuration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n        [\n          {\n            text: 'OK',\n            onPress: () => {\n              setIsLoading(false);\n              setScannedCode(null);\n              apiResponseReceived.current = false;\n            }\n          }\n        ]\n      );\n    }\n  };\n\n\n\n  if (!device) {\n    return (\n      <View style={styles.center}>\n        <Text style={styles.centerText}>Loading camera...</Text>\n      </View>\n    );\n  }\n\n  if (!hasPermission) {\n    return (\n      <View style={styles.center}>\n        <Text style={styles.centerText}>No camera permission</Text>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Camera\n        style={StyleSheet.absoluteFill}\n        device={device}\n        isActive={!isLoading}\n        codeScanner={codeScanner}\n        torch={isFlashOn ? 'on' : 'off'}\n      />\n      \n      {/* Top Overlay */}\n      <View style={styles.topOverlay}>\n        {/* Header */}\n        <View style={styles.header}>\n          <TouchableOpacity \n            style={styles.closeButton} \n            onPress={() => navigation.goBack()}\n          >\n            <Text style={styles.closeIcon}>✕</Text>\n          </TouchableOpacity>\n          \n         <TouchableOpacity style={styles.flashButton} onPress={toggleFlash}>\n            <Flash color={'#FFFFFF'} width={24} height={24} />\n        </TouchableOpacity>\n\n        </View>\n\n        {/* Title Section */}\n        <View style={styles.titleSection}>\n          <Text style={styles.title}>Scan to Preview Your Chat UI</Text>\n          <Text style={styles.subtitle}>\n            Instantly load and test your configuration{'\\n'}from the Visual builder.\n          </Text>\n        </View>\n      </View>\n\n      {/* Simple full overlay approach */}\n      <View style={styles.overlayTop} />\n      <View style={styles.overlayLeft} />\n      <View style={styles.overlayRight} />\n      <View style={styles.overlayBottom} />\n\n      {/* Bottom Overlay */}\n      <View style={styles.bottomOverlay}>\n        {/* Instructions Section */}\n        <View style={styles.instructionsSection}>\n          <Text style={styles.instructionsTitle}>How to Use:</Text>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>1.</Text>\n            <Text style={styles.stepText}>Go to the Visual builder & generate QR code.</Text>\n          </View>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>2.</Text>\n            <Text style={styles.stepText}>Point your camera at the QR code to scan.</Text>\n          </View>\n          \n          <View style={styles.stepContainer}>\n            <Text style={styles.stepNumber}>3.</Text>\n            <Text style={styles.stepText}>Preview your design live on this device instantly.</Text>\n          </View>\n\n          {/* Note Section */}\n          <View style={styles.noteSection}>\n            <Text style={styles.noteTitle}>Note:</Text>\n            <Text style={styles.noteText}>\n              Make sure to save changes on the builder before scanning again to view updates.\n            </Text>\n          </View>\n        </View>\n      </View>\n\n      {/* Camera Container Border */}\n      \n      <View style={styles.cameraBorder}>\n        <Animated.View\n          style={[\n            styles.animatedLine,\n            {\n              transform: [\n                {\n                  translateY: lineAnim.interpolate({\n                    inputRange: [0, 1],\n                    outputRange: [0, 370], \n                  }),\n                },\n              ],\n            },\n          ]}\n        />\n      </View>\n\n      {/* Loading Overlay */}\n      {isLoading && (\n        <View style={styles.loadingOverlay}>\n          <View style={styles.loadingContent}>\n            <Image \n              source={sync} \n              style={styles.syncImage}\n              resizeMode=\"contain\"\n            />\n            <Text style={styles.loadingText}>Syncing with Visual Builder...</Text>\n            <View style={styles.progressBarContainer}>\n              <Animated.View \n                style={[\n                  styles.progressBarFill,\n                  {\n                    width: loadingProgress.interpolate({\n                      inputRange: [0, 1],\n                      outputRange: ['0%', '100%'],\n                    }),\n                  },\n                ]}\n              />\n            </View>\n          </View>\n        </View>\n      )}\n\n      {/* Scanned Code Display */}\n      {scannedCode && !isLoading && (\n        <View style={styles.resultBox}>\n          <Text style={styles.resultLabel}>Scanned:</Text>\n          <Text style={styles.resultText} numberOfLines={2}>\n            {scannedCode}\n          </Text>\n        </View>\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: { \n    flex: 1, \n    backgroundColor: 'black' ,\n  },\n  center: { \n    flex: 1, \n    alignItems: 'center', \n    justifyContent: 'center', \n    backgroundColor: 'black'\n  },\n  centerText: {\n    color: 'white',\n    fontSize: 16,\n  },\n  topOverlay: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    height: 180,\n    zIndex: 3,\n  },\n  overlayTop: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    height: 180,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayLeft: {\n    position: 'absolute',\n    top: 180,\n    left: 0,\n    width: 20,\n    height: 370,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayRight: {\n    position: 'absolute',\n    top: 180,\n    right: 0,\n    width: 20,\n    height: 370,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  overlayBottom: {\n    position: 'absolute',\n    top: 550,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 1,\n  },\n  animatedLine: {\n    position: 'absolute',\n    left: 0,\n    right: 0,\n    height: 3,\n    backgroundColor: '#6366f1',\n    borderRadius: 2,\n  },\n  bottomOverlay: {\n    position: 'absolute',\n    bottom: 0,\n    left: 0,\n    right: 0,\n    top: 560,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    zIndex: 3,\n  },\n  cameraBorder: {\n    position: 'absolute',\n    top: 180,\n    left: 20,\n    right: 20,\n    height: 370,\n    zIndex: 4,\n  },\n  header: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    paddingTop: 20,\n    paddingBottom: 20,\n  },\n  closeButton: {\n    width: 40,\n    height: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  closeIcon: {\n    color: '#FFFFFF',\n    fontSize: 24,\n    fontWeight: 'bold',\n  },\n  flashButton: {\n    width: 40,\n    height: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  flashIcon: {\n    fontSize: 24,\n  },\n  titleSection: {\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    marginBottom: 30,\n  },\n  title: {\n    color: '#FFFFFF',\n    fontSize: 24,\n    fontWeight: 'bold',\n    textAlign: 'center',\n    marginBottom: 12,\n  },\n  subtitle: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    textAlign: 'center',\n    lineHeight: 22,\n  },\n  instructionsSection: {\n    flex: 1,\n    paddingHorizontal: 20,\n    paddingBottom: 40,\n  },\n  instructionsTitle: {\n    color: '#FFFFFF',\n    fontSize: 18,\n    fontWeight: 'bold',\n    marginBottom: 16,\n  },\n  stepContainer: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n    marginBottom: 12,\n  },\n  stepNumber: {\n    color: '#FFFFFF',\n    fontSize: 16,\n    fontWeight: '600',\n    marginRight: 8,\n    minWidth: 20,\n  },\n  stepText: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    lineHeight: 24,\n    flex: 1,\n  },\n  noteSection: {\n    backgroundColor: '#0B7BEA33',\n    borderRadius: 8,\n    padding: 16,\n    marginTop: 20,\n    borderLeftWidth: 4,\n  },\n  noteTitle: {\n    color: '#CCCCCC',\n    fontSize: 16,\n    fontWeight: 'bold',\n    marginBottom: 8,\n  },\n  noteText: {\n    color: '#CCCCCC',\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  resultBox: {\n    position: 'absolute',\n    bottom: 130,\n    alignSelf: 'center',\n    padding: 12,\n    backgroundColor: 'rgba(0,0,0,0.8)',\n    borderRadius: 8,\n    maxWidth: '80%',\n    borderWidth: 1,\n    borderColor: '#6366f1',\n    zIndex: 2,\n  },\n  resultLabel: {\n    color: '#6366f1',\n    fontSize: 12,\n    fontWeight: 'bold',\n    marginBottom: 4,\n  },\n  resultText: { \n    color: 'white', \n    fontSize: 14 \n  },\n  loadingOverlay: {\n    ...StyleSheet.absoluteFillObject,\n    backgroundColor: 'rgba(0,0,0,0.9)',\n    justifyContent: 'center',\n    alignItems: 'center',\n    zIndex: 999,\n  },\n  loadingContent: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    width: '100%',\n  },\n  syncImage: {\n    width: 300,\n    height: 455,\n    marginBottom: 24,\n  },\n  loadingText: {\n    color: '#000000',\n    fontSize: 18,\n    fontWeight: '600',\n    marginBottom: 24,\n  },\n  progressBarContainer: {\n    width: 280,\n    height: 8,\n    backgroundColor: '#E5E7EB',\n    borderRadius: 4,\n    overflow: 'hidden',\n  },\n  progressBarFill: {\n    height: '100%',\n    backgroundColor: '#6366f1',\n    borderRadius: 4,\n  },\n});\n\nexport default QRScreen;"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/groups/GroupHelper.tsx",
    "content": "import React, {useEffect, useState} from 'react';\nimport {\n  Dimensions,\n  View,\n  Text,\n  TouchableOpacity,\n  TextInput,\n  TouchableWithoutFeedback,\n} from 'react-native';\nimport {\n  CometChatAvatar,\n  CometChatBottomSheet,\n  CometChatUIKitHelper,\n  Icon,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport {styles} from './styles';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport GroupAdd from '../../assets/icons/GroupAdd';\nimport Group from '../../assets/icons/Group';\n\n/**\n * Subcomponent for the default AppBar Options\n * (e.g., the \"Add Group\" button/icon on the header).\n */\nexport const GroupScreenAppBarOptions: React.FC<{\n  onPress: () => void;\n}> = ({onPress}) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n\n  return (\n    <View style={{paddingRight: 10}}>\n      <TouchableOpacity onPress={onPress}>\n        <Icon\n          icon={<GroupAdd color={theme.color.primary} height={28} width={28} />}\n          size={28}\n        />\n      </TouchableOpacity>\n    </View>\n  );\n};\n\n/**\n * Bottom sheet for creating a new group.\n */\ninterface CreateGroupBottomSheetProps {\n  visible: boolean;\n  onClose: () => void;\n  onGroupCreated: (group: CometChat.Group) => void;\n}\n\nexport const CreateGroupBottomSheet: React.FC<CreateGroupBottomSheetProps> = ({\n  visible,\n  onClose,\n  onGroupCreated,\n}) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n\n  // Group state\n  const [groupName, setGroupName] = useState('');\n  const [groupPassword, setGroupPassword] = useState('');\n  const [selectedOption, setSelectedOption] = useState('Public');\n  const [showPasswordField, setShowPasswordField] = useState(false);\n  const [showError, setShowError] = useState('');\n  const groupTypes = [\n    { display: t('PUBLIC'), value: 'Public' },\n    { display: t('PRIVATE'), value: 'Private' },\n    { display: t('PASSWORD'), value: 'Password' }\n  ];\n\n  const resetFields = () => {\n    setGroupName('');\n    setGroupPassword('');\n    setSelectedOption('Public');\n    setShowPasswordField(false);\n    setShowError('');\n  };\n\n  const handleDismiss = () => {\n    resetFields(); // clear everything the user typed/selected\n    onClose(); // let the parent know the sheet closed\n  };\n\n  useEffect(() => {\n    if (!showError) return;\n    const timer = setTimeout(() => setShowError(''), 3000);\n    return () => clearTimeout(timer);\n  }, [showError]);\n\n  const handleOptionPress = (option: { display: string, value: string }) => {\n    setSelectedOption(option.value);\n    setShowPasswordField(option.value === 'Password');\n  };\n\n  const handleCreateGroup = async () => {\n    if (!groupName.trim()) {\n      setShowError('Group name cannot be empty');\n      return;\n    }\n    const GUID = 'group_' + Date.now();\n    let groupType = CometChat.GROUP_TYPE.PUBLIC;\n    let password = '';\n\n    switch (selectedOption) {\n      case 'Private':\n        groupType = CometChat.GROUP_TYPE.PRIVATE;\n        break;\n      case 'Password':\n        groupType = CometChat.GROUP_TYPE.PASSWORD;\n        break;\n      default:\n        groupType = CometChat.GROUP_TYPE.PUBLIC;\n    }\n\n    if (groupType === CometChat.GROUP_TYPE.PASSWORD) {\n      if (!groupPassword.trim()) {\n        setShowError('Password is mandatory for password-protected groups');\n        return;\n      }\n      password = groupPassword;\n    }\n\n    const newGroup = new CometChat.Group(GUID, groupName, groupType, password);\n\n    try {\n      const createdGroup = await CometChat.createGroup(newGroup);\n      CometChatUIKitHelper.onGroupCreated(createdGroup);\n      onGroupCreated(createdGroup);\n\n      // Reset fields after creation\n      resetFields();\n      onClose();\n    } catch (error) {\n      console.log('Group creation failed with exception:', error);\n    }\n  };\n\n  return (\n    <CometChatBottomSheet\n      isOpen={visible}\n      onClose={handleDismiss}\n      doNotOccupyEntireHeight={true}\n      scrollEnabled={true}\n      style={{maxHeight: Dimensions.get('window').height * 0.8}}>\n      <View style={styles.bottomSheetContainer}>\n        {/* Header / Icon */}\n        <View style={{alignItems: 'center'}}>\n          <View\n            style={[\n              styles.avatarIconContainer,\n              {backgroundColor: theme.color.background3},\n            ]}>\n            <Icon\n              icon={\n                <Group color={theme.color.primary} height={44} width={44} />\n              }\n              size={44}\n            />\n          </View>\n          <Text\n            style={[\n              {color: theme.color.textPrimary, marginTop: 15},\n              theme.typography.heading2.regular,\n            ]}>\n            {t('NEW__GROUP')}\n          </Text>\n        </View>\n\n        {/* Group Type Tabs */}\n        <View style={{marginVertical: 20}}>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              {marginBottom: 10, color: theme.color.textPrimary},\n            ]}>\n            {t('TYPE')}\n          </Text>\n          <View\n            style={[\n              styles.optionTabsContainer,\n              {\n                borderColor: theme.color.borderLight,\n                backgroundColor: theme.color.background3,\n              },\n            ]}>\n            {groupTypes.map(option => {\n              const isSelected = selectedOption === option.value;\n              return (\n                <TouchableOpacity\n                  key={option.value}\n                  onPress={() => handleOptionPress(option)}\n                  style={{\n                    flex: 1,\n                    alignItems: 'center',\n                    paddingVertical: 8,\n                    borderRadius: 12,\n                    backgroundColor: isSelected\n                      ? theme.color.background1\n                      : 'transparent',\n                  }}>\n                  <Text\n                    style={[\n                      theme.typography.body.medium,\n                      {\n                        color: isSelected\n                          ? theme.color.textHighlight\n                          : theme.color.textSecondary,\n                        fontWeight: isSelected ? 'bold' : 'normal',\n                      },\n                    ]}>\n                    {option.display}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n\n        {/* Group Name Input */}\n        <View style={styles.marginBottom20}>\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              {marginBottom: 10, color: theme.color.textPrimary},\n            ]}>\n            {t('NAME')}\n          </Text>\n          <View\n            style={[\n              styles.passwordInputContainer,\n              {\n                borderColor: theme.color.borderLight,\n                backgroundColor: theme.color.background3,\n              },\n            ]}>\n            <TextInput\n              value={groupName}\n              onChangeText={setGroupName}\n              style={[\n                theme.typography.body.regular,\n                {\n                  flex: 1,\n                  color: theme.color.textPrimary,\n                },\n              ]}\n              placeholder={t(\"ENTER_GROUP_NAME\")}\n              placeholderTextColor={theme.color.textTertiary}\n            />\n          </View>\n        </View>\n\n        {/* Group Password (if needed) */}\n        {showPasswordField && (\n          <View style={styles.marginBottom20}>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginBottom: 10, color: theme.color.textPrimary},\n              ]}>\n              {t('PASSWORD')}\n            </Text>\n            <View\n              style={[\n                styles.passwordInputContainer,\n                {\n                  borderColor: theme.color.borderLight,\n                  backgroundColor: theme.color.background3,\n                },\n              ]}>\n              <TextInput\n                value={groupPassword}\n                onChangeText={setGroupPassword}\n                style={[\n                  theme.typography.body.regular,\n                  {flex: 1, color: theme.color.textPrimary},\n                ]}\n                placeholder={t(\"ENTER_PASSWORD\")}\n                placeholderTextColor={theme.color.textTertiary}\n                secureTextEntry\n              />\n            </View>\n          </View>\n        )}\n\n        {showError !== '' && (\n          <View style={styles.toastContainer}>\n            <Text style={[styles.toastMessage, {color: theme.color.error}]}>\n              {showError}\n            </Text>\n          </View>\n        )}\n\n        {/* Create Group Button */}\n        <TouchableWithoutFeedback onPress={handleCreateGroup}>\n          <View\n            style={[\n              styles.createButton,\n              {backgroundColor: theme.color.primaryButtonBackground},\n            ]}>\n            <Text\n              style={[\n                theme.typography.button.medium,\n                {color: theme.color.primaryButtonText},\n              ]}>\n              {t('CREATE_GROUP')}\n            </Text>\n          </View>\n        </TouchableWithoutFeedback>\n      </View>\n    </CometChatBottomSheet>\n  );\n};\n\n/**\n * Bottom sheet for joining a password-protected group.\n */\ninterface JoinGroupBottomSheetProps {\n  visible: boolean;\n  groupToJoin: CometChat.Group | null;\n  onClose: () => void;\n  onJoinSuccess: (joinedGroup: CometChat.Group) => void;\n}\n\nexport const JoinGroupBottomSheet: React.FC<JoinGroupBottomSheetProps> = ({\n  visible,\n  groupToJoin,\n  onClose,\n  onJoinSuccess,\n}) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n  const [enteredPassword, setEnteredPassword] = useState('');\n  const [isPasswordErrorVisible, setIsPasswordErrorVisible] = useState(false);\n\n  useEffect(() => {\n    if (!isPasswordErrorVisible) return;\n    const timer = setTimeout(() => setIsPasswordErrorVisible(false), 3000);\n    return () => clearTimeout(timer);\n  }, [isPasswordErrorVisible]);\n\n  const joinPasswordGroup = async () => {\n    if (!groupToJoin || !enteredPassword.trim()) return;\n\n    try {\n      const joinedGroup = await CometChat.joinGroup(\n        groupToJoin.getGuid(),\n        groupToJoin.getType() as CometChat.GroupType,\n        enteredPassword,\n      );\n      onJoinSuccess(joinedGroup);\n      handleClose();\n    } catch (error) {\n      setIsPasswordErrorVisible(true);\n      console.log('Error joining password group:', error);\n    }\n  };\n\n  const handleClose = () => {\n    setEnteredPassword('');\n    setIsPasswordErrorVisible(false);\n    onClose();\n  };\n\n  return (\n    <CometChatBottomSheet\n      isOpen={visible}\n      onClose={handleClose}\n      scrollEnabled={true}\n      doNotOccupyEntireHeight={true}>\n      {groupToJoin && (\n        <View style={styles.joiningGroup}>\n          {/* Header */}\n          <Text\n            style={[\n              theme.typography.heading3.bold,\n              {\n                marginBottom: 20,\n                textAlign: 'center',\n                color: theme.color.textPrimary,\n              },\n            ]}>\n            {t('JOIN_GROUP')}\n          </Text>\n\n          {/* Group Info */}\n          <View style={{alignItems: 'center', marginBottom: 20}}>\n            <CometChatAvatar\n              name={groupToJoin.getName()}\n              image={{uri: groupToJoin.getIcon()}}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                {marginTop: 10, color: theme.color.textPrimary},\n              ]}>\n              {groupToJoin.getName()}\n            </Text>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginTop: 5, color: theme.color.textSecondary},\n              ]}>\n              {groupToJoin.getMembersCount()} {t('MEMBERS')}\n            </Text>\n          </View>\n\n          {/* Password Input */}\n          <View style={styles.marginBottom20}>\n            <Text\n              style={[\n                theme.typography.caption1.medium,\n                {marginBottom: 10, color: theme.color.textPrimary},\n              ]}>\n              {t('ENTER_PASSWORD')}\n            </Text>\n            <View\n              style={[\n                styles.passwordInputContainer,\n                {\n                  borderColor: theme.color.borderLight,\n                  backgroundColor: theme.color.background3,\n                },\n              ]}>\n              <TextInput\n                value={enteredPassword}\n                onChangeText={setEnteredPassword}\n                style={[\n                  theme.typography.body.regular,\n                  {flex: 1, color: theme.color.textPrimary},\n                ]}\n                placeholder={t('ENTER_PASSWORD')}\n                placeholderTextColor={theme.color.textTertiary}\n                secureTextEntry\n              />\n            </View>\n          </View>\n\n          {/* Incorrect Password Toast */}\n          {isPasswordErrorVisible && (\n            <View style={styles.toastContainer}>\n              <Text style={[styles.toastMessage, {color: theme.color.error}]}>\n                {t('PASSWORD_INCORRECT_GROUP')}\n              </Text>\n            </View>\n          )}\n\n          {/* Join Button */}\n          <TouchableWithoutFeedback onPress={joinPasswordGroup}>\n            <View\n              style={[\n                styles.joinGroupButton,\n                {backgroundColor: theme.color.primaryButtonBackground},\n              ]}>\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  {color: theme.color.primaryButtonText},\n                ]}>\n                {t('JOIN_GROUP')}\n              </Text>\n            </View>\n          </TouchableWithoutFeedback>\n        </View>\n      )}\n    </CometChatBottomSheet>\n  );\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/groups/Groups.tsx",
    "content": "import React, { useCallback, useEffect, useState } from 'react';\nimport { View } from 'react-native';\nimport { useFocusEffect, useNavigation } from '@react-navigation/native';\nimport { StackNavigationProp } from '@react-navigation/stack';\nimport {\n  CometChatGroups,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { RootStackParamList } from '../../navigation/types';\nimport { styles } from './styles';\n\nimport {\n  GroupScreenAppBarOptions,\n  CreateGroupBottomSheet,\n  JoinGroupBottomSheet,\n} from './GroupHelper';\nimport {SCREEN_CONSTANTS} from '../../utils/AppConstants';\nimport { useConfig } from '../../config/store';\n\ntype GroupNavigationProp = StackNavigationProp<RootStackParamList, 'Groups'>;\n\ninterface GroupsProps {\n  hideHeader?: boolean;\n}\n\nconst Groups: React.FC<GroupsProps> = ({hideHeader = false}) => {\n  const createGroup = useConfig(\n    (state) => state.settings.chatFeatures.groupManagement.createGroup\n  );\n  const theme = useTheme();\n  const navigation = useNavigation<GroupNavigationProp>();\n  const [pendingChat, setPendingChat] = useState<CometChat.Group | null>(null);\n\n  // State to handle showing/hiding bottom sheets\n  const [isCreateGroupSheetVisible, setCreateGroupSheetVisible] =\n    useState(false);\n  const [isJoinGroupSheetVisible, setJoinGroupSheetVisible] = useState(false);\n\n  // State for the group that user wants to join\n  const [groupToJoin, setGroupToJoin] = useState<CometChat.Group | null>(null);\n\n  // Condition to hide the entire screen if needed\n  const [shouldHide, setShouldHide] = useState(false);\n\n  useEffect(() => {\n    if (!isCreateGroupSheetVisible && !isJoinGroupSheetVisible && pendingChat) {\n      const timer = setTimeout(() => {\n        navigation.navigate(SCREEN_CONSTANTS.MESSAGES, { group: pendingChat });\n        setPendingChat(null);\n      }, 300);\n      return () => clearTimeout(timer);\n    }\n  }, [isCreateGroupSheetVisible, isJoinGroupSheetVisible, pendingChat]);\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, [navigation]),\n  );\n\n  /**\n   * Navigates to the Messages screen after group creation or join.\n   */\n  const handleNavigateToMessages = (group: CometChat.Group) => {\n    // close any open sheet first\n    setCreateGroupSheetVisible(false);\n    setJoinGroupSheetVisible(false);\n\n    // save the group – navigation will happen in a useEffect below\n    setPendingChat(group);\n  };\n\n  /**\n   * Handle group item press:\n   * - If joined, open chat\n   * - If public, join automatically\n   * - If password, show the join modal\n   */\n  const handleGroupItemPress = (group: CometChat.Group) => {\n    if (group.getHasJoined()) {\n      handleNavigateToMessages(group);\n      return;\n    }\n\n    if (group.getType() === CometChat.GROUP_TYPE.PUBLIC) {\n      joinPublicGroup(group);\n    } else if (group.getType() === CometChat.GROUP_TYPE.PASSWORD) {\n      setGroupToJoin(group);\n      setJoinGroupSheetVisible(true);\n    }\n    // For private group, you'd have a different flow.\n  };\n\n  const joinPublicGroup = async (group: CometChat.Group) => {\n    try {\n      const joinedGroup = await CometChat.joinGroup(\n        group.getGuid(),\n        group.getType() as CometChat.GroupType,\n        '',\n      );\n\n      handleNavigateToMessages(joinedGroup);\n      CometChatUIEventHandler.emitGroupEvent(\n        CometChatUIEvents.ccGroupMemberJoined,\n        {\n          joinedUser: CometChatUIKit.loggedInUser,\n          joinedGroup: joinedGroup,\n        },\n      );\n    } catch (error) {\n      console.log('Error joining public group:', error);\n    }\n  };\n\n  if (shouldHide) return null;\n\n  return (\n    <View\n      style={[\n        styles.safeAreaContainer,\n        { backgroundColor: theme.color.background1 },\n      ]}\n    >\n      {/* CometChatGroups list component */}\n      <CometChatGroups\n        AppBarOptions={\n          createGroup\n            ? () => (\n              <GroupScreenAppBarOptions\n                onPress={() => setCreateGroupSheetVisible(true)}\n              />\n            )\n            : undefined\n        }\n        onItemPress={handleGroupItemPress}\n        hideHeader={hideHeader}\n      />\n\n      {/* Create Group Bottom Sheet */}\n      <CreateGroupBottomSheet\n        visible={isCreateGroupSheetVisible}\n        onClose={() => setCreateGroupSheetVisible(false)}\n        onGroupCreated={handleNavigateToMessages}\n      />\n\n      {/* Join Group Bottom Sheet */}\n      <JoinGroupBottomSheet\n        visible={isJoinGroupSheetVisible}\n        groupToJoin={groupToJoin}\n        onClose={() => {\n          setJoinGroupSheetVisible(false);\n          setGroupToJoin(null);\n        }}\n        onJoinSuccess={handleNavigateToMessages}\n      />\n    </View>\n  );\n};\n\nexport default Groups;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/groups/styles.ts",
    "content": "import {Platform, StyleSheet} from 'react-native';\n\nexport const styles = StyleSheet.create({\n  safeAreaContainer: {\n    flex: 1,\n  },\n  bottomSheetContainer: {\n    paddingHorizontal: 20,\n    paddingBottom: Platform.OS==='android'  ? 10: 30,\n    flex: 1,\n  },\n  marginBottom20: {\n    marginBottom: 20,\n  },\n  toastContainer: {\n    alignItems: 'flex-start',\n  },\n  toastMessage: {\n    paddingVertical: 10,\n  },\n  passwordInputContainer: {\n    borderWidth: 1,\n    flexDirection: 'row',\n    borderRadius: 12,\n    paddingLeft: 10,\n    paddingTop: Platform.select({android: 0, ios: 8}),\n    paddingBottom: Platform.select({android: 0, ios: 12}),\n  },\n  avatarIconContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n    width: 80,\n    height: 80,\n    borderRadius: 50,\n  },\n  optionTabsContainer: {\n    borderWidth: 1,\n    flexDirection: 'row',\n    borderRadius: 12,\n    padding: Platform.OS === 'ios' ? 5 : 2,\n    justifyContent: 'space-between',\n    alignItems: 'center',\n  },\n  createButton: {\n    borderRadius: 8,\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingHorizontal: 20,\n    paddingVertical: 12,\n  },\n  joinGroupButton: {\n    borderRadius: 8,\n    alignItems: 'center',\n    justifyContent: 'center',\n    paddingHorizontal: 20,\n    paddingVertical: 12,\n  },\n  joiningGroup: {\n    paddingHorizontal: 20,\n    flex: 1,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/login/AppCredentials.tsx",
    "content": "import React, { useEffect, useMemo, useState } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TextInput,\n  TouchableOpacity,\n  Image,\n  useColorScheme,\n  Platform,\n  StatusBar,\n  Dimensions,\n  ScrollView,\n  KeyboardAvoidingView,\n  BackHandler,\n  Keyboard,\n} from 'react-native';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport {\n  CometChatUIKit,\n  UIKitSettings,\n  useCometChatTranslation,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport { navigate, navigationRef } from '../../navigation/NavigationService';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useFocusEffect } from '@react-navigation/native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { useHeaderHeight } from '@react-navigation/elements';\n\nconst AppCredentials: React.FC = () => {\n  const [storedAppId, setStoredAppId] = useState<string>('');\n  const [storedAuthKey, setStoredAuthKey] = useState<string>('');\n  const [storedRegion, setStoredRegion] = useState<string>('US');\n\n  // These are the *editable* states bound to the TextInput fields\n  const [appId, setAppId] = useState<string>('');\n  const [authKey, setAuthKey] = useState<string>('');\n  const [selectedRegion, setSelectedRegion] = useState<string>('US');\n\n  // Toast state for showing error messages\n  const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const { width } = Dimensions.get('window');\n  const mode = useColorScheme();\n\n  const insets = useSafeAreaInsets();\n  const headerHeight = useHeaderHeight(); // returns 0 if no header\n  const statusBarHeight =\n    Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;\n\n  const [keyboardBehavior, setKeyboardBehavior] = useState<\n    'padding' | 'height' | undefined\n  >(Platform.OS === 'ios' ? 'padding' : 'height');\n\n  useEffect(() => {\n    const showListener = Keyboard.addListener('keyboardDidShow', () => {\n      setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');\n    });\n\n    const hideListener = Keyboard.addListener('keyboardDidHide', () => {\n      setKeyboardBehavior(undefined); // Remove behavior when keyboard hides\n    });\n\n    return () => {\n      showListener.remove();\n      hideListener.remove();\n    };\n  }, []);\n  // For iOS use insets.top (not status bar) + header height\n  const keyboardVerticalOffset = useMemo(() => {\n  return Platform.OS === 'ios'\n    ? (insets.top || 0) + (headerHeight || 0)\n    : (statusBarHeight || 0) + (headerHeight || 0);\n}, [insets.top, headerHeight, statusBarHeight]);\n\n  // Compute if form is valid (all fields provided)\n  const isFormValid =\n    appId.trim().length > 0 &&\n    authKey.trim().length > 0 &&\n    selectedRegion.trim().length > 0;\n\n  // Load existing credentials if any\n  useFocusEffect(\n    React.useCallback(() => {\n      const onBackPress = () => {\n        navigationRef.goBack();\n        return true; // Prevent default behavior\n      };\n      const backHandler = BackHandler.addEventListener(\n        'hardwareBackPress',\n        onBackPress,\n      );\n\n      async function loadCredentials() {\n        try {\n          const credentialsStr = await AsyncStorage.getItem('appCredentials');\n          if (credentialsStr) {\n            const credentials = JSON.parse(credentialsStr);\n            // Update 'stored' states\n            setStoredAppId(credentials.appId || '');\n            setStoredAuthKey(credentials.authKey || '');\n            setStoredRegion(credentials.region || 'US');\n\n            // Also set the fields so user sees them pre-populated\n            setAppId(credentials.appId || '');\n            setAuthKey(credentials.authKey || '');\n            setSelectedRegion(credentials.region || 'US');\n          }\n        } catch (error) {\n          console.log('Error loading stored credentials:', error);\n        }\n      }\n\n      loadCredentials();\n\n      return () => backHandler.remove();\n    }, []),\n  );\n\n  const showToast = (message: string) => {\n    setToastMessage(message);\n    setTimeout(() => {\n      setToastMessage(null);\n    }, 2500);\n  };\n\n  const handleContinue = async (): Promise<void> => {\n    // Validate the inputs.\n    // If the user has modified a field and cleared it (i.e. the input becomes empty),\n    // show a toast message.\n    if (!appId.trim()) {\n      showToast('Please enter App ID');\n      return;\n    }\n    if (!authKey.trim()) {\n      showToast('Please enter Auth Key');\n      return;\n    }\n    if (!selectedRegion.trim()) {\n      showToast('Please select a region');\n      return;\n    }\n\n    // Since all fields are non-empty, use their current values.\n    const newAppId = appId.trim();\n    const newAuthKey = authKey.trim();\n    const newRegion = selectedRegion.trim();\n\n    try {\n      const credentials = {\n        region: newRegion,\n        appId: newAppId,\n        authKey: newAuthKey,\n      };\n      console.log('Saving credentials:', credentials);\n      await AsyncStorage.setItem('appCredentials', JSON.stringify(credentials));\n\n      // Re-initialize with updated credentials\n      await CometChatUIKit.init({\n        appId: newAppId,\n        authKey: newAuthKey,\n        region: newRegion,\n        subscriptionType: CometChat.AppSettings\n          .SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],\n      });\n    } catch (error) {\n      console.error('Failed to save credentials', error);\n    }\n\n    // Navigate to the next screen.\n    navigate('BottomTabNavigator');\n    navigationRef.reset({\n      index: 0,\n      routes: [{ name: SCREEN_CONSTANTS.SAMPLE_USER }],\n    });\n  };\n\n  return (\n    <View\n      style={[styles.container, { backgroundColor: theme.color.background2 }]}\n    >\n      <KeyboardAvoidingView\n        style={{ flex: 1 }}\n        behavior={keyboardBehavior} // Use dynamic behavior\n        keyboardVerticalOffset={keyboardVerticalOffset}\n      >\n        <View style={styles.contentContainer}>\n          <ScrollView\n            contentContainerStyle={styles.scrollContent}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {/* Header/Logo */}\n            <View style={styles.logoContainer}>\n              <Image\n                source={\n                  mode === 'dark'\n                    ? require('../../assets/icons/Dark.png')\n                    : require('../../assets/icons/Light.png')\n                }\n                style={{\n                  width: width * 0.25,\n                  height: width * 0.25,\n                  resizeMode: 'contain',\n                }}\n              />\n            </View>\n\n            {/* Title */}\n            <Text\n              style={[\n                theme.typography.heading2.bold,\n                {\n                  color: theme.color.textPrimary,\n                  marginBottom: 20,\n                  alignSelf: 'center',\n                },\n              ]}\n            >\n              {t('APP_CREDENTIALS')}\n            </Text>\n\n            {/* Region Selector */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, marginBottom: 10 },\n                ]}\n              >\n                {t('REGION')}\n              </Text>\n              <View style={styles.regionRow}>\n                {/* US */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'US'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'US'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('US')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/US.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      US\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* EU */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'EU'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('EU')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/EU.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      EU\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n\n                {/* IN */}\n                <TouchableOpacity\n                  style={[\n                    styles.flagContainer,\n                    {\n                      backgroundColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.extendedPrimary50\n                          : theme.color.background1,\n                      borderColor:\n                        selectedRegion === 'IN'\n                          ? theme.color.borderHighlight\n                          : theme.color.borderDefault,\n                    },\n                  ]}\n                  onPress={() => setSelectedRegion('IN')}\n                >\n                  <View style={styles.flagInnerContainer}>\n                    <Image\n                      source={require('../../assets/icons/India.png')}\n                      style={styles.flagImage}\n                    />\n                    <Text\n                      style={[\n                        theme.typography.button.medium,\n                        { color: theme.color.textSecondary },\n                      ]}\n                    >\n                      IN\n                    </Text>\n                  </View>\n                </TouchableOpacity>\n              </View>\n            </View>\n\n            {/* App ID */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, paddingBottom: 5 },\n                ]}\n              >\n                APP ID\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={appId}\n                onChangeText={setAppId}\n                placeholder=\"Enter the App ID\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n\n            {/* Auth Key */}\n            <View style={styles.inputContainer}>\n              <Text\n                style={[\n                  theme.typography.caption1.medium,\n                  { color: theme.color.textPrimary, paddingBottom: 5 },\n                ]}\n              >\n                Auth Key\n              </Text>\n              <TextInput\n                style={[\n                  styles.input,\n                  {\n                    borderColor: theme.color.borderLight,\n                    backgroundColor: theme.color.background2,\n                    color: theme.color.textPrimary,\n                  },\n                ]}\n                value={authKey}\n                onChangeText={setAuthKey}\n                placeholder=\"Enter the Auth Key\"\n                placeholderTextColor={theme.color.textTertiary}\n              />\n            </View>\n          </ScrollView>\n\n          {/* Continue Button */}\n          <View style={styles.buttonWrapper}>\n            <TouchableOpacity\n              style={[\n                styles.continueButton,\n                {\n                  backgroundColor: theme.color.primaryButtonBackground,\n                  opacity: isFormValid ? 1 : 0.6,\n                },\n              ]}\n              onPress={handleContinue}\n            >\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  { textAlign: 'center', color: theme.color.staticWhite },\n                ]}\n              >\n                {t('CONTINUE')}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n      {/* Toast Message */}\n      {toastMessage && (\n        <View style={styles.toastContainer}>\n          <Text style={styles.toastText}>{toastMessage}</Text>\n        </View>\n      )}\n    </View>\n  );\n};\n\nexport default AppCredentials;\n\nconst styles = StyleSheet.create({\n  logoContainer: {\n    alignItems: 'center',\n    marginTop: Platform.OS === 'android' ? 30 : 50,\n    marginBottom: 20,\n  },\n  inputContainer: {\n    width: '100%',\n    marginTop: 20,\n  },\n  regionRow: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n  },\n  flagContainer: {\n    width: '32%',\n    borderWidth: 2,\n    borderColor: 'transparent',\n    borderRadius: 12,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  flagInnerContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingVertical: 10,\n    gap: 5,\n  },\n  flagImage: {\n    width: 30,\n    height: 30,\n    resizeMode: 'contain',\n  },\n  input: {\n    paddingHorizontal: 12,\n    paddingVertical: 10,\n    borderRadius: 8,\n    borderWidth: 1,\n  },\n  continueButton: {\n    borderRadius: 8,\n    paddingVertical: 12,\n    width: '100%',\n  },\n  toastContainer: {\n    position: 'absolute',\n    bottom: '8%',\n    left: 20,\n    right: 20,\n    backgroundColor: '#C73C3E',\n    padding: 6,\n    borderRadius: 8,\n    alignItems: 'center',\n  },\n  toastText: {\n    color: '#fff',\n    fontSize: 14,\n  },\n  container: {\n    flex: 1,\n  },\n  contentContainer: {\n    flex: 1,\n    paddingHorizontal: 16,\n  },\n  scrollContent: {\n    flexGrow: 1,\n    paddingBottom: 80,\n  },\n  buttonWrapper: {\n    justifyContent: 'center',\n    alignItems: 'center',\n    paddingVertical: 10,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/login/SampleUser.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport {\n  View,\n  Text,\n  StyleSheet,\n  TouchableOpacity,\n  TextInput,\n  Image,\n  Dimensions,\n  useColorScheme,\n  Pressable,\n  ImageSourcePropType,\n  KeyboardAvoidingView,\n  Platform,\n  ScrollView,\n  ActivityIndicator,\n  StatusBar,\n  Keyboard,\n} from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatAvatar,\n  CometChatUIKit,\n  Icon,\n  useTheme,\n} from '@cometchat/chat-uikit-react-native';\nimport Check from '../../assets/icons/CheckFill';\nimport { sampleData } from '../../utils/helper';\nimport { SCREEN_CONSTANTS } from '../../utils/AppConstants';\nimport { navigate, navigationRef } from '../../navigation/NavigationService';\nimport Skeleton from './Skeleton';\nimport {\n  SafeAreaView,\n  useSafeAreaInsets,\n} from 'react-native-safe-area-context';\nimport { useHeaderHeight } from '@react-navigation/elements';\n\ntype GridItem = CometChat.User | { dummy: true };\n\nconst LoginScreen: React.FC = () => {\n  const [users, setUsers] = useState<CometChat.User[]>([]);\n  const [selectedUser, setSelectedUser] = useState<string | null>(null);\n  const [userUID, setUserUID] = useState<string>('');\n  const [isLoading, setIsLoading] = useState<boolean>(false);\n  const [loadingUsers, setLoadingUsers] = useState<boolean>(true);\n\n  const theme = useTheme();\n  const mode = useColorScheme();\n  const { width } = Dimensions.get('window');\n  const statusBarHeight =\n    Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;\n\n  const insets = useSafeAreaInsets();\n  const headerHeight = useHeaderHeight(); // returns 0 if no header\n  const keyboardVerticalOffset =\n    Platform.OS === 'ios'\n      ? (insets.top + 8 || 0) + (headerHeight || 0)\n      : (statusBarHeight + 6 || 0) + (headerHeight || 0);\n\n  const [keyboardBehavior, setKeyboardBehavior] = useState<\n    'padding' | 'height' | undefined\n  >(Platform.OS === 'ios' ? 'padding' : 'height');\n\n  useEffect(() => {\n    const showListener = Keyboard.addListener('keyboardDidShow', () => {\n      setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');\n    });\n\n    const hideListener = Keyboard.addListener('keyboardDidHide', () => {\n      setKeyboardBehavior(undefined); // Remove behavior when keyboard hides\n    });\n\n    return () => {\n      showListener.remove();\n      hideListener.remove();\n    };\n  }, []);\n\n  useEffect(() => {\n    (async function loadUsers(): Promise<void> {\n      try {\n        setLoadingUsers(true);\n        const fetchedUsers = await fetchUsers();\n        setUsers(fetchedUsers);\n      } catch (error) {\n        console.error(error);\n      } finally {\n        setLoadingUsers(false);\n      }\n    })();\n  }, []);\n\n  const handleSelectUser = (user: CometChat.User): void => {\n    setSelectedUser(user.getUid());\n    setUserUID('');\n  };\n\n  const handleContinue = async () => {\n    if ((!selectedUser && !userUID.trim()) || isLoading) return;\n    setIsLoading(true);\n    const uid: string = userUID.trim() || selectedUser!;\n    try {\n      await CometChatUIKit.login({ uid });\n      navigate('BottomTabNavigator');\n      navigationRef.reset({\n        index: 0,\n        routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],\n      });\n    } catch (error: any) {\n      console.log('Login failed with exception:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  /**\n   * Fetch users from a remote sample JSON file.\n   * Falls back to local sample data if there's an error.\n   */\n  async function fetchUsers(): Promise<CometChat.User[]> {\n    try {\n      const response = await fetch(\n        'https://assets.cometchat.io/sampleapp/sampledata.json',\n      );\n\n      if (response.ok) {\n        const data = await response.json();\n        const fetchedUsers = data.users || [];\n        return fetchedUsers.map((user: any) => new CometChat.User(user));\n      } else {\n        throw new Error('Failed to load users');\n      }\n    } catch (error) {\n      console.error('Exception while fetching users:', error);\n      return await getDefaultUsers();\n    }\n  }\n\n  /**\n   * Get users from local sample data (used in case the remote fetch fails).\n   */\n  async function getDefaultUsers(): Promise<CometChat.User[]> {\n    const localUsers = sampleData.users || [];\n    return localUsers.map((user: any) => new CometChat.User(user));\n  }\n\n  /**\n   * Returns the appropriate image source object for the avatar.\n   */\n  const getAvatarSource = (\n    avatar: string | ImageSourcePropType,\n  ): ImageSourcePropType => {\n    if (typeof avatar === 'string') {\n      if (avatar.startsWith('http://') || avatar.startsWith('https://')) {\n        return { uri: avatar };\n      }\n    }\n    return avatar as ImageSourcePropType;\n  };\n\n  // Show skeleton if the API is still loading or if no users are available.\n  const showSkeleton = loadingUsers || users.length === 0;\n\n  // Compute grid data only if users are available.\n  let gridData: GridItem[] = [];\n  if (users.length > 0) {\n    gridData = [...users];\n    const numColumns = 3;\n    const numberOfElementsLastRow = users.length % numColumns;\n    if (numberOfElementsLastRow !== 0) {\n      for (let i = 0; i < numColumns - numberOfElementsLastRow; i++) {\n        gridData.push({ dummy: true });\n      }\n    }\n  }\n\n  const Loading = () => {\n    return (\n      <View\n        style={{\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        <ActivityIndicator\n          size=\"small\"\n          color={theme.color.staticWhite}\n          style={{ alignSelf: 'center', justifyContent: 'center' }}\n        />\n      </View>\n    );\n  };\n\n  return (\n    <SafeAreaView\n      style={[styles.container, { backgroundColor: theme.color.background2 }]}\n      edges={['top']}\n    >\n      <KeyboardAvoidingView\n        style={styles.keyboardAvoidingContainer}\n        behavior={keyboardBehavior} // Use dynamic behavior\n        keyboardVerticalOffset={keyboardVerticalOffset}\n      >\n        <ScrollView\n          style={{ flex: 1 }}\n          contentContainerStyle={styles.scrollContainer}\n          keyboardShouldPersistTaps=\"handled\"\n          showsVerticalScrollIndicator={false}\n        >\n          {/* App Logo */}\n          <View style={styles.logoContainer}>\n            <Image\n              source={\n                mode === 'dark'\n                  ? require('../../assets/icons/Dark.png')\n                  : require('../../assets/icons/Light.png')\n              }\n              style={{\n                width: width * 0.25,\n                height: width * 0.25,\n                resizeMode: 'contain',\n              }}\n            />\n          </View>\n\n          {/* Title */}\n          <Text\n            style={[\n              theme.typography.heading2.bold,\n              styles.logInTitle,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Log In\n          </Text>\n\n          {/* Subtitle */}\n          <Text\n            style={[\n              theme.typography.body.medium,\n              styles.subtitle,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Choose a Sample User\n          </Text>\n\n          {/* Sample Users Grid */}\n          <View style={styles.userGridWrapper}>\n            {showSkeleton ? (\n              <Skeleton />\n            ) : (\n              <View style={styles.usersContainer}>\n                {gridData.map((item, index) => {\n                  // Render a blank view for dummy items\n                  if ('dummy' in item && item.dummy) {\n                    return (\n                      <View key={`dummy-${index}`} style={styles.userCard} />\n                    );\n                  }\n\n                  // Otherwise, render a user\n                  const user = item as CometChat.User;\n                  const isSelected = selectedUser === user.getUid();\n                  const firstName = user.getName();\n\n                  return (\n                    <Pressable\n                      key={user.getUid()}\n                      style={[\n                        styles.userCard,\n                        {\n                          borderWidth: isSelected ? 1.5 : 1,\n                          borderColor: isSelected\n                            ? theme.color.borderHighlight\n                            : theme.color.borderLight,\n                          backgroundColor: isSelected\n                            ? theme.color.extendedPrimary50\n                            : theme.color.background1,\n                        },\n                      ]}\n                      onPress={() => handleSelectUser(user)}\n                    >\n                      {/* Show the check icon ONLY if selected */}\n                      {isSelected && (\n                        <View style={styles.checkIconContainer}>\n                          <Icon\n                            icon={\n                              <Check\n                                color={theme.color.staticWhite}\n                                height={18}\n                                width={18}\n                              />\n                            }\n                          />\n                        </View>\n                      )}\n                      <CometChatAvatar\n                        name={user.getName()}\n                        image={getAvatarSource(user.getAvatar())}\n                      />\n                      {/* Display only the first name */}\n                      <Text\n                        style={[\n                          theme.typography.body.medium,\n                          styles.firstNameText,\n                          { color: theme.color.textPrimary },\n                        ]}\n                      >\n                        {firstName}\n                      </Text>\n                      <Text\n                        style={[\n                          theme.typography.caption1.regular,\n                          styles.uidText,\n                          { color: theme.color.textSecondary },\n                        ]}\n                      >\n                        {user.getUid()}\n                      </Text>\n                    </Pressable>\n                  );\n                })}\n              </View>\n            )}\n          </View>\n\n          {/* Horizontal divider with \"Or\" in the middle */}\n          <View style={styles.dividerRow}>\n            <View\n              style={[\n                styles.divider,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            />\n            <Text\n              style={[\n                theme.typography.body.medium,\n                { color: theme.color.textTertiary },\n              ]}\n            >\n              Or\n            </Text>\n            <View\n              style={[\n                styles.divider,\n                { borderColor: theme.color.borderDefault },\n              ]}\n            />\n          </View>\n\n          {/* UID Input */}\n          <Text\n            style={[\n              theme.typography.caption1.medium,\n              styles.uidLabel,\n              { color: theme.color.textPrimary },\n            ]}\n          >\n            Enter Your UID\n          </Text>\n          <TextInput\n            placeholder=\" Enter UID\"\n            placeholderTextColor={theme.color.textTertiary}\n            style={[\n              theme.typography.body.regular,\n              styles.uidInput,\n              {\n                borderColor: theme.color.borderLight,\n                color: theme.color.textPrimary,\n              },\n            ]}\n            value={userUID}\n            onChangeText={(text: string) => {\n              setUserUID(text);\n              setSelectedUser(null);\n            }}\n          />\n        </ScrollView>\n\n        {/* Bottom container with \"Continue\" button and \"Change App Credentials\" */}\n        <View style={styles.bottomContainer}>\n          <TouchableOpacity\n            style={[\n              styles.continueButton,\n              {\n                backgroundColor: theme.color.primaryButtonBackground,\n              },\n            ]}\n            onPress={handleContinue}\n            disabled={(!selectedUser && !userUID.trim()) || isLoading}\n          >\n            {isLoading ? (\n              <Loading />\n            ) : (\n              <Text\n                style={[\n                  theme.typography.button.medium,\n                  styles.continueButtonText,\n                  { color: theme.color.staticWhite },\n                ]}\n              >\n                Continue\n              </Text>\n            )}\n          </TouchableOpacity>\n\n          <View style={styles.changeCredentialsWrapper}>\n            <Text\n              style={[\n                theme.typography.body.regular,\n                { color: theme.color.textSecondary },\n              ]}\n            >\n              Change{' '}\n            </Text>\n            <TouchableOpacity\n              style={styles.changeCredentialsContainer}\n              onPress={() => {\n                navigationRef.navigate(SCREEN_CONSTANTS.APP_CRED);\n              }}\n            >\n              <Text\n                style={[\n                  theme.typography.body.regular,\n                  { color: theme.color.primary },\n                ]}\n              >\n                App Credentials\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </SafeAreaView>\n  );\n};\n\nexport default LoginScreen;\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  keyboardAvoidingContainer: {\n    flex: 1,\n  },\n  scrollContainer: {\n    paddingHorizontal: 16,\n    paddingTop: 16,\n  },\n  logoContainer: {\n    alignItems: 'center',\n    marginBottom: 16,\n  },\n  logInTitle: {\n    marginBottom: 16,\n    alignSelf: 'center',\n  },\n  subtitle: {\n    marginBottom: 6,\n  },\n  usersContainer: {\n    flexDirection: 'row',\n    flexWrap: 'wrap',\n    justifyContent: 'space-around',\n  },\n  userCard: {\n    position: 'relative',\n    width: '30%',\n    borderRadius: 8,\n    paddingVertical: 16,\n    paddingHorizontal: 8,\n    marginBottom: 12,\n    alignItems: 'center',\n    overflow: 'hidden',\n  },\n  checkIconContainer: {\n    position: 'absolute',\n    top: 0,\n    right: 0,\n    borderBottomLeftRadius: 10,\n    borderTopRightRadius: 7,\n    width: '27%',\n    height: '22%',\n    backgroundColor: '#7367F0',\n    alignItems: 'center',\n    justifyContent: 'center',\n    zIndex: 2,\n  },\n  firstNameText: {\n    marginTop: 8,\n    textAlign: 'center',\n  },\n  uidText: {\n    marginTop: 4,\n    textAlign: 'center',\n  },\n  dividerRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginVertical: 16,\n    gap: 10,\n  },\n  divider: {\n    flex: 1,\n    height: 1,\n    borderWidth: 0.5,\n  },\n  uidLabel: {\n    paddingBottom: 5,\n  },\n  uidInput: {\n    borderWidth: 1,\n    borderRadius: 8,\n    padding: 10,\n    marginBottom: 24,\n  },\n  bottomContainer: {\n    paddingHorizontal: 16,\n    backgroundColor: 'transparent',\n  },\n  continueButton: {\n    paddingVertical: 12,\n    borderRadius: 6,\n    marginBottom: 12,\n  },\n  continueButtonText: {\n    alignSelf: 'center',\n  },\n  changeCredentialsWrapper: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'center',\n    marginBottom: 10,\n  },\n  changeCredentialsContainer: {\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  userGridWrapper: {\n    minHeight: 240,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/login/Skeleton.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Dimensions, Easing, StyleSheet, useColorScheme, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\n// import { useThemeInternal } from \"../../../theme/hook\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonBox = ({ index, boxSize, gradientColors }: any) => {\n  // Determine if the box is the last in its row\n  const isLastInRow = (index + 1) % 3 === 0;\n\n  return (\n    <Svg\n      height={boxSize}\n      width={boxSize}\n      viewBox=\"0 0 100 100\"\n      fill=\"none\"\n      style={[\n        styles.skeletonBox,\n        {\n          width: boxSize,\n          height: boxSize,\n          marginRight: isLastInRow ? 0 : 8,\n        },\n      ]}\n    >\n      <Defs>\n        <LinearGradient\n          id={`paint0_linear_${index}`}\n          x1={10}\n          y1={50}\n          x2={90}\n          y2={50}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <Stop stopColor={gradientColors[0]} />\n          <Stop offset={1} stopColor={gradientColors[1]} />\n        </LinearGradient>\n      </Defs>\n      <Rect\n        x={10}\n        y={10}\n        width={80}\n        height={80}\n        rx={15}\n        ry={15}\n        fill={`url(#paint0_linear_${index})`}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const animatedValue = useRef(new Animated.Value(0)).current;\n  const [isLoading, setIsLoading] = useState(true);\n  const  mode  = useColorScheme();\n\n  // Define static colors\n  const color = {\n    staticBlack: \"#000000\",\n    staticWhite: \"#FFFFFF\",\n  };\n\n  // Define skeletonStyle based on the theme mode\n  const skeletonStyle =\n    mode === \"light\"\n      ? {\n          linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n          shimmerBackgroundColor: color.staticBlack,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        }\n      : {\n          linearGradientColors: [\"#383838\", \"#272727\"],\n          shimmerBackgroundColor: color.staticWhite,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        };\n\n  const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } =\n    skeletonStyle;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n\n    // Simulate a loading time of 3 seconds\n    const loadData = setTimeout(() => {\n      setIsLoading(false);\n    }, 3000);\n\n    return () => clearTimeout(loadData);\n  }, [animatedValue, speed]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth, screenWidth],\n  });\n\n  if (!isLoading) {\n    return null;\n  }\n\n  const boxSize = (screenWidth - 22) / 3;\n  const shimmerWidth = screenWidth;\n  const shimmerHeight = boxSize + 16;\n\n  return (\n    <View style={styles.container}>\n      <View style={styles.grid}>\n        {new Array(6).fill(0).map((_, index) => (\n          <SkeletonBox\n            key={index}\n            index={index}\n            boxSize={boxSize}\n            gradientColors={linearGradientColors}\n          />\n        ))}\n      </View>\n      <Animated.View\n        style={[\n          styles.animatedShimmer,\n          {\n            width: shimmerWidth,\n            height: shimmerHeight,\n            transform: [{ translateX: shimmerTranslateX }],\n            backgroundColor: shimmerBackgroundColor,\n            opacity: shimmerOpacity,\n          },\n        ]}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    position: \"relative\",\n    borderRadius: 16,\n    overflow: \"hidden\",\n  },\n  grid: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n  },\n  skeletonBox: {\n    marginBottom: 10,\n    marginHorizontal: -10,\n  },\n  animatedShimmer: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n  },\n});\n\nexport default Skeleton;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/components/users/Users.tsx",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatUsers, useTheme } from '@cometchat/chat-uikit-react-native';\nimport React, { useCallback } from 'react';\nimport { View } from 'react-native';\nimport { useFocusEffect, useNavigation } from '@react-navigation/native';\nimport { RootStackParamList } from '../../navigation/types';\nimport { StackNavigationProp } from '@react-navigation/stack';\n\ntype UserNavigationProp = StackNavigationProp<RootStackParamList, 'Users'>;\n\nconst Users: React.FC = () => {\n  const theme = useTheme();\n  const navigation = useNavigation<UserNavigationProp>();\n  const [shouldHide, setShouldHide] = React.useState(false);\n\n  useFocusEffect(\n    useCallback(() => {\n      setShouldHide(false);\n      return () => {\n        setShouldHide(true);\n      };\n    }, []),\n  );\n\n  return shouldHide ? null : (\n    <View style={{ flex: 1, backgroundColor: theme.color.background1 }}>\n      <CometChatUsers\n        onItemPress={(user: CometChat.User) => {\n          navigation.navigate('Messages', {\n            user: user,\n          });\n        }}\n        usersRequestBuilder={new CometChat.UsersRequestBuilder()\n          .setLimit(30)\n          .hideBlockedUsers(false)\n          // .setRoles(['@agentic'])\n          .friendsOnly(false)\n          .setStatus('')\n          .setTags([])\n          .sortBy('name')\n          .setUIDs([])}\n      />\n    </View>\n  );\n};\n\nexport default Users;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/config/config.json",
    "content": "{\n  \"builderId\": \"8c0c1c80-b08e-4960-80d4-686396119711\",\n  \"settings\": {\n    \"chatFeatures\": {\n      \"coreMessagingExperience\": {\n        \"typingIndicator\": true,\n        \"threadConversationAndReplies\": true,\n        \"photosSharing\": true,\n        \"videoSharing\": true,\n        \"audioSharing\": true,\n        \"fileSharing\": true,\n        \"editMessage\": true,\n        \"deleteMessage\": true,\n        \"messageDeliveryAndReadReceipts\": true,\n        \"userAndFriendsPresence\": true\n      },\n      \"deeperUserEngagement\": {\n        \"mentions\": true,\n        \"reactions\": true,\n        \"messageTranslation\": true,\n        \"polls\": true,\n        \"collaborativeWhiteboard\": true,\n        \"collaborativeDocument\": true,\n        \"voiceNotes\": true,\n        \"emojis\": true,\n        \"stickers\": true,\n        \"userInfo\": true,\n        \"groupInfo\": true\n      },\n      \"aiUserCopilot\": {\n        \"conversationStarter\": true,\n        \"conversationSummary\": true,\n        \"smartReply\": true\n      },\n      \"groupManagement\": {\n        \"createGroup\": true,\n        \"addMembersToGroups\": true,\n        \"joinLeaveGroup\": true,\n        \"deleteGroup\": true,\n        \"viewGroupMembers\": true\n      },\n      \"moderatorControls\": {\n        \"kickUsers\": true,\n        \"banUsers\": true,\n        \"promoteDemoteMembers\": true\n      },\n      \"privateMessagingWithinGroups\": {\n        \"sendPrivateMessageToGroupMembers\": true\n      }\n    },\n    \"callFeatures\": {\n      \"voiceAndVideoCalling\": {\n        \"oneOnOneVoiceCalling\": true,\n        \"oneOnOneVideoCalling\": true,\n        \"groupVideoConference\": true,\n        \"groupVoiceConference\": true\n      }\n    },\n    \"layout\": {\n      \"withSideBar\": true,\n      \"tabs\": [\n        \"chats\",\n        \"calls\",\n        \"users\",\n        \"groups\"\n      ],\n      \"chatType\": \"both\"\n    },\n    \"style\": {\n          \"theme\": \"system\",\n          \"color\": {\n              \"brandColor\": \"#6852D6\",\n              \"primaryTextLight\": \"#141414\",\n              \"primaryTextDark\": \"#FFFFFF\",\n              \"secondaryTextLight\": \"#727272\",\n              \"secondaryTextDark\": \"#989898\"\n          },\n          \"typography\": {\n              \"font\": \"roboto\",\n              \"size\": \"default\"\n          }\n    },\n    \"noCode\": {\n      \"docked\": false,\n      \"styles\": {\n        \"buttonBackGround\": \"#6952d6\",\n        \"buttonShape\": \"rounded\",\n        \"openIcon\": \"https://nocode-js.cometchat.io/v1/resources/docked_open_icon.svg\",\n        \"closeIcon\": \"https://nocode-js.cometchat.io/v1/resources/docked_close_icon.svg\",\n        \"customJs\": \"\",\n        \"customCss\": \"\"\n      }\n    }\n  },\n  \"name\": \"ttttt\",\n  \"type\": \"low-code\",\n  \"createdAt\": 1749032525,\n  \"updatedAt\": 1749032525,\n  \"expiresAt\": 1812108125\n}"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/config/store.ts",
    "content": "import { create } from 'zustand';\nimport config from './config.json';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\n// TypeScript interfaces for the config structure\ninterface ColorConfig {\n  brandColor: string;\n  primaryTextLight: string;\n  primaryTextDark: string;\n  secondaryTextLight: string;\n  secondaryTextDark: string;\n}\n\ninterface TypographyConfig {\n  font: string;\n  size: string;\n}\n\ninterface StyleConfig {\n  theme: string;\n  color: ColorConfig;\n  typography: TypographyConfig;\n}\n\ninterface NoCodeStyles {\n  buttonBackGround: string;\n  buttonShape: string;\n  openIcon: string;\n  closeIcon: string;\n  customJs: string;\n  customCss: string;\n}\n\ninterface NoCodeConfig {\n  docked: boolean;\n  styles: NoCodeStyles;\n}\n\ninterface LayoutConfig {\n  withSideBar: boolean;\n  tabs: string[];\n  chatType: string;\n  compactMessageComposer: boolean;\n}\n\ninterface CoreMessagingConfig {\n  typingIndicator: boolean;\n  threadConversationAndReplies: boolean;\n  photosSharing: boolean;\n  videoSharing: boolean;\n  audioSharing: boolean;\n  fileSharing: boolean;\n  editMessage: boolean;\n  deleteMessage: boolean;\n  messageDeliveryAndReadReceipts: boolean;\n  userAndFriendsPresence: boolean;\n}\n\ninterface DeeperEngagementConfig {\n  mentions: boolean;\n  reactions: boolean;\n  messageTranslation: boolean;\n  polls: boolean;\n  collaborativeWhiteboard: boolean;\n  collaborativeDocument: boolean;\n  voiceNotes: boolean;\n  emojis: boolean;\n  stickers: boolean;\n  userInfo: boolean;\n  groupInfo: boolean;\n}\n\ninterface AiUserCopilotConfig {\n  conversationStarter: boolean;\n  conversationSummary: boolean;\n  smartReply: boolean;\n}\n\ninterface GroupManagementConfig {\n  createGroup: boolean;\n  addMembersToGroups: boolean;\n  joinLeaveGroup: boolean;\n  deleteGroup: boolean;\n  viewGroupMembers: boolean;\n}\n\ninterface ModeratorControlsConfig {\n  kickUsers: boolean;\n  banUsers: boolean;\n  promoteDemoteMembers: boolean;\n}\n\ninterface PrivateMessagingConfig {\n  sendPrivateMessageToGroupMembers: boolean;\n}\n\ninterface ChatFeaturesConfig {\n  coreMessagingExperience: CoreMessagingConfig;\n  deeperUserEngagement: DeeperEngagementConfig;\n  aiUserCopilot: AiUserCopilotConfig;\n  groupManagement: GroupManagementConfig;\n  moderatorControls: ModeratorControlsConfig;\n  privateMessagingWithinGroups: PrivateMessagingConfig;\n}\n\ninterface VoiceVideoCallingConfig {\n  oneOnOneVoiceCalling: boolean;\n  oneOnOneVideoCalling: boolean;\n  groupVideoConference: boolean;\n  groupVoiceConference: boolean;\n}\n\ninterface CallFeaturesConfig {\n  voiceAndVideoCalling: VoiceVideoCallingConfig;\n}\n\ninterface SettingsConfig {\n  chatFeatures: ChatFeaturesConfig;\n  callFeatures: CallFeaturesConfig;\n  layout: LayoutConfig;\n  style: StyleConfig;\n  noCode: NoCodeConfig;\n}\n\ninterface AppConfig {\n  builderId: string;\n  settings: SettingsConfig;\n  name: string;\n  type: string;\n  createdAt: number;\n  updatedAt: number;\n  expiresAt: number;\n}\n\n// Zustand store interface\ninterface ConfigStore {\n  config: AppConfig;\n  updateConfig: (newConfig: Partial<AppConfig>) => void;\n  resetConfig: () => void;\n}\n\n// Initialize config from storage\nconst initializeConfig = async (): Promise<AppConfig> => {\n  try {\n    const savedConfig = await AsyncStorage.getItem('@app_config');\n    if (savedConfig) {\n      const parsedConfig = JSON.parse(savedConfig);\n      \n      // Handle both old format (direct config) and new API response format (nested in data)\n      let actualConfig: AppConfig;\n      if (parsedConfig.data && parsedConfig.data.settings) {\n        // New API response format - extract data property\n        actualConfig = parsedConfig.data as AppConfig;\n      } else if (parsedConfig.settings && parsedConfig.builderId) {\n        // Old format - direct config\n        actualConfig = parsedConfig as AppConfig;\n      } else {\n        // Invalid structure, use default\n        return config as AppConfig;\n      }\n      \n      // Validate that the config has the expected structure\n      if (actualConfig && actualConfig.settings && actualConfig.builderId) {\n        return actualConfig;\n      }\n    }\n  } catch (error) {\n    console.error('Error loading config from storage:', error);\n  }\n  return config as AppConfig;\n};\n\n// Create the Zustand store\nexport const useConfigStore = create<ConfigStore>((set, _get) => ({\n  // Initialize with the config.json data\n  config: config as AppConfig,\n\n  // Update the entire config\n  updateConfig: (newConfig) =>\n    set((state) => ({\n      config: { ...state.config, ...newConfig },\n    })),\n\n  // Reset to original config\n  resetConfig: () =>\n    set({\n      config: config as AppConfig,\n    }),\n}));\n\n// Initialize the store with saved config if available\ninitializeConfig().then((initialConfig) => {\n  useConfigStore.setState({ config: initialConfig });\n}).catch((error) => {\n  console.error('Error initializing config:', error);\n  // Fallback to default config from JSON file\n  useConfigStore.setState({ config: config as AppConfig });\n});\n\nexport const useConfig = <T>(selector: (state: AppConfig) => T) =>\n  useConfigStore((state) => selector(state.config));\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/declarations.d.ts",
    "content": "declare module '*.png' {\n    const value: any;\n    export default value;\n  }\n  "
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/hooks/useGroupMemberStatus.ts",
    "content": "import { useState, useEffect, useRef } from 'react';\n//@ts-ignore\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatUIEventHandler } from '@cometchat/chat-uikit-react-native';\n\n/**\n * Hook that detects whether the logged-in user is no longer a member\n * of the given group (kicked or banned).\n *\n * - On mount: checks current membership via CometChat.getGroup()\n * - Real-time: listens to SDK + UI events for kicked/banned/re-added\n *\n * @param group - The CometChat group object (pass undefined for 1-on-1 chats)\n * @returns `true` when the current user is no longer a member\n */\nexport const useGroupMemberStatus = (group?: CometChat.Group): boolean => {\n  const [isNoLongerMember, setIsNoLongerMember] = useState(false);\n  const loggedInUser = useRef<CometChat.User | null>(null);\n\n  // Fetch logged-in user once\n  useEffect(() => {\n    CometChat.getLoggedinUser().then((u: CometChat.User | null) => {\n      if (u) loggedInUser.current = u;\n    });\n  }, []);\n\n  // Check membership on mount by fetching fresh group data\n  useEffect(() => {\n    if (!group) return;\n\n    CometChat.getGroup(group.getGuid())\n      .then((freshGroup: CometChat.Group) => {\n        if (!freshGroup.getHasJoined()) {\n          setIsNoLongerMember(true);\n        }\n      })\n      .catch(() => {\n        setIsNoLongerMember(true);\n      });\n  }, [group]);\n\n  useEffect(() => {\n    if (!group) return;\n\n    const uiListenerId = 'composer_group_status_' + new Date().getTime();\n    const sdkListenerId = 'composer_sdk_group_status_' + new Date().getTime();\n\n    CometChatUIEventHandler.addGroupListener(uiListenerId, {\n      ccGroupMemberKicked: ({ kickedUser }: any) => {\n        if (kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()) {\n          setIsNoLongerMember(true);\n        }\n      },\n      ccGroupMemberBanned: ({ kickedUser, bannedUser }: any) => {\n        const affected = bannedUser || kickedUser;\n        if (affected?.getUid?.() === loggedInUser.current?.getUid?.()) {\n          setIsNoLongerMember(true);\n        }\n      },\n      ccGroupMemberAdded: ({ usersAdded }: any) => {\n        if (Array.isArray(usersAdded)) {\n          const wasReAdded = usersAdded.some(\n            (u: any) =>\n              u?.getUid?.() === loggedInUser.current?.getUid?.() ||\n              u?.uid === loggedInUser.current?.getUid?.()\n          );\n          if (wasReAdded) {\n            setIsNoLongerMember(false);\n          }\n        }\n      },\n    });\n\n    CometChat.addGroupListener(\n      sdkListenerId,\n      new CometChat.GroupListener({\n        onGroupMemberKicked: (\n          _message: any,\n          kickedUser: any,\n          _kickedBy: any,\n          kickedFrom: any\n        ) => {\n          if (\n            kickedFrom?.getGuid?.() === group.getGuid() &&\n            kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(true);\n          }\n        },\n        onGroupMemberBanned: (\n          _message: any,\n          bannedUser: any,\n          _bannedBy: any,\n          bannedFrom: any\n        ) => {\n          if (\n            bannedFrom?.getGuid?.() === group.getGuid() &&\n            bannedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(true);\n          }\n        },\n        onMemberAddedToGroup: (\n          _message: any,\n          addedUser: any,\n          _addedBy: any,\n          addedTo: any\n        ) => {\n          if (\n            addedTo?.getGuid?.() === group.getGuid() &&\n            addedUser?.getUid?.() === loggedInUser.current?.getUid?.()\n          ) {\n            setIsNoLongerMember(false);\n          }\n        },\n      })\n    );\n\n    return () => {\n      CometChatUIEventHandler.removeGroupListener(uiListenerId);\n      CometChat.removeGroupListener(sdkListenerId);\n    };\n  }, [group]);\n\n  return isNoLongerMember;\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/hooks/useIsKeyboardVisible.ts",
    "content": "import { useState, useEffect } from 'react';\nimport { Keyboard, KeyboardEvent } from 'react-native';\n\n/**\n * Custom hook that returns whether the keyboard is currently visible.\n * Uses React Native's Keyboard API directly, independent of ChatUiKit.\n * @returns {boolean} True if keyboard is visible, false otherwise.\n */\nexport const useIsKeyboardVisible = (): boolean => {\n  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);\n\n  useEffect(() => {\n    const showSubscription = Keyboard.addListener('keyboardDidShow', () => {\n      setIsKeyboardVisible(true);\n    });\n\n    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {\n      setIsKeyboardVisible(false);\n    });\n\n    return () => {\n      showSubscription.remove();\n      hideSubscription.remove();\n    };\n  }, []);\n\n  return isKeyboardVisible;\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/navigation/AuthContext.tsx",
    "content": "import React from 'react';\n\nexport interface AuthContextProps {\n  isLoggedIn: boolean;\n  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const AuthContext = React.createContext<AuthContextProps>({\n  isLoggedIn: false,\n  setIsLoggedIn: () => {},\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/navigation/BottomTabNavigator.tsx",
    "content": "import React from 'react';\nimport {\n  StyleSheet,\n  Platform,\n  View,\n  TouchableWithoutFeedback,\n  Text,\n} from 'react-native';\nimport {\n  createBottomTabNavigator,\n  BottomTabBarButtonProps,\n} from '@react-navigation/bottom-tabs';\nimport {useTheme, Icon, useCometChatTranslation } from '@cometchat/chat-uikit-react-native';\nimport {SCREEN_CONSTANTS} from '../utils/AppConstants';\nimport { useIsKeyboardVisible } from '../hooks/useIsKeyboardVisible';\nimport ChatFill from '../assets/icons/Chatfill';\nimport Chat from '../assets/icons/Chat';\nimport PersonFill from '../assets/icons/PersonFill';\nimport Person from '../assets/icons/Person';\nimport GroupFill from '../assets/icons/GroupFill';\nimport CallFill from '../assets/icons/CallFill';\nimport Call from '../assets/icons/Call';\nimport Group from '../assets/icons/Group';\nimport Conversations from '../components/conversations/screens/Conversations';\nimport Calls from '../components/calls/Calls';\nimport Users from '../components/users/Users';\nimport Groups from '../components/groups/Groups';\nimport {BottomTabParamList} from './types';\nimport { useConfig } from '../config/store';\n\n// Create the tab navigator.\nconst Tab = createBottomTabNavigator<BottomTabParamList>();\n\n// Define a type for icon components that accept color, height, and width props.\ntype IconComponentType = React.ComponentType<{\n  color?: string;\n  height?: number;\n  width?: number;\n}>;\n\n// Update the icons mapping to use the imported image components.\nconst icons: Record<\n  string,\n  {active: IconComponentType; inactive: IconComponentType}\n> = {\n  Chats: {active: ChatFill, inactive: Chat},\n  Users: {active: PersonFill, inactive: Person},\n  Calls: {active: CallFill, inactive: Call},\n  Groups: {active: GroupFill, inactive: Group},\n};\n\nconst CustomTabBarButton = ({children, onPress}: BottomTabBarButtonProps) => (\n  <TouchableWithoutFeedback onPress={onPress}>\n    <View style={styles.tabButton}>{children}</View>\n  </TouchableWithoutFeedback>\n);\n\nconst BottomTabNavigator = () => {\n  const theme = useTheme();\n  const tabs = useConfig(state => state.settings.layout.tabs);\n  const { t } = useCometChatTranslation();\n  // Use the custom hook to track keyboard visibility\n  const isKeyboardVisible = useIsKeyboardVisible();\n\n  // Map tab keys to screen names and components\n  const TAB_COMPONENTS: Record<string, { name: string; component: React.ComponentType<any> }> = {\n    chats: { name: SCREEN_CONSTANTS.CHATS, component: Conversations },\n    calls: { name: SCREEN_CONSTANTS.CALLS, component: Calls },\n    users: { name: SCREEN_CONSTANTS.USERS, component: Users },\n    groups: { name: SCREEN_CONSTANTS.GROUPS, component: Groups },\n  };\n\n  return (\n    <Tab.Navigator\n      initialRouteName=\"Chats\"\n      screenOptions={({route}) => ({\n        headerShown: false,\n        // Hide tab bar when keyboard is visible\n        tabBarStyle: isKeyboardVisible \n          ? { display: 'none' } \n          : styles.tabBar,\n        animation: 'none',\n        tabBarIcon: ({focused}) => {\n          const iconSet = icons[route.name];\n          if (!iconSet) return null;\n\n          const IconComponent = focused ? iconSet.active : iconSet.inactive;\n          const iconColor = focused\n            ? theme.color.primary\n            : theme.color.iconSecondary;\n\n          return (\n            <Icon\n              icon={\n                <IconComponent\n                  color={iconColor as string}\n                  height={24}\n                  width={24}\n                />\n              }\n            />\n          );\n        },\n        tabBarShowLabel: true,\n        tabBarLabel: ({focused}) => \n          focused ? (\n            <View>\n              <Text\n                style={[\n                  styles.tabLabel,\n                  {\n                    color: theme.color.primary,\n                    fontFamily: theme.typography.heading1.bold.fontFamily,\n                  },\n                ]}\n              >\n                {t(route.name.toUpperCase())}\n              </Text>\n            </View>\n       ) : null,\n        tabBarButton: props => <CustomTabBarButton {...props} />,\n        tabBarBackground: () => (\n          <View style={{backgroundColor: theme.color.background1, flex: 1}} />\n        ),\n      })}\n    >\n      {tabs.map(tabKey => {\n        const tab = TAB_COMPONENTS[tabKey.toLowerCase()];\n        return tab ? (\n          <Tab.Screen\n            key={tab.name}\n            name={tab.name as keyof BottomTabParamList}\n            component={tab.component}\n          />\n        ) : null;\n      })}\n    </Tab.Navigator>\n  );\n};\n\nconst styles = StyleSheet.create({\n  tabBar: {\n    height: Platform.OS === 'ios' ? 60 : 70,\n    paddingBottom: Platform.OS === 'ios' ? 0 : 10,\n    paddingTop: 15,\n    borderTopWidth: 0,\n    elevation: 5,\n    shadowColor: '#000',\n    shadowOffset: {width: 0, height: -2},\n    shadowOpacity: 0.1,\n    shadowRadius: 3,\n  },\n  tabLabel: {\n    fontSize: 12,\n    marginBottom: 5,\n  },\n  tabButton: {\n    flex: 1,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n});\n\nexport default BottomTabNavigator;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/navigation/NavigationService.ts",
    "content": "import {createNavigationContainerRef} from '@react-navigation/native';\nimport {RootStackParamList} from './types';\n\nexport const navigationRef = createNavigationContainerRef<RootStackParamList>();\n\ninterface PendingNavigation {\n  name: keyof RootStackParamList;\n  params?: any;\n}\n\nlet pendingNavigation: PendingNavigation | null = null;\n\nexport function navigate<RouteName extends keyof RootStackParamList>(\n  name: RouteName,\n  params?: RootStackParamList[RouteName] extends undefined\n    ? undefined\n    : RootStackParamList[RouteName],\n) {\n  if (navigationRef.isReady()) {\n    // navigationRef.navigate(name as never);\n    navigationRef.navigate(name as any, params as any); \n  } else {\n    // Save the navigation intent for later processing\n    pendingNavigation = {name, params};\n  }\n}\n\nexport function processPendingNavigation() {\n  if (pendingNavigation && navigationRef.isReady()) {\n    const {name, params} = pendingNavigation;\n    navigationRef.navigate(name as any, params as any);\n    pendingNavigation = null;\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/navigation/RootStackNavigator.tsx",
    "content": "import React from 'react';\nimport { createNativeStackNavigator } from '@react-navigation/native-stack';\nimport { NavigationContainer, DefaultTheme } from '@react-navigation/native';\nimport BottomTabNavigator from './BottomTabNavigator';\nimport OngoingCallScreen from '../components/conversations/screens/OngoingCallScreen';\nimport { SCREEN_CONSTANTS } from '../utils/AppConstants';\nimport { useTheme } from '@cometchat/chat-uikit-react-native';\nimport { RootStackParamList } from './types';\nimport { navigationRef, processPendingNavigation } from './NavigationService';\nimport SampleUser from '../components/login/SampleUser';\nimport AppCredentials from '../components/login/AppCredentials';\nimport { Platform, StatusBar, useColorScheme } from 'react-native';\nimport notifee from '@notifee/react-native';\nimport { navigateToConversation } from '../utils/helper';\nimport Conversations from '../components/conversations/screens/Conversations';\nimport CreateConversation from '../components/conversations/screens/CreateConversation';\nimport Messages from '../components/conversations/screens/Messages';\nimport ThreadView from '../components/conversations/screens/ThreadView';\nimport UserInfo from '../components/conversations/screens/UserInfo';\nimport AddMember from '../components/conversations/screens/AddMember';\nimport BannedMember from '../components/conversations/screens/BannedMember';\nimport ViewMembers from '../components/conversations/screens/ViewMembers';\nimport GroupInfo from '../components/conversations/screens/GroupInfo';\nimport TransferOwnership from '../components/conversations/screens/TransferOwnership';\nimport Calls from '../components/calls/Calls';\nimport { CallDetails } from '../components/calls/CallDetails';\nimport Users from '../components/users/Users';\nimport Groups from '../components/groups/Groups';\nimport AIAgents from '../components/AIAgent/AIAgents';\nimport QRScreen from '../components/conversations/screens/qr_screen';\nimport SearchMessages from '../components/conversations/screens/SearchMessages';\n\ntype Props = {\n  isLoggedIn: boolean;\n  hasValidAppCredentials: boolean;\n};\n\nconst Stack = createNativeStackNavigator<RootStackParamList>();\n\nconst RootStackNavigator = ({isLoggedIn, hasValidAppCredentials: _hasValidAppCredentials}: Props) => {\n  const theme = useTheme();\n  const NavigationTheme = {\n    ...DefaultTheme,\n    colors: {\n      ...DefaultTheme.colors,\n      background: theme.color.background1 as string,\n    },\n  };\n\n  const isDark = useColorScheme() === 'dark';\n  const backgroundColor = theme.color.background2;\n  const barStyle = isDark ? 'light-content' : 'dark-content';\n\n  async function checkInitialNotification() {\n    if (Platform.OS === 'android') {\n      // Retrieve the initial notification that opened the app.\n      const initialNotification = await notifee.getInitialNotification();\n\n      if (initialNotification) {\n        const { notification } = initialNotification;\n\n        // Cancel the notification if needed.\n        if (notification?.id) {\n          await notifee.cancelNotification(notification.id);\n        }\n\n        // Retrieve data attached to the notification.\n        const data = notification?.data || {};\n        navigateToConversation(navigationRef, data);\n      }\n    }\n  }\n\n  return (\n    <>\n      <StatusBar\n        backgroundColor={backgroundColor}\n        barStyle={barStyle}\n        translucent={false}\n      />\n      <NavigationContainer\n        ref={navigationRef}\n        onReady={() => {\n          processPendingNavigation();\n          checkInitialNotification(); // Check for initial notification (SampleAppWithPushNotifications)\n        }}\n        theme={NavigationTheme}\n      >\n        <Stack.Navigator\n          id={undefined}\n          initialRouteName={\n            isLoggedIn\n              ? SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR\n              : _hasValidAppCredentials\n              ? SCREEN_CONSTANTS.SAMPLE_USER\n              : SCREEN_CONSTANTS.APP_CRED\n          }\n          screenOptions={{\n            gestureEnabled: true,\n            headerShown: false,\n            animation: 'slide_from_right',\n          }}\n        >\n          {/* Auth Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.APP_CRED}\n            component={AppCredentials}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SAMPLE_USER}\n            component={SampleUser}\n          />\n\n          {/* Tab Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR}\n            component={BottomTabNavigator}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.USERS} component={Users} />\n          <Stack.Screen name={SCREEN_CONSTANTS.GROUPS} component={Groups} />\n          <Stack.Screen name={SCREEN_CONSTANTS.AI_AGENTS} component={AIAgents} />\n\n          {/* Chat Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CONVERSATION}\n            component={Conversations}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CREATE_CONVERSATION}\n            component={CreateConversation}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.MESSAGES} component={Messages} />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.THREAD_VIEW}\n            component={ThreadView}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.SEARCH_MESSAGES}\n            component={SearchMessages}\n          />\n\n          {/* Info Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.USER_INFO}\n            component={UserInfo}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.GROUP_INFO}\n            component={GroupInfo}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.ADD_MEMBER}\n            component={AddMember}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.TRANSFER_OWNERSHIP}\n            component={TransferOwnership}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.BANNED_MEMBER}\n            component={BannedMember}\n          />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.VIEW_MEMBER}\n            component={ViewMembers}\n          />\n\n          {/* Call Screens */}\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.ONGOING_CALL_SCREEN}\n            component={OngoingCallScreen}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.CALL_LOGS} component={Calls} />\n          <Stack.Screen\n            name={SCREEN_CONSTANTS.CALL_DETAILS}\n            component={CallDetails}\n          />\n          <Stack.Screen name={SCREEN_CONSTANTS.QR_SCREEN} component={QRScreen} />\n        </Stack.Navigator>\n      </NavigationContainer>\n    </>\n  );\n};\n\nexport default RootStackNavigator;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/navigation/types.ts",
    "content": "import { NavigatorScreenParams } from '@react-navigation/native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\n\nexport type CallType = 'audio' | 'video';\n\nexport type RootStackParamList = {\n  Login: undefined;\n  BottomTabNavigator: NavigatorScreenParams<BottomTabParamList>;\n  OngoingCallScreen: { sessionId: string; callType?: CallType } | { call: any };\n  AppCredentials: undefined;\n  SampleUser: undefined;\n  Conversation: undefined;\n  CreateConversation: undefined;\n  Messages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    fromMention?: boolean;\n    fromMessagePrivately?: boolean;\n    parentMessageId?: string;\n    messageId?: string;\n    searchKeyword?: string;\n    navigatedFromSearch?: boolean;\n  };\n  SearchMessages: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  };\n  AIAgents: undefined;\n  BannedMembers: undefined;\n  UserInfo: {\n    user: CometChat.User;\n  };\n  GroupInfo: {\n    group: CometChat.Group;\n  };\n  ThreadView: {\n    message: CometChat.BaseMessage;\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    highlightMessageId?: string;\n  };\n  AddMember: {\n    group: CometChat.Group;\n  };\n  TransferOwnershipSection: {\n    group: CometChat.Group;\n  };\n  BannedMember: {\n    group: CometChat.Group;\n  };\n  ViewMembers: {\n    group: CometChat.Group;\n  };\n  CallLogs: undefined;\n  CallDetails: {\n    call: any;\n  };\n  Users: undefined;\n  Groups: undefined;\n  QRScreen: undefined;\n};\n\nexport type BottomTabParamList = {\n  Chats: undefined;\n  Calls: undefined;\n  Users: undefined;\n  Groups: undefined;\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/ActiveChatContext.tsx",
    "content": "import React, { createContext, useContext, useState } from 'react';\n\ntype ActiveChat = {\n  type: 'user' | 'group';\n  id: string; // userUID or groupID\n} | null;\n\ntype ActiveChatContextType = {\n  activeChat: ActiveChat;\n  setActiveChat: React.Dispatch<React.SetStateAction<ActiveChat>>;\n};\n\nconst ActiveChatContext = createContext<ActiveChatContextType | undefined>(undefined);\n\nexport function ActiveChatProvider({ children }: { children: React.ReactNode }) {\n  const [activeChat, setActiveChat] = useState<ActiveChat>(null);\n\n  return (\n    <ActiveChatContext.Provider value={{ activeChat, setActiveChat }}>\n      {children}\n    </ActiveChatContext.Provider>\n  );\n}\n\n// Hook for using in any component\nexport function useActiveChat() {\n  const context = useContext(ActiveChatContext);\n  if (!context) {\n    throw new Error('useActiveChat must be used within an ActiveChatProvider');\n  }\n  return context;\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/AppConstants.tsx",
    "content": "export const AppConstants = {\n  fcmProviderId: '',\n  apnsProviderId: '',\n  authKey: '',\n  appId: '',\n  region: '',\n  subscriptionType: 'ALL_USERS',\n  versionNumber: 'V5.3.4',\n  webClientId:\n    '',\n  iosClientId:\n    '',\n};\n\nexport const SCREEN_CONSTANTS = {\n  LOGIN: 'Login',\n  APP_CRED: 'AppCredentials',\n  SAMPLE_USER: 'SampleUser',\n  ONGOING_CALL_SCREEN: 'OngoingCallScreen',\n  BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator',\n  CHATS: 'Chats',\n  CALLS: 'Calls',\n  USERS: 'Users',\n  GROUPS: 'Groups',\n  CONVERSATION: 'Conversation',\n  CREATE_CONVERSATION: 'CreateConversation',\n  MESSAGES: 'Messages',\n  SEARCH_MESSAGES: 'SearchMessages',\n  THREAD_VIEW: 'ThreadView',\n  USER_INFO: 'UserInfo',\n  GROUP_INFO: 'GroupInfo',\n  ADD_MEMBER: 'AddMember',\n  TRANSFER_OWNERSHIP: 'TransferOwnershipSection',\n  BANNED_MEMBER: 'BannedMember',\n  VIEW_MEMBER: 'ViewMembers',\n  CALL_LOGS: 'CallLogs',\n  CALL_DETAILS: 'CallDetails',\n  QR_SCREEN: 'QRScreen',\n  AI_AGENTS: 'AIAgents',\n} as const;\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/CommonUtils.ts",
    "content": "export class CommonUtils {\n  static clone<T extends any>(arg: T): T {\n    /*\n    If there are additional properties attached to a function or an array object other than the standard properties, those properties will be ignored\n    Cannot copy private properties (those that start with a \"#\" symbol inside a class block)\n    Functions are copied by reference\n    */\n    if (typeof arg !== 'object' || !arg) {\n      return arg;\n    }\n    let res;\n    if (Array.isArray(arg)) {\n      // arg is an array, there's no hatch to fool the Array.isArray method, so lets create an array\n      res = [];\n      for (const value of arg) {\n        res.push(CommonUtils.clone(value));\n      }\n      return res as T;\n    } else {\n      // arg is an object\n      res = {};\n      const descriptor = Object.getOwnPropertyDescriptors(arg);\n      for (const k of Reflect.ownKeys(descriptor)) {\n        const curDescriptor = descriptor[k as any];\n        if (curDescriptor.hasOwnProperty('value')) {\n          // Property is a data property\n          Object.defineProperty(res, k, {\n            ...curDescriptor,\n            value: CommonUtils.clone(curDescriptor['value']),\n          });\n        } else {\n          // Property is an accessor property\n          Object.defineProperty(res, k, curDescriptor);\n        }\n      }\n      Object.setPrototypeOf(res, Object.getPrototypeOf(arg));\n    }\n    return res as T;\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/PendingCallManager.ts",
    "content": "\n// A lightweight manager to hold a pending answered call\n// when the app cold-starts (killed state) and navigation / login\n// are not yet ready.\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\nexport interface PendingAnsweredCallPayload {\n  sessionId: string;\n  raw: any; // original call object (voipHandler.msg)\n  storedAt: number;\n}\n\nlet inMemoryPending: PendingAnsweredCallPayload | null = null;\nconst STORAGE_KEY = 'pendingAnsweredCall';\n\nexport async function setPendingAnsweredCall(payload: PendingAnsweredCallPayload) {\n  inMemoryPending = payload;\n  try {\n    await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(payload));\n  } catch (e) {\n    // non-fatal\n    console.log('[PendingCallManager] Failed to persist pending call', e);\n  }\n}\n\nexport async function consumePendingAnsweredCall(): Promise<PendingAnsweredCallPayload | null> {\n  if (inMemoryPending) {\n    const tmp = inMemoryPending;\n    inMemoryPending = null;\n    try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {}\n    return tmp;\n  }\n  try {\n    const raw = await AsyncStorage.getItem(STORAGE_KEY);\n    if (raw) {\n      await AsyncStorage.removeItem(STORAGE_KEY);\n      const parsed: PendingAnsweredCallPayload = JSON.parse(raw);\n      inMemoryPending = null;\n      return parsed;\n    }\n  } catch (e) {\n    console.log('[PendingCallManager] Failed to read pending call', e);\n  }\n  return null;\n}\n\n// Optionally prune stale pending calls (e.g., older than 2 minutes)\nexport function isPendingStale(p: PendingAnsweredCallPayload, maxAgeMs = 2 * 60 * 1000) {\n  return Date.now() - p.storedAt > maxAgeMs;\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/PushNotification.tsx",
    "content": "import {CometChatNotifications} from '@cometchat/chat-sdk-react-native';\nimport {Platform} from 'react-native';\nimport {AppConstants} from './AppConstants';\n\n// Determine the push platform based on FCM and VoIP flags\nconst getPushPlatform = (isFcm: boolean, isVoip: boolean) => {\n  if (isFcm) {\n    return Platform.OS === 'android'\n      ? CometChatNotifications.PushPlatforms.FCM_REACT_NATIVE_ANDROID\n      : CometChatNotifications.PushPlatforms.FCM_REACT_NATIVE_IOS;\n  } else {\n    return isVoip\n      ? CometChatNotifications.PushPlatforms.APNS_REACT_NATIVE_VOIP\n      : CometChatNotifications.PushPlatforms.APNS_REACT_NATIVE_DEVICE;\n  }\n};\n\n// Get the provider ID based on FCM\nconst getProviderId = (isFcm: boolean) => {\n  return isFcm ? AppConstants.fcmProviderId : AppConstants.apnsProviderId;\n};\n\nexport const registerPushToken = async (\n  token: string,\n  isFcm = true,\n  isVoip = false,\n) => {\n  try {\n    const platform = getPushPlatform(isFcm, isVoip);\n    const providerId = getProviderId(isFcm);\n    console.log('Push Platform:', platform);\n\n    const response = await CometChatNotifications.registerPushToken(\n      token,\n      platform,\n      providerId,\n    );\n\n    console.log('registerPushToken:success', response);\n    return response;\n  } catch (error) {\n    console.error('registerPushToken:error', error);\n    return null;\n  }\n};\n\n// Unregister Push Token from CometChat\nexport const unregisterPushToken = async () => {\n  try {\n    const response = await CometChatNotifications.unregisterPushToken();\n    console.log('unregisterPushToken:success', response);\n    return response;\n  } catch (error) {\n    console.error('unregisterPushToken:error', error);\n    return null;\n  }\n};\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/TooltipMenu.tsx",
    "content": "import { Icon, useTheme } from \"@cometchat/chat-uikit-react-native\";\nimport { JSX, useMemo } from \"react\";\nimport {\n  ColorValue,\n  Dimensions,\n  ImageSourcePropType,\n  Modal,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  TouchableWithoutFeedback,\n  View,\n} from \"react-native\";\n\nconst { width: screenWidth, height: screenHeight } = Dimensions.get(\"window\");\n\ntype CometChatTooltipMenuProps = {\n  visible?: boolean;\n  onDismiss?: () => void;\n  onClose?: () => void;\n  event: {\n    nativeEvent: {\n      pageX: number;\n      pageY: number;\n    };\n  };\n  menuItems: {\n    text: string;\n    onPress: () => void;\n    textColor?: ColorValue;\n    iconColor?: ColorValue;\n    icon?: ImageSourcePropType | JSX.Element;\n  }[];\n};\n\nexport const TooltipMenu = (props: CometChatTooltipMenuProps) => {\n  const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;\n  const theme = useTheme();\n\n  const position = useMemo(() => {\n    let x = event.nativeEvent.pageX;\n    let y = event.nativeEvent.pageY;\n    const position: {\n      left?: number;\n      right?: number;\n      top?: number;\n      bottom?: number;\n    } = {};\n    if (x <= screenWidth / 3) {\n      position.left = x + 10;\n    } else {\n      position.right = 12;\n    }\n\n    if (y <= screenHeight / 2) {\n      position.top = y + 20;\n    } else if (y >= screenHeight / 2) {\n      position.bottom = Math.max(screenHeight - y + 10, 40);\n    }\n    return position;\n  }, [event]);\n\n  return (\n    <Modal\n      presentationStyle='overFullScreen'\n      transparent={true}\n      visible={visible}\n      onRequestClose={onClose}\n      onDismiss={onDismiss}\n      animationType='fade'\n    >\n      <TouchableWithoutFeedback onPress={onClose}>\n        <View style={tooltipStyles.overlay}>\n          <View\n            style={[\n              tooltipStyles.menu,\n              position,\n              {\n                backgroundColor: theme.color.background1,\n                borderWidth: 1,\n                borderColor: theme.color.borderLight,\n                borderRadius: theme.spacing.radius.r2,\n                shadowColor: theme.color.neutral900,\n              },\n            ]}\n          >\n            {menuItems.map((item, i) => {\n              return (\n                <TouchableOpacity\n                  key={i} // Ensure each item has a unique key\n                  onPress={() => {\n                    item.onPress();\n                    onClose();\n                  }}\n                  style={[\n                    {\n                      flexDirection: \"row\",\n                      alignItems: \"center\",\n                      paddingVertical: 10,\n                      paddingHorizontal: 16,\n                      gap: 8,\n                      backgroundColor: theme.color.background1,\n                      minWidth: 160,\n                    },\n                    i === 0\n                      ? {\n                          borderTopLeftRadius: theme.spacing.radius.r2,\n                          borderTopRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                    i === menuItems.length - 1\n                      ? {\n                          borderBottomLeftRadius: theme.spacing.radius.r2,\n                          borderBottomRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                  ]}\n                >\n                  <Icon\n                    color={item.iconColor ?? theme.color.textSecondary}\n                    size={theme.spacing.spacing.s6}\n                    icon={item.icon}\n                  />\n                  <Text\n                    style={[\n                      {\n                        color: item.textColor ?? theme.color.textPrimary,\n                        ...theme.typography.heading4.regular,\n                      },\n                    ]}\n                  >\n                    {item.text}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n      </TouchableWithoutFeedback>\n    </Modal>\n  );\n};\n\nconst tooltipStyles = StyleSheet.create({\n  overlay: {\n    flex: 1,\n    backgroundColor: \"transparent\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  menu: {\n    position: \"absolute\",\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.025,\n    shadowRadius: 4,\n    elevation: 3,\n  },\n  menuItem: {\n    fontSize: 16,\n    paddingVertical: 5,\n  },\n});\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/VoipNotificationHandler.ts",
    "content": "import { Platform } from 'react-native';\nimport notifee, { AndroidImportance } from '@notifee/react-native';\nimport RNCallKeep, { IOptions } from 'react-native-callkeep';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { navigate, navigationRef } from '../navigation/NavigationService';\nimport { setPendingAnsweredCall } from './PendingCallManager';\nimport VoipPushNotification from 'react-native-voip-push-notification';\n\n/**\n * Generate a unique UUID (Universally Unique Identifier).\n * This will be used, for example, as a 'callUUID' in RNCallKeep so it can track calls.\n */\nfunction generateUUID(): string {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n    const r = (Math.random() * 16) | 0;\n    const v = c === 'x' ? r : (r & 0x3) | 0x8;\n    return v.toString(16);\n  });\n}\n\n/**\n * Configuration options for RNCallKeep (for both iOS and Android).\n * This is mostly about setting up how the app handles calls natively on each platform.\n */\nconst options: IOptions = {\n  ios: {\n    appName: 'Sampleapp',\n  },\n  android: {\n    alertTitle: 'VOIP required',\n    alertDescription:\n      'This application needs to access your phone accounts to make calls',\n    cancelButton: 'Cancel',\n    okButton: 'OK',\n    imageName: 'ic_notification', // For Android notifications\n    additionalPermissions: [],\n    foregroundService: {\n      channelId: 'com.cometchat.sampleapp.reactnative.android',\n      channelName: 'Sampleapp Channel',\n      notificationTitle: 'Sampleapp is running in the background',\n    },\n  },\n};\n\nexport class VoipNotificationHandler {\n  channelId: string = '';\n  currentNotificationId: string = '';\n  // Whether the phone is currently 'ringing'\n  isRinging: boolean = false;\n  // Whether the call has been answered\n  isAnswered: boolean = false;\n  // Whether call acceptance is deferred waiting for login/navigation\n  pendingAcceptance: boolean = false;\n  // UUID of the caller\n  callerId: string = '';\n  // Object that holds data about the incoming message/call\n  msg: any = {};\n  // initialization guard\n  initialized: boolean = false;\n  // ensures setup only runs once per process\n  private setupPromise: Promise<void> | null = null;\n  // prevents attaching duplicate listeners after reloads\n  private listenersAttached: boolean = false;\n\n  constructor() {\n    // Lazy initialization; explicit initialize() will be called from App once environment ready.\n  }\n\n  /**\n   * The main initialization function that sets up:\n   *  - RNCallKeep permissions\n   *  - The Notifee channel (on Android)\n   *  - Event listeners for incoming calls and actions\n   */\n  async initialize(): Promise<void> {\n    if (this.initialized && this.setupPromise) {\n      await this.setupPromise;\n      return;\n    }\n\n    if (!this.setupPromise) {\n      this.setupPromise = (async () => {\n        if (Platform.OS === 'android') {\n          await this.createNotificationChannel();\n        }\n        await this.getPermissions();\n        this.setupEventListeners();\n        this.initialized = true;\n      })().catch(error => {\n        this.setupPromise = null;\n        throw error;\n      });\n    }\n\n    await this.setupPromise;\n  }\n\n  /**\n   * Request permissions and set up RNCallKeep for handling calls.\n   * - On iOS, checks/requests permission to show native call UI.\n   * - On Android, sets up phone account if needed.\n   */\n  async getPermissions() {\n    try {\n      // Setup RNCallKeep using the provided options\n      await RNCallKeep.setup(options);\n      // Mark the device as available and reachable for calls\n      RNCallKeep.setAvailable(true);\n      RNCallKeep.setReachable();\n      // Check if a phone account is enabled (Android-specific)\n      try {\n        await RNCallKeep.checkPhoneAccountEnabled();\n      } catch (err) {\n        console.log(\n          '[CallKeep] Phone account not enabled yet, will retry later.',\n        );\n      }\n    } catch (err) {\n      console.error('[VoipNotificationHandler] Error in getPermissions:', err);\n    }\n  }\n\n  /**\n   * Create a Notifee notification channel on Android\n   * so that notifications have the correct importance level (HIGH),\n   * vibration, lights, etc.\n   */\n  async createNotificationChannel() {\n    try {\n      this.channelId = await notifee.createChannel({\n        id: 'message',\n        name: 'Messages',\n        lights: true,\n        vibration: true,\n        importance: AndroidImportance.HIGH,\n      });\n    } catch (error) {\n      console.error(\n        '[VoipNotificationHandler] Error in createNotificationChannel:',\n        error,\n      );\n    }\n  }\n\n  /**\n   * Display the native incoming call screen on Android using RNCallKeep.\n   * - Called when an incoming call notification is received.\n   */\n  async displayCallAndroid(): Promise<void> {\n    if (this.isAnswered || this.pendingAcceptance) {\n      console.log(\n        '[VoipNotificationHandler] Skipping displayCallAndroid - already answered or pending acceptance',\n      );\n      return;\n    }\n\n    try {\n      await this.initialize();\n    } catch (error) {\n      console.error(\n        '[VoipNotificationHandler] Failed to initialize before displaying call:',\n        error,\n      );\n    }\n\n    this.isRinging = true;\n    this.callerId = generateUUID();\n\n    if (this.msg) {\n      const callerName =\n        typeof this.msg.senderName === 'string' && this.msg.senderName.trim()\n          ? this.msg.senderName\n          : 'Incoming Call';\n      // Display the incoming call UI with the caller's name\n      try {\n        await RNCallKeep.displayIncomingCall(\n          this.callerId,\n          callerName,\n          callerName,\n          'generic',\n        );\n      } catch (error) {\n        this.isRinging = false;\n        console.error(\n          '[VoipNotificationHandler] displayCallAndroid => displayIncomingCall failed:',\n          error,\n        );\n        throw error;\n      }\n    } else {\n      console.error(\n        '[VoipNotificationHandler] displayCallAndroid => No call data found in this.msg!',\n      );\n      this.isRinging = false;\n    }\n  }\n\n  /**\n   * Callback function for 'didDisplayIncomingCall' event (iOS only),\n   * which is fired when the native iOS call screen is displayed.\n   * - This sets the internal callerId if provided by iOS,\n   *   and marks that we are indeed ringing.\n   */\n  didDisplayIncomingCall(args: { callUUID?: string; error?: any }) {\n    if (args.callUUID && Platform.OS === 'ios') {\n      this.callerId = args.callUUID;\n    }\n    if (args.error) {\n      console.error(\n        '[VoipNotificationHandler] didDisplayIncomingCall error =>',\n        args.error,\n      );\n    }\n    this.isRinging = true;\n  }\n\n  /**\n   * Ends the call with a given UUID (or fallback to our stored callerId).\n   * - This is typically used when we manually want to remove the dialer UI.\n   */\n  removeCallDialerWithUUID = (callerId: string) => {\n    const uuidToEnd = callerId || this.callerId;\n    if (uuidToEnd) {\n      // 6 -> Call hung up\n      RNCallKeep.reportEndCallWithUUID(uuidToEnd, 6);\n    }\n  };\n\n  /**\n   * Handles the 'answerCall' event from RNCallKeep.\n   * - When the user answers the call on the native call screen,\n   *   bring the app to the foreground, accept the call via CometChat,\n   *   and then navigate to our 'OngoingCallScreen'.\n   */\n  onAnswerCall = async ({ callUUID }: { callUUID: string }) => {\n    if (this.isAnswered) {\n      return; // Avoid double-answer\n    }\n\n    this.isRinging = false;\n    this.isAnswered = true;\n\n    const sessionID = this.msg?.sessionId;\n    if (!sessionID) {\n      console.error(\n        '[VoipNotificationHandler] onAnswerCall => No session ID to accept call.',\n      );\n      return;\n    }\n\n    // Defer logic slightly so app/root initialization (cold start) can happen\n    setTimeout(async () => {\n      try {\n        // If there is no logged in user yet OR navigation not ready, stash for later.\n        const loggedInUser = await CometChat.getLoggedinUser().catch(\n          () => null,\n        );\n        if (!loggedInUser || !navigationRef.isReady()) {\n          console.log(\n            '[VoipNotificationHandler] Deferring call acceptance until login/navigation ready',\n          );\n          this.pendingAcceptance = true;\n          await setPendingAnsweredCall({\n            sessionId: sessionID,\n            raw: this.msg,\n            storedAt: Date.now(),\n          });\n          // Bring app foreground anyway\n          RNCallKeep.backToForeground();\n          return;\n        }\n\n        let acceptedCall: any = null;\n        try {\n          acceptedCall = await CometChat.acceptCall(sessionID);\n          console.log('[VoipNotificationHandler] acceptCall => success');\n        } catch (error: any) {\n          if (error?.code === 'ERR_CALL_USER_ALREADY_JOINED') {\n            console.log(\n              '[VoipNotificationHandler] Already joined; using active call',\n            );\n            acceptedCall = CometChat.getActiveCall();\n          } else {\n            throw error;\n          }\n        }\n\n        if (Platform.OS === 'android') {\n          RNCallKeep.endAllCalls();\n        }\n        \n        const active = acceptedCall || CometChat.getActiveCall();\n        const callTypeForNav =\n          (typeof active?.getType === 'function'\n            ? active.getType()\n            : undefined) ??\n          (this.msg?.callType as any) ??\n          (this.msg?.type as any);\n\n        navigate('OngoingCallScreen', {\n          sessionId: sessionID,\n          callType: callTypeForNav,\n        });\n      } catch (error: any) {\n        console.error(\n          '[VoipNotificationHandler] Accept call error (deferred path):',\n          error,\n        );\n      } finally {\n        RNCallKeep.backToForeground();\n        this.pendingAcceptance = false;\n      }\n    }, 600); // shorter delay – rely on deferral if not ready\n  };\n\n  /**\n   * Convenience method to end all calls on the dialer UI immediately.\n   */\n  removeCallDialer() {\n    RNCallKeep.endAllCalls();\n  }\n\n  endCall = async ({ callUUID }: { callUUID: string }) => {\n    if (this.msg?.type === 'call') {\n      const sessionID = this.msg.sessionId;\n      if (this.isAnswered && sessionID) {\n        this.isAnswered = false;\n        CometChat.endCall(sessionID);\n      } else if (sessionID) {\n        try {\n          const loggedInUser = await CometChat.getLoggedinUser().catch(\n            () => null,\n          );\n          if (loggedInUser) {\n            setTimeout(() => {\n              CometChat.rejectCall(sessionID, CometChat.CALL_STATUS.REJECTED);\n            }, 300);\n          } else {\n            console.log(\n              '[VoipNotificationHandler] Skipping rejectCall: user not logged in (likely cold start)',\n            );\n          }\n        } catch (err) {\n          console.error(\n            '[VoipNotificationHandler] endCall => Error handling rejection logic:',\n            err,\n          );\n        }\n      }\n    }\n\n    const callIdToEnd = callUUID || this.callerId;\n    if (callIdToEnd) {\n      RNCallKeep.endCall(callIdToEnd);\n    }\n    RNCallKeep.endAllCalls();\n    this.isRinging = false;\n    this.isAnswered = false;\n    this.pendingAcceptance = false;\n    this.callerId = '';\n    this.msg = {};\n  };\n\n  /**\n   * Cancel the Notifee notification if one is shown.\n   * - Typically used if you have a custom notification that\n   *   you need to cancel for an incoming call.\n   */\n  async cancel(notificationId: any) {\n    await notifee.cancelNotification(\n      notificationId || this.currentNotificationId,\n    );\n  }\n\n  /**\n   * Set up various event listeners for incoming VOIP push notifications\n   * (on iOS) and for RNCallKeep (on both iOS and Android).\n   */\n  setupEventListeners() {\n    if (this.listenersAttached) {\n      return;\n    }\n\n    // For iOS VOIP push notifications\n    if (Platform.OS === 'ios') {\n      VoipPushNotification.addEventListener(\n        'notification',\n        (notification: any) => {\n          // Store the call data\n          console.log(\n            '[VoipNotificationHandler] Received VOIP push notification:',\n            notification,\n          );\n          this.msg = notification;\n        },\n      );\n\n      VoipPushNotification.addEventListener('didLoadWithEvents', events => {\n        if (!events || !Array.isArray(events) || events.length < 1) {\n          return;\n        }\n        for (let voipPushEvent of events) {\n          let { name, data } = voipPushEvent;\n          if (\n            name ===\n            VoipPushNotification.RNVoipPushRemoteNotificationReceivedEvent\n          ) {\n            this.msg = data;\n          }\n        }\n      });\n    }\n\n    // RNCallKeep events for all platforms\n    RNCallKeep.addEventListener('answerCall', this.onAnswerCall);\n    RNCallKeep.addEventListener('endCall', this.endCall);\n    RNCallKeep.addEventListener(\n      'didDisplayIncomingCall',\n      this.didDisplayIncomingCall.bind(this),\n    );\n\n    this.listenersAttached = true;\n  }\n}\n\n// Export a single instance of VoipNotificationHandler to be reused\nexport const voipHandler = new VoipNotificationHandler();"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/helper.ts",
    "content": "import {Platform, PermissionsAndroid} from 'react-native';\nimport messaging from '@react-native-firebase/messaging';\nimport notifee, {AndroidImportance} from '@notifee/react-native';\nimport PushNotificationIOS from '@react-native-community/push-notification-ios';\nimport {CometChat} from '@cometchat/chat-sdk-react-native';\nimport {navigate} from '../navigation/NavigationService';\nimport Ironman from '../assets/icons/ironman.png';\nimport Captainamerica from '../assets/icons/captainamerica.png';\nimport Wolverine from '../assets/icons/wolverine.png';\nimport Spiderman from '../assets/icons/spiderman.png';\nimport Cyclops from '../assets/icons/cyclops.png';\nimport {registerPushToken} from './PushNotification';\nimport {\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n} from '@cometchat/chat-uikit-react-native';\nimport {\n  NavigationContainerRefWithCurrent,\n  StackActions,\n} from '@react-navigation/native';\nimport {RootStackParamList} from '../navigation/types';\nimport {SCREEN_CONSTANTS} from './AppConstants';\nimport dayjs from 'dayjs';\n\ninterface Translations {\n  lastSeen: string;\n  minutesAgo: (minutes: number) => string;\n  hoursAgo: (hours: number) => string;\n}\n\ninterface NotifeeData {\n  receiverType?: 'user' | 'group';\n  conversationId?: string;\n  sender?: string;\n  messageId?: string;\n  parentId?: string;\n  [key: string]: any;\n}\n\n/**\n * Display a local notification (Android) using Notifee.\n * This is triggered when the app is in the foreground.\n * Uses notification grouping with summary to show unread count.\n */\nexport async function displayLocalNotification(\n  remoteMessage: any,\n  activeChat?: any,\n) {\n  try {\n    if (remoteMessage?.data?.type !== 'chat') {\n      return;\n    }\n    if (\n      activeChat &&\n      ((activeChat.type === 'user' &&\n        String(activeChat.id) === String(remoteMessage?.data?.sender)) ||\n        (activeChat.type === 'group' &&\n          String(activeChat.id) === String(remoteMessage?.data?.receiver)))\n    ) {\n      return;\n    }\n\n    const {title, body, senderAvatar} = remoteMessage.data || {};\n    const skey = remoteMessage.sentTime.toString();\n    const channelId = await notifee.createChannel({\n      id: 'chat-messages',\n      name: 'Chat Messages',\n      vibration: true,\n      importance: AndroidImportance.HIGH,\n    });\n\n    // Extract parent ID for agentic messages\n    let parentId: string | undefined;\n    let messageId: string | undefined;\n    \n    try {\n      if (remoteMessage.data?.message) {\n        const parsedMessage = JSON.parse(remoteMessage.data.message);\n        parentId = parsedMessage.parentId;\n        messageId = parsedMessage.id;\n      }\n      // Fallback to tag if message parsing fails\n      if (!messageId && remoteMessage.data?.tag) {\n        messageId = remoteMessage.data.tag;\n      }\n    } catch (error) {\n      console.log('Error parsing message data:', error);\n      // Use tag as fallback\n      if (remoteMessage.data?.tag) {\n        messageId = remoteMessage.data.tag;\n      }\n    }\n\n    const notificationData = {\n      receiverType: remoteMessage.data?.receiverType,\n      sender: remoteMessage.data?.sender,\n      conversationId: remoteMessage.data?.conversationId,\n      ...(messageId && { messageId }),\n      ...(parentId && { parentId }),\n    };\n\n    // Get badge count from payload\n    const unreadCount = remoteMessage.data?.unreadMessageCount;\n    const parsedCount = unreadCount != null ? parseInt(String(unreadCount), 10) : NaN;\n    const badgeCount = !isNaN(parsedCount) && parsedCount >= 0 ? parsedCount : undefined;\n\n    // Add unread count to title if more than 1\n    const displayTitle = badgeCount && badgeCount > 1\n      ? `${title || 'New Message'} (${badgeCount} unread)`\n      : title || 'New Message';\n\n    // Set badge count directly from backend unreadMessageCount\n    if (badgeCount != null && badgeCount > 0) {\n      await notifee.setBadgeCount(badgeCount);\n    }\n\n    // Use fixed notification ID so Samsung doesn't add badge counts from multiple notifications\n    // This ensures badge shows exact unreadMessageCount from backend\n    // Build android config — only include badgeCount if it's a valid number\n    const androidConfig: any = {\n      channelId,\n      sortKey: skey,\n      autoCancel: true,\n      smallIcon: 'ic_notification',\n      largeIcon:\n        senderAvatar ||\n        'https://cdn-icons-png.flaticon.com/512/149/149071.png',\n      importance: AndroidImportance.HIGH,\n      pressAction: {\n        id: 'default',\n      },\n    };\n    if (badgeCount != null) {\n      androidConfig.badgeCount = badgeCount;\n    }\n\n    await notifee.displayNotification({\n      id: 'chat-notification',\n      title: displayTitle,\n      body: body || 'You received a new message.',\n      android: androidConfig,\n      data: notificationData,\n    });\n  } catch (error) {\n    console.error('displayLocalNotification error:', error);\n  }\n}\n\n/**\n * Request common Android permissions (notifications, camera, etc.)\n * Only needed on Android.\n */\nexport async function requestAndroidPermissions() {\n  if (Platform.OS !== 'android') return;\n\n  try {\n    // Ask for push‑notification permission\n    const authStatus = await messaging().requestPermission();\n    const enabled =\n      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||\n      authStatus === messaging.AuthorizationStatus.PROVISIONAL;\n\n    if (!enabled) {\n      console.warn('Notification permission denied (FCM).');\n    }\n  } catch (error) {\n    console.warn('FCM permission request error:', error);\n  }\n\n  try {\n    await PermissionsAndroid.requestMultiple([\n      PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,\n      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,\n    ]);\n  } catch (err) {\n    console.warn('Android permissions error:', err);\n  }\n}\n\n/**\n * Retrieve the initial iOS push notification (if the user tapped on one\n * to open the app) and navigate to the correct screen. (iOS only)\n */\nexport async function checkInitialNotificationIOS() {\n  if (Platform.OS !== 'ios') return;\n\n  try {\n    const notification = await PushNotificationIOS.getInitialNotification();\n    if (notification) {\n      const data = notification.getData();\n      if (data && data.type === 'chat') {\n        // Extract parent ID for agentic messages\n        let parentId: string | undefined;\n        try {\n          if (data.message) {\n            const parsedMessage = JSON.parse(data.message);\n            parentId = parsedMessage.parentId;\n          }\n        } catch (error) {\n          console.log('Error parsing iOS message data:', error);\n        }\n\n        if (data.receiverType === 'group') {\n          try {\n            const group = await CometChat.getGroup(data.receiver);\n            const params: any = { group };\n            if (parentId) {\n              params.parentMessageId = parentId;\n            }\n            navigate(SCREEN_CONSTANTS.MESSAGES, params);\n          } catch (error) {\n            console.log('Error fetching group details:', error);\n          }\n        } else if (data.receiverType === 'user') {\n          try {\n            const user = await CometChat.getUser(data.sender);\n            const params: any = { user };\n            if (parentId) {\n              params.parentMessageId = parentId;\n            }\n            navigate(SCREEN_CONSTANTS.MESSAGES, params);\n          } catch (error) {\n            console.log('Error fetching user details:', error);\n          }\n        }\n      }\n    }\n  } catch (error) {\n    console.error('checkInitialNotificationIOS error:', error);\n  }\n}\n\n/**\n * Handle remote notification in iOS. If the user taps on it,\n * navigate accordingly. (Foreground or background scenario)\n */\nexport async function onRemoteNotificationIOS(notification: any) {\n  // Handle badge count from push notification\n  const data = notification.getData();\n  const unreadCount = data?.unreadMessageCount;\n  if (unreadCount !== undefined && unreadCount !== null) {\n    const count = parseInt(unreadCount, 10);\n    if (!isNaN(count) && count >= 0) {\n      PushNotificationIOS.setApplicationIconBadgeNumber(count);\n    }\n  } else {\n    console.log('No unreadMessageCount in payload - check dashboard settings');\n  }\n\n  const isClicked = data?.userInteraction === 1;\n  if (isClicked) {\n    if (data && data.type === 'chat') {\n      // Extract parent ID for agentic messages\n      let parentId: string | undefined;\n      try {\n        if (data.message) {\n          const parsedMessage = JSON.parse(data.message);\n          parentId = parsedMessage.parentId;\n        }\n      } catch (error) {\n        console.log('Error parsing iOS message data:', error);\n      }\n\n      if (data.receiverType === 'group') {\n        try {\n          const group = await CometChat.getGroup(data.receiver);\n          const params: any = { group };\n          if (parentId) {\n            params.parentMessageId = parentId;\n          }\n          navigate(SCREEN_CONSTANTS.MESSAGES, params);\n        } catch (error) {\n          console.log('Error fetching group details:', error);\n        }\n      } else if (data.receiverType === 'user') {\n        try {\n          const user = await CometChat.getUser(data.sender);\n          const params: any = { user };\n          if (parentId) {\n            params.parentMessageId = parentId;\n          }\n          navigate(SCREEN_CONSTANTS.MESSAGES, params);\n        } catch (error) {\n          console.log('Error fetching user details:', error);\n        }\n      }\n    }\n  }\n  // Must call finish to let iOS know we're done processing the notification\n  notification.finish(PushNotificationIOS.FetchResult.NoData);\n}\n\n/**\n * Retrieve and register the FCM token with CometChat (Android only).\n */\nexport async function getAndRegisterFCMToken(\n  user: boolean,\n  currentToken: string,\n  isTokenRegistered: boolean,\n  setIsTokenRegistered: (val: boolean) => void,\n  setCurrentToken: (token: string) => void,\n) {\n  try {\n    await messaging().registerDeviceForRemoteMessages();\n    const token = await messaging().getToken();\n    console.log('FCM Token:', token);\n\n    if (user && !isTokenRegistered) {\n      if (token !== currentToken) {\n        await registerPushToken(token, true, false);\n        setIsTokenRegistered(true);\n        setCurrentToken(token);\n      }\n    }\n  } catch (error) {\n    console.error('Failed to get FCM Token:', error);\n  }\n}\n\n/**\n * Register iOS's APNs (non-VoIP) token with CometChat.\n */\nexport async function handleIosApnsToken(\n  user: boolean,\n  deviceToken: string,\n  currentToken: string,\n  isTokenRegistered: boolean,\n  setCurrentToken: (token: string) => void,\n  setIsTokenRegistered: (val: boolean) => void,\n) {\n  if (user && deviceToken !== currentToken && !isTokenRegistered) {\n    try {\n      await registerPushToken(deviceToken, false, false);\n      console.log('APNs device token registered successfully with CometChat.');\n      setCurrentToken(deviceToken);\n      setIsTokenRegistered(true);\n    } catch (err) {\n      console.error('APNs device token registration failed:', err);\n    }\n  }\n}\n\n/**\n * Register iOS VoIP token with CometChat.\n */\nexport async function handleIosVoipToken(user: boolean, voipToken: string) {\n  if (user) {\n    try {\n      await registerPushToken(voipToken, false, true);\n      console.log('APNs VOIP token registered successfully with CometChat.');\n    } catch (err) {\n      console.error('APNs VOIP token registration failed:', err);\n    }\n  }\n}\n\n/**\n * getLastSeenTime UserInfoSection.\n */\nexport function getLastSeenTime(\n  timestamp: number | null,\n  translations: Translations,\n): string {\n  if (timestamp === null) {\n    return `${translations.lastSeen} Unknown`;\n  }\n\n  // If timestamp is in seconds (length = 10), convert to milliseconds.\n  if (String(timestamp).length === 10) {\n    timestamp *= 1000;\n  }\n\n  const now = new Date();\n  const lastSeen = new Date(timestamp);\n\n  // Calculate the time differences\n  const diffInMillis = now.getTime() - lastSeen.getTime();\n  const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));\n  const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));\n\n  // Check if within last hour\n  if (diffInMinutes === 0) {\n    return `${translations.lastSeen} ${translations.minutesAgo(1)}`;\n  } else if (diffInMinutes < 60) {\n    return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;\n  }\n\n  // Check if within the last 24 hours\n  if (diffInHours < 24) {\n    return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;\n  }\n\n  // Determine if timestamp is within the current year\n  const isSameYear = lastSeen.getFullYear() === now.getFullYear();\n\n  // Options for date formatting\n  const dateOptions: Intl.DateTimeFormatOptions = {\n    day: '2-digit',\n    month: 'short',\n    ...(isSameYear ? {} : { year: 'numeric' }),\n  };\n\n  // Options for time formatting\n  const timeOptions: Intl.DateTimeFormatOptions = {\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: true,\n  };\n\n  const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);\n  const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);\n  if (formattedDate === 'Invalid Date' || formattedTime === 'Invalid Date') {\n    return `Offline`;\n  }\n\n  return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;\n}\n\n/**\n * UNBLOCK\n */\nexport const unblock = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n  setUserObj: React.Dispatch<React.SetStateAction<CometChat.User>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.unblockUsers([uid]);\n    const unBlockedUser = await CometChat.getUser(uid);\n    if (response) {\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, {\n        user: unBlockedUser,\n      });\n      setBlocked(false);\n      setUserObj(unBlockedUser);\n    } else {\n      console.log(\n        `Failed to unblock user with UID ${uid}. Response:`,\n        response,\n      );\n    }\n  } catch (error) {\n    console.error('Error unblocking user:', error);\n  }\n};\n\n/**\n * BLOCK\n */\nexport const blockUser = async (\n  uid: string,\n  user: CometChat.User,\n  setBlocked: React.Dispatch<React.SetStateAction<boolean>>,\n): Promise<void> => {\n  try {\n    const response = await CometChat.blockUsers([uid]);\n    if (response) {\n      user.setBlockedByMe(true);\n      setBlocked(true);\n      CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, {\n        user,\n      });\n    } else {\n      console.log(`Failed to block user with UID ${uid}. Response:`, response);\n    }\n  } catch (error) {\n    console.error('Error blocking user:', error);\n  }\n};\n\n/**\n * LEAVE GROUP\n */\nexport const leaveGroup = (\n  group: CometChat.Group,\n  navigation: any,\n  pop: number,\n) => {\n  if (group) {\n    const groupID = group.getGuid();\n    CometChat.leaveGroup(groupID).then(\n      () => {\n        let actionMessage: CometChat.Action = new CometChat.Action(\n          groupID,\n          CometChat.MESSAGE_TYPE.TEXT,\n          CometChat.RECEIVER_TYPE.GROUP,\n          CometChat.CATEGORY_ACTION as CometChat.MessageCategory,\n        );\n        actionMessage.setMessage(\n          `${CometChatUIKit.loggedInUser!.getName()} has left`,\n        );\n        // Initialize data to prevent crash when SDK accesses getData().metadata during render\n        actionMessage.setData({ metadata: {} });\n        CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n          message: actionMessage, //Note: Add Action message after discussion\n          leftUser: CometChatUIKit.loggedInUser,\n          leftGroup: group,\n        });\n        navigation.pop(pop);\n      },\n      error => {\n        console.log('Group leaving failed:', error);\n      },\n    );\n  } else {\n    console.log('Group is not defined');\n  }\n};\n\n/**\n * Sample Users Data\n */\nexport const sampleData = {\n  users: [\n    {uid: 'superhero1', name: 'Iron Man', avatar: Ironman},\n    {uid: 'superhero2', name: 'Captain America', avatar: Captainamerica},\n    {uid: 'superhero3', name: 'Spiderman', avatar: Spiderman},\n    {uid: 'superhero4', name: 'Wolverine', avatar: Wolverine},\n    {uid: 'superhero5', name: 'Cyclops', avatar: Cyclops},\n  ],\n};\n\n/**\n * Navigate to conversation based on notification data.\n */\nexport async function navigateToConversation(\n  navigationRef: NavigationContainerRefWithCurrent<RootStackParamList>,\n  data?: NotifeeData,\n) {\n  if (!data || !navigationRef.current) {\n    return;\n  }\n  \n  try {\n    // Handle group\n    if (data.receiverType === 'group') {\n      const extractedId =\n        typeof data.conversationId === 'string'\n          ? data.conversationId.split('_').slice(1).join('_')\n          : '';\n      const group = await CometChat.getGroup(extractedId);\n\n      // Navigate with parent message ID if available (for agentic conversations)\n      const params: any = {group};\n      if (data.parentId) {\n        params.parentMessageId = data.parentId;\n      }\n\n      navigationRef.current?.dispatch(StackActions.push(SCREEN_CONSTANTS.MESSAGES, params));\n    }\n\n    // Handle user\n    else if (data.receiverType === 'user') {\n      const ccUser = await CometChat.getUser(data.sender);\n\n      // Navigate with parent message ID if available (for agentic conversations)\n      const params: any = {user: ccUser};\n      if (data.parentId) {\n        params.parentMessageId = data.parentId;\n      }\n\n      navigationRef.current?.dispatch(\n        StackActions.push(SCREEN_CONSTANTS.MESSAGES, params),\n      );\n    }\n  } catch (error) {\n    console.log('Error in navigateToConversation:', error);\n  }\n}\n"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/src/utils/themeTypography.ts",
    "content": "import { Platform } from 'react-native';\n\nexport type TypographyVariant = {\n  regular: { fontFamily: string };\n  medium: { fontFamily: string };\n  bold: { fontFamily: string };\n};\n\nexport type Typography = {\n  fontFamily: string;\n  link: { fontFamily: string };\n} & Record<\n  | \"title\"\n  | \"heading1\"\n  | \"heading2\"\n  | \"heading3\"\n  | \"heading4\"\n  | \"body\"\n  | \"caption1\"\n  | \"caption2\"\n  | \"button\",\n  TypographyVariant\n>;\n\n// Simple font mapping - response name to file names (without extension)\nconst FONT_MAP: Record<string, { regular: string; medium: string; bold: string }> = {\n  'times new roman': {\n    regular: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_regular',\n    medium: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_medium',\n    bold: Platform.OS === 'ios' ? 'TimesNewRomanPS-BoldMT' : 'times_new_roman_bold',\n  },\n  'inter': {\n    regular: Platform.OS === 'ios' ? 'Inter-Regular' : 'inter_regular',\n    medium: Platform.OS === 'ios' ? 'Inter-Medium' : 'inter_medium',\n    bold: Platform.OS === 'ios' ? 'Inter-Bold' : 'inter_bold',\n  },\n  'roboto': {\n    regular: Platform.OS === 'ios' ? 'Roboto-Regular' : 'roboto_regular',\n    medium: Platform.OS === 'ios' ? 'Roboto-Medium' : 'roboto_medium',\n    bold: Platform.OS === 'ios' ? 'Roboto-Bold' : 'roboto_bold',\n  },\n};\n\n/**\n * Creates a complete Typography object for CometChat theme.\n * Normalizes font name, provides fallback, and builds all variants.\n * \n * @param font - The font name from backend (e.g., 'inter', 'roboto', 'times new roman')\n * @returns Complete Typography object with all variants configured\n */\nexport const createTypography = (font: string): Typography => {\n  const types = [\n    \"title\",\n    \"heading1\",\n    \"heading2\",\n    \"heading3\",\n    \"heading4\",\n    \"body\",\n    \"caption1\",\n    \"caption2\",\n    \"button\",\n  ] as const;\n\n  // Normalize font name from backend and provide fallback\n  const fontKey = font ? font.toLowerCase().trim() : '';\n  const fontVariants = FONT_MAP[fontKey] || FONT_MAP['times new roman'];\n\n  // Defensive: ensure all variants exist\n  const baseStyle: TypographyVariant = {\n    regular: { fontFamily: fontVariants.regular },\n    medium: { fontFamily: fontVariants.medium },\n    bold: { fontFamily: fontVariants.bold },\n  };\n\n  const typography: Typography = {\n    fontFamily: fontVariants.regular,\n    link: { fontFamily: fontVariants.regular },\n  } as Typography;\n\n  types.forEach((type) => {\n    typography[type] = { ...baseStyle };\n  });\n\n  return typography;\n};"
  },
  {
    "path": "examples/SampleAppWithPushNotifications/tsconfig.json",
    "content": "{\n  \"extends\": \"@react-native/typescript-config\",\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"**/node_modules\", \"**/Pods\"]\n}\n"
  },
  {
    "path": "packages/ChatUiKit/.gitignore",
    "content": "# Logs\n*.log\nnpm-debug.log\n\n# Runtime data\ntmp\nbuild\nlib\n\n# Dependency directory\nnode_modules\n*.DS_Store\nexample/development2\nexample/development\n# Build file for local testing\ncometchat-pro-react-native-ui-kit-1.0.0.tgz\npackage-lock.json\nPods\n.idea\n.gradle\nexample3\n\n# Yarn\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n"
  },
  {
    "path": "packages/ChatUiKit/.prettierrc.json",
    "content": "{\n  \"arrowParens\": \"always\",\n  \"bracketSpacing\": true,\n  \"bracketSameLine\": false,\n  \"jsxSingleQuote\": true,\n  \"quoteProps\": \"as-needed\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"useTabs\": false,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/CONTRIBUTING.md",
    "content": "# Contributing\n\nWhen contributing to this repository, please first discuss the change you wish to make via issue,\nemail, or any other method with the owners of this repository before making a change.\n\nPlease note we have a code of conduct, please follow it in all your interactions with the project.\n\n## Pull Request Process\n\n1. Ensure any install or build dependencies are removed before the end of the layer when doing a\n   build.\n2. Update the README.md with details of changes to the interface, this includes new environment\n   variables, exposed ports, useful file locations and container parameters.\n3. Increase the version numbers in any examples files and the README.md to the new version that this\n   Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).\n4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you\n   do not have permission to do that, you may request the second reviewer to merge it for you.\n\n## Code of Conduct\n\n### Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n### Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n### Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n### Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n### Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at [INSERT EMAIL ADDRESS]. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n### Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "packages/ChatUiKit/LICENSE.md",
    "content": "Copyright (c) 2023 CometChat Inc.\n\nLicense agreement: https://www.cometchat.com/legal-terms-of-service"
  },
  {
    "path": "packages/ChatUiKit/README.md",
    "content": "<p align=\"center\">\n  <img alt=\"CometChat\" src=\"https://assets.cometchat.io/website/images/logos/banner.png\">\n</p>\n\n# CometChat UI Kit for React Native\n\nThe CometChat React Native UI Kit provides a pre-built user interface kit that developers can use to quickly integrate a reliable & fully-featured chat experience into an existing or a new app.\n\n## Prerequisites\n- Node.js 18 or higher\n- React Native 0.77\n\n## Getting Started\n To set up CometChat React Native UI Kit and utilize CometChat for your chat and calls functionality, you'll need to follow these steps:\n- Register at the [CometChat Dashboard](https://app.cometchat.com/) to create an account.\n- After registering, log into your CometChat account and create a new app. Once created, CometChat will generate an Auth Key and App ID for you. Keep these credentials secure as you'll need them later.\n- Check the [Key Concepts](https://www.cometchat.com/docs/react-native-uikit/key-concepts) to understand the basic components of CometChat.\n- Refer to the [Integration Steps](https://www.cometchat.com/docs/react-native-uikit/integration) in our documentation to integrate the UI Kit into your React Native app.\n\n## Help and Support\nFor issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/react-native-uikit/integration) or create a [support ticket](https://help.cometchat.com/hc/en-us) or seek real-time support via the [CometChat Dashboard](https://app.cometchat.com/)."
  },
  {
    "path": "packages/ChatUiKit/SECURITY.md",
    "content": "# Security Policy\n\nThis document outlines the Responsible Disclosure Program for CometChat Pro\n\n## Responsible Disclosure Policy\n\nAt CometChat we take security seriously and consider it a top priority. Since a\npublic disclosure of a security vulnerability could put the entire\ncommunity at risk, we require that potential vulnerabilities are kept\nconfidential until they are confirmed and fixed. We appreciate your efforts in\nkeeping CometChat Pro and its users safe by responsibly disclosing any security\nvulnerability. Rest assured we will make every effort to acknowledge your\ncontributions.\n\n## Reporting a vulnerability\n\nTo report any security related issues, please communicate with the CometChat Pro security team by sending an email to security@cometchat.com\n"
  },
  {
    "path": "packages/ChatUiKit/SUPPORT.md",
    "content": "# Getting Support\n\nThe quickest way to get support is to contact us via your CometChat Pro Dashboard.\n"
  },
  {
    "path": "packages/ChatUiKit/android/.settings/org.eclipse.buildship.core.prefs",
    "content": "connection.project.dir=../../../../android\neclipse.preferences.version=1\n"
  },
  {
    "path": "packages/ChatUiKit/android/build.gradle",
    "content": "buildscript {\n  repositories {\n      google()\n      mavenCentral()\n  }\n\n  dependencies {\n    classpath 'com.android.tools.build:gradle:4.2.0'\n    classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22\"\n  }\n}\n\ndef isNewArchitectureEnabled() {\n  return rootProject.hasProperty(\"newArchEnabled\") &&  rootProject.getProperty(\"newArchEnabled\") == \"true\"\n}\n\napply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\n\nif (isNewArchitectureEnabled()) {\n  apply plugin: 'com.facebook.react'\n}\n\ndef getExtOrDefault(name) {\n  return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['CometchatUiKit_' + name]\n}\n\ndef getExtOrIntegerDefault(name) {\n  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['CometchatUiKit_' + name]).toInteger()\n}\n\nandroid {\n  compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')\n\n  defaultConfig {\n    minSdkVersion getExtOrIntegerDefault('minSdkVersion')\n    targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')\n    buildConfigField \"boolean\", \"IS_NEW_ARCHITECTURE_ENABLED\", isNewArchitectureEnabled().toString()\n  }\n  buildTypes {\n    release {\n      minifyEnabled false\n    }\n  }\n\n  lintOptions {\n    disable 'GradleCompatible'\n  }\n\n  compileOptions {\n    sourceCompatibility JavaVersion.VERSION_1_8\n    targetCompatibility JavaVersion.VERSION_1_8\n  }\n\n           \n}\n\nrepositories {\n  mavenCentral()\n  google()\n\n  def found = false\n  def defaultDir = null\n  def androidSourcesName = 'React Native sources'\n\n  if (rootProject.ext.has('reactNativeAndroidRoot')) {\n    defaultDir = rootProject.ext.get('reactNativeAndroidRoot')\n  } else {\n    defaultDir = new File(\n      projectDir,\n      '/../../../node_modules/react-native/android'\n    )\n  }\n\n  if (defaultDir.exists()) {\n    maven {\n      url defaultDir.toString()\n      name androidSourcesName\n    }\n\n    logger.info(\":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}\")\n    found = true\n  } else {\n    def parentDir = rootProject.projectDir\n\n    1.upto(5, {\n      if (found) return true\n      parentDir = parentDir.parentFile\n\n      def androidSourcesDir = new File(\n        parentDir,\n        'node_modules/react-native'\n      )\n\n      def androidPrebuiltBinaryDir = new File(\n        parentDir,\n        'node_modules/react-native/android'\n      )\n\n      if (androidPrebuiltBinaryDir.exists()) {\n        maven {\n          url androidPrebuiltBinaryDir.toString()\n          name androidSourcesName\n        }\n\n        logger.info(\":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}\")\n        found = true\n      } else if (androidSourcesDir.exists()) {\n        maven {\n          url androidSourcesDir.toString()\n          name androidSourcesName\n        }\n\n        logger.info(\":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}\")\n        found = true\n      }\n    })\n  }\n\n  if (!found) {\n    throw new GradleException(\n      \"${project.name}: unable to locate React Native android sources. \" +\n      \"Ensure you have you installed React Native as a dependency in your project and try again.\"\n    )\n  }\n}\n\n\ndependencies {\n    //noinspection GradleDynamicVersion\n  implementation \"com.facebook.react:react-native:+\"\n    implementation 'androidx.appcompat:appcompat:1.4.1'\n    implementation 'com.google.android.material:material:1.5.0'\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib:1.9.22\"\n// From node_modules\n}\n\nif (isNewArchitectureEnabled()) {\n  react {\n    jsRootDir = file(\"../src/\")\n    libraryName = \"CometchatUiKit\"\n    codegenJavaPackageName = \"com.reactnativecometchatuikit\"\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.4-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "packages/ChatUiKit/android/gradle.properties",
    "content": "CometchatUiKit_kotlinVersion=1.7.0\nCometchatUiKit_minSdkVersion=21\nCometchatUiKit_targetSdkVersion=31\nCometchatUiKit_compileSdkVersion=31\nCometchatUiKit_ndkversion=21.4.7075529\n"
  },
  {
    "path": "packages/ChatUiKit/android/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "packages/ChatUiKit/android/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "packages/ChatUiKit/android/settings.gradle",
    "content": "rootProject.name = 'example'\napply from: file(\"../node_modules/@react-native-community/cli-platform-android/native_modules.gradle\"); applyNativeModulesSettingsGradle(settings)\ninclude ':app'\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.reactnativecometchatuikit\">\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <application android:requestLegacyExternalStorage=\"true\"\n        android:largeHeap=\"true\" >\n        <activity\n            android:name=\"com.reactnativecometchatuikit.CometChatVideoPlayer\"\n            android:theme=\"@style/Theme.AppCompat\" />\n        <activity\n            android:name=\"com.reactnativecometchatuikit.CometChatImageViewer\"\n            android:theme=\"@style/Theme.AppCompat\" />\n        <activity\n            android:name=\"com.reactnativecometchatuikit.CometChatWebView\"\n            android:theme=\"@style/Theme.AppCompat\" />\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/filepaths\" />\n        </provider>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/CometChatImageViewer.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.MediaController;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.net.URLConnection;\n\npublic class CometChatImageViewer extends AppCompatActivity {\n    ImageView imageView;\n    LinearLayout linearLayout;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        getSupportActionBar().hide();\n        Context context = getApplicationContext();\n        linearLayout = new LinearLayout(context);\n        linearLayout.setOrientation(LinearLayout.HORIZONTAL);\n        String imageUrl = getIntent().getStringExtra(\"imageUrl\");\n        this.getSupportActionBar().hide();\n\n        imageView = new ImageView(context);\n        imageView.setLayoutParams(\n                new LinearLayout.LayoutParams(\n                        ViewGroup.LayoutParams.MATCH_PARENT,\n                        ViewGroup.LayoutParams.MATCH_PARENT\n                )\n        );\n        linearLayout.addView(imageView);\n        setContentView(linearLayout);\n        new DownloadImage().execute(imageUrl);\n    }\n\n    class DownloadImage extends AsyncTask<String, String, Bitmap> {\n        @Override\n        protected Bitmap doInBackground(String... strings) {\n            return getImageBitmap(strings[0]);\n        }\n\n        @Override\n        protected void onPostExecute(Bitmap bitmap) {\n            super.onPostExecute(bitmap);\n            imageView.setImageBitmap(bitmap);\n        }\n    }\n\n    private Bitmap getImageBitmap(String url) {\n        Bitmap bm = null;\n        try {\n            URL aURL = new URL(url);\n            URLConnection conn = aURL.openConnection();\n            conn.connect();\n            InputStream is = conn.getInputStream();\n            BufferedInputStream bis = new BufferedInputStream(is);\n            bm = BitmapFactory.decodeStream(bis);\n            bis.close();\n            is.close();\n        } catch (IOException e) {\n            Log.e(\"ImageViewer\", \"Error getting bitmap\", e);\n        }\n        return bm;\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/CometChatSoundModule.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.media.AudioManager;\nimport android.media.MediaPlayer;\nimport android.media.MediaPlayer.OnCompletionListener;\nimport android.media.MediaPlayer.OnErrorListener;\nimport android.net.Uri;\nimport android.util.Log;\nimport com.facebook.react.bridge.Arguments;\nimport com.facebook.react.bridge.Callback;\nimport com.facebook.react.bridge.Promise;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.bridge.ReadableMap;\nimport com.facebook.react.bridge.WritableMap;\nimport com.facebook.react.modules.core.DeviceEventManagerModule;\nimport com.facebook.react.modules.core.ExceptionsManagerModule;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class CometChatSoundModule\n  extends ReactContextBaseJavaModule\n  implements AudioManager.OnAudioFocusChangeListener {\n\n  Map<Double, MediaPlayer> playerPool = new HashMap<>();\n  ReactApplicationContext context;\n  static final Object NULL = null;\n  String category;\n  Boolean mixWithOthers = true;\n  Double focusedPlayerKey;\n  Boolean wasPlayingBeforeFocusChange = false;\n\n  public CometChatSoundModule(ReactApplicationContext context) {\n    super(context);\n    this.context = context;\n    this.category = null;\n  }\n\n  private void setOnPlay(boolean isPlaying, final Double playerKey) {\n    final ReactContext reactContext = this.context;\n    WritableMap params = Arguments.createMap();\n    params.putBoolean(\"isPlaying\", isPlaying);\n    params.putDouble(\"playerKey\", playerKey);\n    reactContext\n      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)\n      .emit(\"onPlayChange\", params);\n  }\n\n  @Override\n  public String getName() {\n    return \"CometChatSoundModule\";\n  }\n\n  @ReactMethod\n  public void multiply(double a, double b, Promise promise) {\n    promise.resolve(a * b);\n  }\n\n  @ReactMethod\n  public void prepare(\n    final String fileName,\n    final Double key,\n    final ReadableMap options,\n    final Callback callback\n  ) {\n    MediaPlayer player = createMediaPlayer(fileName);\n    if (options.hasKey(\"speed\") && android.os.Build.VERSION.SDK_INT >= 23) {\n      player.setPlaybackParams(\n        player.getPlaybackParams().setSpeed((float) options.getDouble(\"speed\"))\n      );\n    }\n    if (player == null) {\n      WritableMap e = Arguments.createMap();\n      e.putInt(\"code\", -1);\n      e.putString(\"message\", \"resource not found\");\n      callback.invoke(e, NULL);\n      return;\n    }\n    this.playerPool.put(key, player);\n\n    final CometChatSoundModule module = this;\n\n    if (module.category != null) {\n      Integer category = null;\n      switch (module.category) {\n        case \"Playback\":\n          category = AudioManager.STREAM_MUSIC;\n          break;\n        case \"Ambient\":\n          category = AudioManager.STREAM_NOTIFICATION;\n          break;\n        case \"System\":\n          category = AudioManager.STREAM_SYSTEM;\n          break;\n        case \"Voice\":\n          category = AudioManager.STREAM_VOICE_CALL;\n          break;\n        case \"Ring\":\n          category = AudioManager.STREAM_RING;\n          break;\n        case \"Alarm\":\n          category = AudioManager.STREAM_ALARM;\n          break;\n        default:\n          Log.e(\n            \"CometChatSoundModule\",\n            String.format(\"Unrecognised category %s\", module.category)\n          );\n          break;\n      }\n      if (category != null) {\n        player.setAudioStreamType(category);\n      }\n    }\n\n    player.setOnPreparedListener(\n      new MediaPlayer.OnPreparedListener() {\n        boolean callbackWasCalled = false;\n\n        @Override\n        public synchronized void onPrepared(MediaPlayer mp) {\n          if (callbackWasCalled) return;\n          callbackWasCalled = true;\n\n          WritableMap props = Arguments.createMap();\n          props.putDouble(\"duration\", mp.getDuration() * .001);\n          try {\n            callback.invoke(NULL, props);\n          } catch (RuntimeException runtimeException) {\n            // The callback was already invoked\n            Log.e(\"CometChatSoundModule\", \"Exception\", runtimeException);\n          }\n        }\n      }\n    );\n\n    player.setOnErrorListener(\n      new OnErrorListener() {\n        boolean callbackWasCalled = false;\n\n        @Override\n        public synchronized boolean onError(\n          MediaPlayer mp,\n          int what,\n          int extra\n        ) {\n          if (callbackWasCalled) return true;\n          callbackWasCalled = true;\n          try {\n            WritableMap props = Arguments.createMap();\n            props.putInt(\"what\", what);\n            props.putInt(\"extra\", extra);\n            callback.invoke(props, NULL);\n          } catch (RuntimeException runtimeException) {\n            // The callback was already invoked\n            Log.e(\"CometChatSoundModule\", \"Exception\", runtimeException);\n          }\n          return true;\n        }\n      }\n    );\n\n    try {\n      if (options.hasKey(\"loadSync\") && options.getBoolean(\"loadSync\")) {\n        player.prepare();\n      } else {\n        player.prepareAsync();\n      }\n    } catch (Exception ignored) {\n      // When loading files from a file, we useMediaPlayer.create, which actually\n      // prepares the audio for us already. So we catch and ignore this error\n      Log.e(\"CometChatSoundModule\", \"Exception\", ignored);\n    }\n  }\n\n  protected MediaPlayer createMediaPlayer(final String fileName) {\n    int res =\n      this.context.getResources()\n        .getIdentifier(fileName, \"raw\", this.context.getPackageName());\n    MediaPlayer mediaPlayer = new MediaPlayer();\n    if (res != 0) {\n      try {\n        AssetFileDescriptor afd = context.getResources().openRawResourceFd(res);\n        mediaPlayer.setDataSource(\n          afd.getFileDescriptor(),\n          afd.getStartOffset(),\n          afd.getLength()\n        );\n        afd.close();\n      } catch (IOException e) {\n        Log.e(\"CometChatSoundModule\", \"Exception\", e);\n        return null;\n      }\n      return mediaPlayer;\n    }\n\n    if (fileName.startsWith(\"http://\") || fileName.startsWith(\"https://\")) {\n      mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n      Log.i(\"CometChatSoundModule\", fileName);\n      try {\n        mediaPlayer.setDataSource(fileName);\n      } catch (IOException e) {\n        Log.e(\"CometChatSoundModule\", \"Exception\", e);\n        return null;\n      }\n      return mediaPlayer;\n    }\n\n    if (fileName.startsWith(\"asset:/\")) {\n      try {\n        AssetFileDescriptor descriptor =\n          this.context.getAssets().openFd(fileName.replace(\"asset:/\", \"\"));\n        mediaPlayer.setDataSource(\n          descriptor.getFileDescriptor(),\n          descriptor.getStartOffset(),\n          descriptor.getLength()\n        );\n        descriptor.close();\n        return mediaPlayer;\n      } catch (IOException e) {\n        Log.e(\"CometChatSoundModule\", \"Exception\", e);\n        return null;\n      }\n    }\n\n    if (fileName.startsWith(\"file:/\")) {\n      try {\n        mediaPlayer.setDataSource(fileName);\n      } catch (IOException e) {\n        Log.e(\"CometChatSoundModule\", \"Exception\", e);\n        return null;\n      }\n      return mediaPlayer;\n    }\n\n    File file = new File(fileName);\n    if (file.exists()) {\n      mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n      Log.i(\"CometChatSoundModule\", fileName);\n      try {\n        mediaPlayer.setDataSource(fileName);\n      } catch (IOException e) {\n        Log.e(\"CometChatSoundModule\", \"Exception\", e);\n        return null;\n      }\n      return mediaPlayer;\n    }\n\n    return null;\n  }\n\n  @ReactMethod\n  public void play(final Double key, final Callback callback) {\n    final boolean[] callbackWasCalled = { false };\n    MediaPlayer player = this.playerPool.get(key);\n    if (player == null) {\n      setOnPlay(false, key);\n      if (callback != null) {\n        callback.invoke(false);\n        callbackWasCalled[0] = true;\n      }\n      return;\n    }\n\n    if (player.isPlaying()) {\n      return;\n    }\n\n    // Request audio focus in Android system\n    if (!this.mixWithOthers) {\n      AudioManager audioManager = (AudioManager) context.getSystemService(\n              Context.AUDIO_SERVICE\n      );\n      audioManager.requestAudioFocus(\n              this,\n              AudioManager.STREAM_MUSIC,\n              AudioManager.AUDIOFOCUS_GAIN\n      );\n      this.focusedPlayerKey = key;\n    }\n\n    player.setOnCompletionListener(mp -> {\n      if (!mp.isLooping()) {\n        setOnPlay(false, key);\n        if (callbackWasCalled[0]) return;\n        callbackWasCalled[0] = true;\n        try {\n          callback.invoke(true);\n        } catch (Exception e) {\n          // Illegal callback invocation from native module\n        }\n      }\n    });\n\n    player.setOnErrorListener((mp, what, extra) -> {\n      setOnPlay(false, key);\n      if (callbackWasCalled[0]) return true;\n      callbackWasCalled[0] = true;\n      try {\n        callback.invoke(true);\n      } catch (Exception e) {\n        // Illegal callback invocation from native module\n      }\n      return true;\n    });\n\n    player.start();\n    setOnPlay(true, key);\n  }\n\n\n  @ReactMethod\n  public void checkMode(Promise promise){\n    Log.e(\"CometChatSoundModule\",\" entry POINT\");\n    AudioManager audioManager = (AudioManager) context.getSystemService(\n      Context.AUDIO_SERVICE\n      );\n  Log.e(\"CometChatSoundModule\",audioManager.toString());\n    String mode= \"\";\n\n    switch( audioManager.getRingerMode() ){\n        case AudioManager.RINGER_MODE_NORMAL:\n          mode = \"NORMAL\";\n          break;\n        case AudioManager.RINGER_MODE_SILENT:\n          mode = \"SILENT\";\n          break;\n        case AudioManager.RINGER_MODE_VIBRATE:\n          mode = \"VIBRATE\";\n          break;\n    }\n      Log.e(\"CometChatSoundModule\",mode);\n      promise.resolve(mode);\n   \n  }\n\n  @ReactMethod\n  public void pause(final Double key, final Callback callback) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null && player.isPlaying()) {\n      player.pause();\n    }\n\n    if (callback != null) {\n      callback.invoke();\n    }\n  }\n\n  @ReactMethod\n  public void stop(final Double key, final Callback callback) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null && player.isPlaying()) {\n      player.pause();\n      player.seekTo(0);\n    }\n\n    // Release audio focus in Android system\n    if (!this.mixWithOthers && key == this.focusedPlayerKey) {\n      AudioManager audioManager = (AudioManager) context.getSystemService(\n        Context.AUDIO_SERVICE\n      );\n      audioManager.abandonAudioFocus(this);\n    }\n\n    callback.invoke();\n  }\n\n  @ReactMethod\n  public void reset(final Double key) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.reset();\n    }\n  }\n\n  @ReactMethod\n  public void release(final Double key) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.reset();\n      player.release();\n      this.playerPool.remove(key);\n\n      // Release audio focus in Android system\n      if (!this.mixWithOthers && key == this.focusedPlayerKey) {\n        AudioManager audioManager = (AudioManager) context.getSystemService(\n          Context.AUDIO_SERVICE\n        );\n        audioManager.abandonAudioFocus(this);\n      }\n    }\n  }\n\n  @Override\n  public void onCatalystInstanceDestroy() {\n    java.util.Iterator it = this.playerPool.entrySet().iterator();\n    while (it.hasNext()) {\n      Map.Entry entry = (Map.Entry) it.next();\n      MediaPlayer player = (MediaPlayer) entry.getValue();\n      if (player != null) {\n        player.reset();\n        player.release();\n      }\n      it.remove();\n    }\n  }\n\n  @ReactMethod\n  public void setVolume(final Double key, final Float left, final Float right) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.setVolume(left, right);\n    }\n  }\n\n  @ReactMethod\n  public void checkOtherAudioPlaying(Promise isPlaying) {\n    try {\n      AudioManager audioManager = (AudioManager) context.getSystemService(\n        Context.AUDIO_SERVICE\n      );\n      if (audioManager.isMusicActive()) {\n        isPlaying.resolve(true);\n       \n      }\n    } catch (RuntimeException runtimeException) {\n      // The callback was already invoked\n      Log.e(\"CometChatSoundModule\", \"Exception\", runtimeException);\n    }\n    Log.e(\"CometChatSoundModule\", \"returns false\");\n    isPlaying.resolve(false);\n    \n  }\n  \n  @ReactMethod\n  public void getSystemVolume(final Callback callback) {\n    try {\n      AudioManager audioManager = (AudioManager) context.getSystemService(\n        Context.AUDIO_SERVICE\n      );\n\n      callback.invoke(\n        (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) /\n        audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)\n      );\n    } catch (Exception error) {\n      WritableMap e = Arguments.createMap();\n      e.putInt(\"code\", -1);\n      e.putString(\"message\", error.getMessage());\n      callback.invoke(e);\n    }\n  }\n\n  @ReactMethod\n  public void setSystemVolume(final Float value) {\n    AudioManager audioManager = (AudioManager) context.getSystemService(\n      Context.AUDIO_SERVICE\n    );\n\n    int volume = Math.round(\n      audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * value\n    );\n    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);\n  }\n\n  @ReactMethod\n  public void setLooping(final Double key, final Boolean looping) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.setLooping(looping);\n    }\n  }\n\n  @ReactMethod\n  public void setSpeed(final Double key, final Float speed) {\n    if (android.os.Build.VERSION.SDK_INT < 23) {\n      Log.w(\"CometChatSoundModule\", \"setSpeed ignored due to sdk limit\");\n      return;\n    }\n\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.setPlaybackParams(player.getPlaybackParams().setSpeed(speed));\n    }\n  }\n\n  @ReactMethod\n  public void setPitch(final Double key, final Float pitch) {\n    if (android.os.Build.VERSION.SDK_INT < 23) {\n      Log.w(\"CometChatSoundModule\", \"setPitch ignored due to sdk limit\");\n      return;\n    }\n\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.setPlaybackParams(player.getPlaybackParams().setPitch(pitch));\n    }\n  }\n\n  @ReactMethod\n  public void setCurrentTime(final Double key, final Float sec) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      player.seekTo((int) Math.round(sec * 1000));\n    }\n  }\n\n  @ReactMethod\n  public void getCurrentTime(final Double key, final Callback callback) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player == null) {\n      callback.invoke(-1, false);\n      return;\n    }\n    callback.invoke(player.getCurrentPosition() * .001, player.isPlaying());\n  }\n\n  //turn speaker on\n  @ReactMethod\n  public void setSpeakerphoneOn(final Double key, final Boolean speaker) {\n    MediaPlayer player = this.playerPool.get(key);\n    if (player != null) {\n      AudioManager audioManager = (AudioManager) this.context.getSystemService(\n          this.context.AUDIO_SERVICE\n        );\n      if (speaker) {\n        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);\n      } else {\n        audioManager.setMode(AudioManager.MODE_NORMAL);\n      }\n      audioManager.setSpeakerphoneOn(speaker);\n    }\n  }\n\n  @ReactMethod\n  public void setCategory(final String category, final Boolean mixWithOthers) {\n    this.category = category;\n    this.mixWithOthers = mixWithOthers;\n  }\n\n  @Override\n  public void onAudioFocusChange(int focusChange) {\n    if (!this.mixWithOthers) {\n      MediaPlayer player = this.playerPool.get(this.focusedPlayerKey);\n\n      if (player != null) {\n        if (focusChange <= 0) {\n          this.wasPlayingBeforeFocusChange = player.isPlaying();\n\n          if (this.wasPlayingBeforeFocusChange) {\n            this.pause(this.focusedPlayerKey, null);\n          }\n        } else {\n          if (this.wasPlayingBeforeFocusChange) {\n            this.play(this.focusedPlayerKey, null);\n            this.wasPlayingBeforeFocusChange = false;\n          }\n        }\n      }\n    }\n  }\n\n  @ReactMethod\n  public void enable(final Boolean enabled) {\n    // no op\n  }\n\n  @Override\n  public Map<String, Object> getConstants() {\n    final Map<String, Object> constants = new HashMap<>();\n    constants.put(\"IsAndroid\", true);\n    return constants;\n  }\n\n  @ReactMethod\n  public void addListener(String eventName) {\n    // Keep: Required for RN built in Event Emitter Calls.\n  }\n\n  @ReactMethod\n  public void removeListeners(Integer count) {\n    // Keep: Required for RN built in Event Emitter Calls.\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/CometChatVideoPlayer.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.MediaController;\nimport android.widget.RelativeLayout;\nimport android.widget.VideoView;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\npublic class CometChatVideoPlayer extends AppCompatActivity {\n    VideoView videoView;\n    RelativeLayout relativeLayout;\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        getSupportActionBar().hide();\n        Context context = getApplicationContext();\n        relativeLayout = new RelativeLayout(context);\n        String videoUrl = getIntent().getStringExtra(\"videoUrl\");\n        this.getSupportActionBar().hide();\n\n        videoView = new VideoView(context);\n        videoView.setLayoutParams(\n                new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n        );\n\n        MediaController mediacontroller = new MediaController(this,true);\n        mediacontroller.setAnchorView(videoView);\n        videoView.setMediaController(mediacontroller);\n        videoView.setVideoURI(Uri.parse(videoUrl));\n        videoView.setOnPreparedListener(mediaPlayer -> mediaPlayer.start());\n        relativeLayout.addView(videoView);\n        setContentView(relativeLayout);\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/CometChatWebView.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.ViewGroup;\nimport android.webkit.WebSettings;\nimport android.webkit.WebView;\nimport android.widget.RelativeLayout;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\npublic class CometChatWebView extends AppCompatActivity {\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        getSupportActionBar().hide();\n\n        // On API 30+ the default changed to edge-to-edge; restore classic behavior\n        // so the layout respects system bar insets (status bar, navigation bar).\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            getWindow().setDecorFitsSystemWindows(true);\n        }\n\n        String urlToLoad = getIntent().getStringExtra(\"load_url\");\n        RelativeLayout layout = new RelativeLayout(this);\n        layout.setFitsSystemWindows(true);\n        layout.setLayoutParams(new RelativeLayout.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.MATCH_PARENT\n        ));\n        WebView webView = new WebView(this);\n        webView.setLayoutParams(\n                new RelativeLayout.LayoutParams(\n                        ViewGroup.LayoutParams.MATCH_PARENT,\n                        ViewGroup.LayoutParams.MATCH_PARENT\n                ));\n        WebSettings webSettings = webView.getSettings();\n        webSettings.setJavaScriptEnabled(true);\n        webSettings.setSupportZoom(true);\n        webSettings.setBuiltInZoomControls(true);\n        webView.loadUrl(urlToLoad);\n        layout.addView(webView);\n        setContentView(layout);\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/CometchatUiKitModule.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.bridge.Promise;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.module.annotations.ReactModule;\n\n@ReactModule(name = CometchatUiKitModule.NAME)\npublic class CometchatUiKitModule extends ReactContextBaseJavaModule {\n    public static final String NAME = \"CometchatUiKit\";\n\n    public CometchatUiKitModule(ReactApplicationContext reactContext) {\n        super(reactContext);\n    }\n\n    @Override\n    @NonNull\n    public String getName() {\n        return NAME;\n    }\n\n\n    // Example method\n    // See https://reactnative.dev/docs/native-modules-android\n   /* @ReactMethod\n    public void multiply(double a, double b, Promise promise) {\n        promise.resolve(a * b);\n    }\n*/\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/CometchatUiKitPackage.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.ReactPackage;\nimport com.facebook.react.bridge.NativeModule;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.uimanager.ViewManager;\nimport com.reactnativecometchatuikit.ProximityModule;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class CometchatUiKitPackage implements ReactPackage {\n    @NonNull\n    @Override\n    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {\n        List<NativeModule> modules = new ArrayList<>();\n        modules.add(new CometchatUiKitModule(reactContext));\n        modules.add(new CometChatSoundModule(reactContext));\n        modules.add(new SoundPlayer(reactContext));\n        modules.add(new FileManager(reactContext));\n        modules.add(new ImageManager(reactContext));\n        modules.add(new WebViewManager(reactContext));\n        modules.add(new TimeZoneCodeManager(reactContext));\n        modules.add(new ProximityModule(reactContext));\n        return modules;\n    }\n\n    @NonNull\n    @Override\n    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {\n        return Arrays.<ViewManager>asList(\n            new ReactVideoViewManager(),\n            new RichTextEditorViewManager()\n        );\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/FileManager.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport static android.os.Environment.DIRECTORY_DOCUMENTS;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.app.DownloadManager;\nimport android.content.ActivityNotFoundException;\nimport android.content.BroadcastReceiver;\nimport android.content.ClipData;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Matrix;\nimport android.media.ExifInterface;\nimport android.media.MediaPlayer;\nimport android.media.MediaRecorder;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.provider.DocumentsContract;\nimport android.provider.MediaStore;\nimport android.provider.OpenableColumns;\nimport android.util.Log;\nimport android.webkit.MimeTypeMap;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport androidx.core.content.FileProvider;\n\nimport com.facebook.react.bridge.ActivityEventListener;\nimport com.facebook.react.bridge.Arguments;\nimport com.facebook.react.bridge.BaseActivityEventListener;\nimport com.facebook.react.bridge.Callback;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.bridge.ReadableMap;\nimport com.facebook.react.bridge.WritableMap;\nimport com.facebook.react.bridge.WritableNativeMap;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.FileInputStream;\nimport java.io.RandomAccessFile;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport org.json.JSONObject;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport com.facebook.react.modules.core.DeviceEventManagerModule;\nimport com.facebook.react.bridge.Promise;\nimport com.facebook.react.bridge.ReadableArray;\n\npublic class FileManager extends ReactContextBaseJavaModule {\n    public static final String NAME = \"FileManager\";\n    private static String folderPath = \"\";\n    public static final String TAG = \"native_code\";\n    private Context mContext;\n    private ReactApplicationContext mReactContext;\n    private static final int READ_REQUEST_CODE = 100;\n    private static final int CAPTURE_STILL_IMAGE = 101;\n    private static final int REQUEST_LAUNCH_CAMERA = 1;\n    private static int IMAGE_QUALITY = 20;\n\n    private Callback callback;\n\n    private static final String FIELD_URI = \"uri\";\n    private static final String FIELD_FILE_COPY_URI = \"fileCopyUri\";\n    private static final String FIELD_COPY_ERROR = \"copyError\";\n    private static final String FIELD_NAME = \"name\";\n    private static final String FIELD_TYPE = \"type\";\n    private static final String FIELD_SIZE = \"size\";\n    private File cameraFile;\n    private JSONObject obj = new JSONObject();\n    private static String currentUrl = \"\";\n    DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = null;\n\n    // Update the BroadcastReceiver to send the download completion event\n    private final BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);\n\n            // Create a WritableMap to pass additional information if necessary\n            WritableMap params = Arguments.createMap();\n            params.putString(\"message\", \"Download complete\");\n            params.putString(\"downloadId\", String.valueOf(id));  // Send file name as part of event\n\n            // Send the event to React Native\n            eventEmitter.emit(\"downloadComplete\", params);\n\n            // Optionally, unregister the receiver\n            context.unregisterReceiver(this);\n        }\n    };\n\n    FileManager(ReactApplicationContext context) {\n        super(context);\n        mContext = context.getApplicationContext();\n        mReactContext = context;\n        folderPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            context.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED);\n        } else {\n            context.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));\n        }\n        context.addActivityEventListener(activityEventListener);\n    }\n\n    private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {\n        @Override\n        public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {\n            if (callback == null) {\n                Log.e(NAME, \"callback was null in onActivityResult\");\n                return;\n            }\n            if (requestCode == READ_REQUEST_CODE) {\n                onShowActivityResult(resultCode, data, callback);\n            }\n            if (requestCode == CAPTURE_STILL_IMAGE) {\n                saveImageFromCameraAndReturn(resultCode, data, callback);\n            }\n            if (requestCode == REQUEST_LAUNCH_CAMERA) {\n                if (resultCode == Activity.RESULT_OK) {\n                    if (cameraFile != null && cameraFile.exists()) {\n                        // Compress the image\n                        try {\n                            Bitmap bitmap = BitmapFactory.decodeFile(cameraFile.getAbsolutePath());\n                            ExifInterface exif = new ExifInterface(cameraFile.getAbsolutePath());\n                            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);\n                            Log.i(TAG, \"onActivityResult: orientation\" + orientation);\n                            int rotationAngle = 0;\n                            if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {\n                                rotationAngle = 90;\n                            }\n                            else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {\n                                rotationAngle = 180;\n                            }\n                            else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {\n                                rotationAngle = 270;\n                            }\n\n                            // Create a new matrix for rotation\n                            Matrix matrix = new Matrix();\n                            if (rotationAngle != 0) {\n                                matrix.setRotate(rotationAngle);\n                                // Rotate the bitmap\n                                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);\n                            }\n                            FileOutputStream out = new FileOutputStream(cameraFile);\n\n                            // The quality parameter can be adjusted to decrease image quality (100 means no compression)\n                            bitmap.compress(Bitmap.CompressFormat.JPEG, IMAGE_QUALITY, out); // Compress to 50% quality\n                            out.flush();\n                            out.close();\n                            bitmap.recycle();\n                        } catch (Exception e) {\n                            WritableMap errorMap = new WritableNativeMap();\n                            errorMap.putString(\"error\", \"Image compression error: \" + e.getMessage());\n                            callback.invoke(errorMap);\n                            return;\n                        }\n\n                        // Prepare the image details to return to React Native\n                        WritableMap imageMap = new WritableNativeMap();\n                        Uri fileUri = Uri.fromFile(cameraFile);\n                        imageMap.putString(\"uri\", fileUri.toString());\n                        imageMap.putString(\"path\", cameraFile.getAbsolutePath());\n                        imageMap.putDouble(\"size\", cameraFile.length());\n                        imageMap.putString(\"name\", cameraFile.getName());\n                        String fileExtension = MimeTypeMap.getFileExtensionFromUrl(fileUri.toString());\n                        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase());\n                        imageMap.putString(\"type\", mimeType != null ? mimeType : \"image/jpeg\");\n\n                        callback.invoke(imageMap);\n                    } else {\n                        WritableMap errorMap = new WritableNativeMap();\n                        errorMap.putString(\"error\", \"Image file does not exist.\");\n                        callback.invoke(errorMap);\n                    }\n                } else {\n                    WritableMap errorMap = new WritableNativeMap();\n                    errorMap.putString(\"error\", \"Camera activity did not return RESULT_OK.\");\n                    callback.invoke(errorMap);\n                }\n            }\n        }\n\n        private void saveImageFromCameraAndReturn(int resultCode, Intent data, Callback callback) {\n            try {\n                if (resultCode == Activity.RESULT_OK) {\n                    Bitmap photo = (Bitmap) data.getExtras().get(\"data\");\n                    File cacheDir = mContext.getExternalCacheDir();\n                    File imageFile = new File(cacheDir, UUID.randomUUID().toString()+\".jpeg\");\n                    OutputStream outputStream = new FileOutputStream(imageFile);\n                    photo.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);\n                    ExecutorService service = Executors.newSingleThreadExecutor();\n                    Handler handler = new Handler(Looper.getMainLooper());\n                    service.execute(new Runnable() {\n                        @Override\n                        public void run() {\n                            WritableMap map = getMetadataForCameraImage(Uri.fromFile(imageFile));\n                            callback.invoke(map);\n                        }\n                    });\n                }\n            } catch (Exception ex) {\n                callback.invoke(\"\");\n            }\n        }\n        private WritableMap getMetadataForCameraImage(Uri uri) {\n            Context context = mContext;\n            if (context == null) {\n                return Arguments.createMap();\n            }\n            ContentResolver contentResolver = context.getContentResolver();\n            WritableMap map = Arguments.createMap();\n            map.putString(FIELD_URI, uri.toString());\n            map.putString(FIELD_TYPE, \"image/*\");\n            String timeStamp = new SimpleDateFormat(\"yyyyMMdd_HHmmss\").format(new Date());\n            String imageFileName = \"JPEG_\" + timeStamp + \"_\";\n            map.putString(FIELD_NAME, imageFileName+\".jpeg\");\n            prepareFileUri(context, map, uri);\n            return map;\n        }\n\n        private WritableMap getMetadata(Uri uri) {\n            Context context = mContext;\n            if (context == null) {\n                return Arguments.createMap();\n            }\n            ContentResolver contentResolver = context.getContentResolver();\n            WritableMap map = Arguments.createMap();\n            map.putString(FIELD_URI, uri.toString());\n            map.putString(FIELD_TYPE, contentResolver.getType(uri));\n            try (Cursor cursor = contentResolver.query(uri, null, null, null, null, null)) {\n                if (cursor != null && cursor.moveToFirst()) {\n                    int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);\n                    if (!cursor.isNull(displayNameIndex)) {\n                        String fileName = cursor.getString(displayNameIndex);\n                        map.putString(FIELD_NAME, fileName);\n                    } else {\n                        map.putNull(FIELD_NAME);\n                    }\n                    int mimeIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE);\n                    if (!cursor.isNull(mimeIndex)) {\n                        map.putString(FIELD_TYPE, cursor.getString(mimeIndex));\n                    }\n                    int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);\n                    if (cursor.isNull(sizeIndex)) {\n                        map.putNull(FIELD_SIZE);\n                    } else {\n                        map.putDouble(FIELD_SIZE, cursor.getLong(sizeIndex));\n                    }\n                }\n            }\n\n            prepareFileUri(context, map, uri);\n            return map;\n        }\n\n        private void prepareFileUri(Context context, WritableMap map, Uri uri) {\n            copyFileToLocalStorage(context, map, uri);\n        }\n\n        private void copyFileToLocalStorage(Context context, WritableMap map, Uri uri) {\n            File dir = context.getExternalCacheDir();\n            // we don't want to rename the file so we put it into a unique location\n            dir = new File(dir, UUID.randomUUID().toString());\n            try {\n                boolean didCreateDir = dir.mkdir();\n                if (!didCreateDir) {\n                    throw new IOException(\"failed to create directory at \" + dir.getAbsolutePath());\n                }\n                String fileName = map.getString(FIELD_NAME);\n                if (fileName == null) {\n                    fileName = String.valueOf(System.currentTimeMillis());\n                }\n                File destFile = new File(dir, fileName);\n                Uri copyPath = copyFile(context, uri, destFile);\n                map.putString(FIELD_FILE_COPY_URI, copyPath.toString());\n            } catch (Exception e) {\n                e.printStackTrace();\n                map.putNull(FIELD_FILE_COPY_URI);\n                map.putString(FIELD_COPY_ERROR, e.getLocalizedMessage());\n            }\n        }\n\n        public Uri copyFile(Context context, Uri uri, File destFile) throws Exception {\n            InputStream inputStream = context.getContentResolver().openInputStream(uri);\n            FileOutputStream outputStream = new FileOutputStream(destFile);\n            byte[] buf = new byte[8192];\n            int len;\n            while ((len = inputStream.read(buf)) > 0) {\n                outputStream.write(buf, 0, len);\n            }\n            return Uri.fromFile(destFile);\n        }\n\n        private void onShowActivityResult(int resultCode, Intent data, Callback storedPromise) {\n            if (resultCode == Activity.RESULT_OK) {\n                Uri uri = null;\n                ClipData clipData = null;\n\n                if (data != null) {\n                    uri = data.getData();\n                    clipData = data.getClipData();\n                }\n\n                try {\n                    List<Uri> uris = new ArrayList<>();\n\n                    if (clipData != null && clipData.getItemCount() > 0) {\n                        final int length = clipData.getItemCount();\n                        for (int i = 0; i < length; ++i) {\n                            ClipData.Item item = clipData.getItemAt(i);\n                            uris.add(item.getUri());\n                        }\n                    } else if (uri != null) {\n                        uris.add(uri);\n                    } else {\n                        Log.e(TAG, \"onShowActivityResult: invalid data returned\" );\n                        return;\n                    }\n                    ExecutorService service = Executors.newSingleThreadExecutor();\n                    Handler handler = new Handler(Looper.getMainLooper());\n                    service.execute(new Runnable() {\n                        @Override\n                        public void run() {\n                            WritableMap map = getMetadata(uris.get(0));\n                            callback.invoke(map);\n                        }\n                    });\n                } catch (Exception e) {\n                }\n            }\n        }\n    };\n\n    BroadcastReceiver onComplete=new BroadcastReceiver() {\n        public void onReceive(Context context, Intent intent) {\n            Toast.makeText(context, \"Download complete\", Toast.LENGTH_SHORT).show();\n        }\n    };\n\n    private String getType(String type) {\n        switch (type) {\n            case \"image\":\n                return \"image/*\";\n            case \"video\":\n                return  \"video/*\";\n            case \"file\":\n                return  \"*/*\";\n            case \"audio\":\n                return \"audio/*\";\n            default:\n                return \"\";\n        }\n    }\n\n    @ReactMethod\n    public void shareMessage(ReadableMap readableMap, Callback callback){\n\n        String messageType = readableMap.getString(\"type\");\n        String message = readableMap.getString(\"message\");\n        String mediaName = readableMap.getString(\"mediaName\");\n        String fileUrl = readableMap.getString(\"fileUrl\");\n        String mimeType = readableMap.getString(\"mimeType\");\n\n        if (getCurrentActivity() != null) {\n            if (messageType.equals(\"text\")) {\n                Intent shareIntent = new Intent();\n                shareIntent.setAction(Intent.ACTION_SEND);\n                shareIntent.putExtra(Intent.EXTRA_TITLE, \"\");\n                shareIntent.putExtra(Intent.EXTRA_TEXT, message);\n                shareIntent.setType(\"text/plain\");\n                Intent intent = Intent.createChooser(shareIntent, \"Share Message\");\n                getCurrentActivity().startActivity(intent);\n\n                callback.invoke(\"{\\\"success\\\": true}\");\n            } else {\n                downloadFileInNewThread(getCurrentActivity(), fileUrl, mediaName, mimeType);\n                callback.invoke(\"{\\\"success\\\": true}\");\n            }\n        } else {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No current activity\\\"}\");\n        }\n    }\n\n    public static void downloadFileInNewThread(Context context, String fileUrl, String fileName, String mimeType) {\n        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);\n        Thread downloadThread = new Thread(() -> {\n            try {\n                if (!file.exists()) {\n                    URL url = new URL(fileUrl);\n                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n                    connection.connect();\n\n                    if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {\n                        handleDownloadFailure(context);\n                        return;\n                    }\n\n                    InputStream input = connection.getInputStream();\n\n                    FileOutputStream output = new FileOutputStream(file);\n\n                    byte[] buffer = new byte[4096];\n                    int bytesRead;\n\n                    while ((bytesRead = input.read(buffer)) != -1) {\n                        output.write(buffer, 0, bytesRead);\n                    }\n\n                    output.flush();\n                    output.close();\n                    input.close();\n\n                    handleDownloadSuccess(context, mimeType, file);\n                } else {\n                    handleDownloadSuccess(context, mimeType, file);\n                }\n            } catch (Exception e) {\n                Log.e(\"DownloadThread\", \"Error downloading file: \" + e.getMessage());\n                handleDownloadFailure(context);\n            }\n        });\n        downloadThread.start();\n    }\n\n    private static void handleDownloadSuccess(Context context, String mimeType, final File file) {\n        ((Activity) context).runOnUiThread(() -> {\n            shareFile(mimeType, context, file);\n        });\n    }\n\n    private static void handleDownloadFailure(Context context) {\n        ((Activity) context).runOnUiThread(() -> {\n            // File download failed\n            // Handle the failure scenario\n            Toast.makeText(context, \"File download failed.\", Toast.LENGTH_SHORT).show();\n        });\n    }\n\n    private static void shareFile(String mimeType, Context context, File file) {\n        try {\n            Intent shareIntent = new Intent();\n            shareIntent.setAction(Intent.ACTION_SEND);\n            shareIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + \".fileprovider\", file));\n            shareIntent.setType(mimeType);\n            Intent intent = Intent.createChooser(shareIntent, \"Share Message\");\n            context.startActivity(intent);\n        } catch (Exception e) {\n            Toast.makeText(context, \"Error\" + \":\" + e.getMessage(), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    private MediaRecorder audioRecorder;\n    private MediaPlayer audioPlayer;\n    private String fileName;\n    private Context context;\n    private Activity activity;\n    private Handler amplitudeHandler;\n    private Runnable amplitudeRunnable;\n    private boolean isAmplitudePolling = false;\n    \n    // Segment-based recording fields\n    private List<String> segmentPaths = new ArrayList<>();\n    private int currentSegmentIndex = 0;\n\n    private void startAmplitudePolling() {\n        if (amplitudeHandler == null) {\n            amplitudeHandler = new Handler(Looper.getMainLooper());\n        }\n        isAmplitudePolling = true;\n        amplitudeRunnable = new Runnable() {\n            @Override\n            public void run() {\n                if (audioRecorder != null && isAmplitudePolling) {\n                    try {\n                        int maxAmplitude = audioRecorder.getMaxAmplitude();\n                        // Normalize to 0.0-1.0 range (max amplitude is ~32767)\n                        double normalizedAmplitude = Math.min(1.0, maxAmplitude / 32767.0);\n                        \n                        // Send event to React Native\n                        if (eventEmitter == null) {\n                            eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n                        }\n                        WritableMap params = Arguments.createMap();\n                        params.putDouble(\"amplitude\", normalizedAmplitude);\n                        eventEmitter.emit(\"audioAmplitude\", params);\n                    } catch (Exception e) {\n                        Log.e(TAG, \"Error getting amplitude: \" + e.getMessage());\n                    }\n                    amplitudeHandler.postDelayed(this, 100); // Poll every 100ms\n                }\n            }\n        };\n        amplitudeHandler.post(amplitudeRunnable);\n    }\n\n    private void stopAmplitudePolling() {\n        isAmplitudePolling = false;\n        if (amplitudeHandler != null && amplitudeRunnable != null) {\n            amplitudeHandler.removeCallbacks(amplitudeRunnable);\n        }\n    }\n\n    @ReactMethod\n    public void startRecording(final Callback callback) {\n    Activity activity = getCurrentActivity();\n    if (activity == null) {\n        callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"E_NO_ACTIVITY\\\"}\");\n        return;\n    }\n\n    // You already check permission in JS for Android. Still be safe:\n    if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO)\n        != PackageManager.PERMISSION_GRANTED) {\n        callback.invoke(\"{\\\"granted\\\": false}\");\n        return;\n    }\n\n    try {\n        // App-scoped external dir (no WRITE permission needed)\n        File dir = activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC);\n        if (dir == null) {\n        callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"E_NO_DIR\\\"}\");\n        return;\n        }\n        String ts = new SimpleDateFormat(\"yyyyMMddHHmmss\").format(new Date());\n        fileName = new File(dir, \"audio-recording-\" + ts + \".m4a\").getAbsolutePath();\n\n        audioRecorder = new MediaRecorder();\n        audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);\n        audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);\n        audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);\n        audioRecorder.setAudioEncodingBitRate(128000);\n        audioRecorder.setAudioSamplingRate(44100);\n        audioRecorder.setOutputFile(fileName);\n\n        audioRecorder.prepare();\n        audioRecorder.start();\n        \n        // Start amplitude polling for real-time waveform\n        startAmplitudePolling();\n\n        // Your JS just needs a callback to move to \"recording\"\n        callback.invoke(\"{\\\"success\\\": true}\");\n    } catch (Exception e) {\n        try { if (audioRecorder != null) audioRecorder.release(); } catch (Exception ignore) {}\n        audioRecorder = null;\n        stopAmplitudePolling();\n        callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n    }\n    }\n\n    private void requestPermissions() {\n        if (ContextCompat.checkSelfPermission(getCurrentActivity(), android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {\n            Log.e(\"AudioRecorder\", \"in requestPermissions permission not granted for audio recording so request permission\");\n            ActivityCompat.requestPermissions(activity, new String[]{android.Manifest.permission.RECORD_AUDIO}, 101);\n        }\n    }\n\n    private boolean checkPermissions() {\n        int audioRecordAccess = ContextCompat.checkSelfPermission(getCurrentActivity(), android.Manifest.permission.RECORD_AUDIO);\n        return audioRecordAccess == PackageManager.PERMISSION_GRANTED;\n    }\n\n    @ReactMethod\n    public void playAudio(Callback callback) {\n        // Check if fileName is valid\n        if (fileName == null || fileName.isEmpty()) {\n            Log.e(\"TAG\", \"playAudio: fileName is null or empty\");\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio file to play\\\"}\");\n            return;\n        }\n        \n        // Stop any existing playback\n        if (audioPlayer != null) {\n            try {\n                if (audioPlayer.isPlaying()) audioPlayer.stop();\n                audioPlayer.release();\n            } catch (Exception ignore) {}\n            audioPlayer = null;\n        }\n        \n        audioPlayer = new MediaPlayer();\n        try {\n            String playPath = fileName;\n            \n            // Handle content:// URIs\n            if (playPath.startsWith(\"content://\")) {\n                Activity activity = getCurrentActivity();\n                if (activity != null) {\n                    Log.e(\"TAG\", \"Playing Audio with content URI: \" + playPath);\n                    audioPlayer.setDataSource(activity, Uri.parse(playPath));\n                } else {\n                    // Fallback: try to extract file path from content URI\n                    String[] parts = playPath.split(\"/external_files\");\n                    if (parts.length > 1) {\n                        playPath = \"/storage/emulated/0\" + parts[1];\n                        Log.e(\"TAG\", \"Converted to file path: \" + playPath);\n                        audioPlayer.setDataSource(playPath);\n                    } else {\n                        throw new IOException(\"Cannot resolve content URI without activity\");\n                    }\n                }\n            } else {\n                // Handle file:// URIs\n                if (playPath.startsWith(\"file://\")) {\n                    playPath = playPath.substring(7);\n                }\n                Log.e(\"TAG\", \"Playing Audio: \" + playPath);\n                audioPlayer.setDataSource(playPath);\n            }\n            \n            // Set up completion listener to emit event when playback finishes\n            audioPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {\n                @Override\n                public void onCompletion(MediaPlayer mp) {\n                    // Emit playback complete event to JS\n                    if (eventEmitter == null) {\n                        eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n                    }\n                    WritableMap params = Arguments.createMap();\n                    params.putString(\"state\", \"playbackComplete\");\n                    eventEmitter.emit(\"status\", params);\n                }\n            });\n            \n            audioPlayer.prepare();\n            audioPlayer.start();\n            Log.e(\"TAG\", \"Audio playback started\");\n            \n            // Build response with file URI\n            String fileUri = fileName;\n            Activity activity = getCurrentActivity();\n            if (activity != null && !fileName.startsWith(\"content://\")) {\n                try {\n                    Uri contentUri = FileProvider.getUriForFile(\n                        activity, \n                        activity.getApplicationContext().getPackageName() + \".fileprovider\", \n                        new File(fileName));\n                    fileUri = contentUri.toString();\n                } catch (Exception e) {\n                    Log.e(\"TAG\", \"FileProvider error: \" + e.getMessage());\n                    fileUri = \"file://\" + fileName;\n                }\n            }\n            \n            callback.invoke(\"{\\\"success\\\": true, \\\"file\\\": \\\"\" + fileUri + \"\\\"}\");\n        } catch (IOException e) {\n            Log.e(\"TAG\", \"prepare() failed: \" + e.getMessage());\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        } catch (Exception e) {\n            Log.e(\"TAG\", \"playAudio error: \" + e.getMessage());\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        }\n    }\n\n    @ReactMethod\n    public void pauseRecording(Promise promise) {\n        if (audioRecorder != null) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                try {\n                    audioRecorder.pause();\n                    stopAmplitudePolling();\n                    promise.resolve(\"Recording paused successfully\");\n                } catch (Exception e) {\n                    promise.resolve(\"Failed to pause recording\");\n                }\n            } else {\n                // Pause is not supported before API 24\n                promise.resolve(\"Pause not supported on this device\");\n            }\n        } else {\n            promise.resolve(\"No audio recorder initialized\");\n        }\n    }\n\n    @ReactMethod\n    public void resumeRecording(Promise promise) {\n        if (audioRecorder != null) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                try {\n                    audioRecorder.resume();\n                    startAmplitudePolling();\n                    promise.resolve(\"Recording resumed successfully\");\n                } catch (Exception e) {\n                    promise.resolve(\"Failed to pause recording\");\n                }\n            } else {\n                // Pause is not supported before API 24\n                promise.resolve(\"Resume not supported on this device\");\n            }\n        } else {\n            promise.resolve(\"No audio recorder initialized\");\n        }\n    }\n\n    @ReactMethod\n    public void pausePlaying(Callback callback) {\n        if (audioPlayer != null && audioPlayer.isPlaying()) {\n            Log.e(\"TAG\", \"Trying to pause Audio: \" + fileName);\n            audioPlayer.pause();\n            \n            // Get current position for accurate resume\n            int currentPosition = audioPlayer.getCurrentPosition();\n            \n            // Build response with file URI and position\n            String fileUri = fileName != null ? fileName : \"\";\n            Activity activity = getCurrentActivity();\n            if (activity != null && fileName != null) {\n                try {\n                    Uri contentUri = FileProvider.getUriForFile(\n                        activity, \n                        activity.getApplicationContext().getPackageName() + \".fileprovider\", \n                        new File(fileName));\n                    fileUri = contentUri.toString();\n                } catch (Exception e) {\n                    Log.e(\"TAG\", \"FileProvider error: \" + e.getMessage());\n                    fileUri = \"file://\" + fileName;\n                }\n            }\n            \n            callback.invoke(\"{\\\"success\\\": true, \\\"file\\\": \\\"\" + fileUri + \"\\\", \\\"position\\\": \" + currentPosition + \"}\");\n            Log.e(\"TAG\", \"Paused Audio at position: \" + currentPosition);\n        } else {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio playing\\\"}\");\n        }\n    }\n\n    /**\n     * Resume playback from the current paused position.\n     * This is instant since the MediaPlayer is already prepared.\n     */\n    @ReactMethod\n    public void resumePlaying(Callback callback) {\n        if (audioPlayer != null) {\n            try {\n                // Check if already playing\n                if (audioPlayer.isPlaying()) {\n                    callback.invoke(\"{\\\"success\\\": true, \\\"message\\\": \\\"Already playing\\\"}\");\n                    return;\n                }\n                \n                // Get current position before resuming\n                int currentPosition = audioPlayer.getCurrentPosition();\n                \n                // Resume playback - this is instant since player is already prepared\n                audioPlayer.start();\n                Log.e(\"TAG\", \"Resumed Audio from position: \" + currentPosition);\n                \n                // Build response with file URI and position\n                String fileUri = fileName != null ? fileName : \"\";\n                Activity activity = getCurrentActivity();\n                if (activity != null && fileName != null) {\n                    try {\n                        Uri contentUri = FileProvider.getUriForFile(\n                            activity, \n                            activity.getApplicationContext().getPackageName() + \".fileprovider\", \n                            new File(fileName));\n                        fileUri = contentUri.toString();\n                    } catch (Exception e) {\n                        Log.e(\"TAG\", \"FileProvider error: \" + e.getMessage());\n                        fileUri = \"file://\" + fileName;\n                    }\n                }\n                \n                callback.invoke(\"{\\\"success\\\": true, \\\"file\\\": \\\"\" + fileUri + \"\\\", \\\"position\\\": \" + currentPosition + \"}\");\n            } catch (Exception e) {\n                Log.e(\"TAG\", \"resumePlaying error: \" + e.getMessage());\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n            }\n        } else {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio player initialized\\\"}\");\n        }\n    }\n\n    @ReactMethod\n    private void stopPlaying(Callback callback) {\n        if (audioPlayer != null) {\n            try {\n                audioPlayer.stop();\n            } catch (Exception ignore) {}\n            try {\n                audioPlayer.release();\n            } catch (Exception ignore) {}\n            audioPlayer = null;\n            \n            // Build response with file URI\n            String fileUri = fileName != null ? fileName : \"\";\n            Activity activity = getCurrentActivity();\n            if (activity != null && fileName != null) {\n                try {\n                    Uri contentUri = FileProvider.getUriForFile(\n                        activity, \n                        activity.getApplicationContext().getPackageName() + \".fileprovider\", \n                        new File(fileName));\n                    fileUri = contentUri.toString();\n                } catch (Exception e) {\n                    Log.e(\"TAG\", \"FileProvider error: \" + e.getMessage());\n                    fileUri = \"file://\" + fileName;\n                }\n            }\n            \n            callback.invoke(\"{\\\"success\\\": true, \\\"file\\\": \\\"\" + fileUri + \"\\\"}\");\n        } else {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio player\\\"}\");\n        }\n    }\n\n    @ReactMethod\n    public void releaseMediaResources(final Callback callback) {\n    // Stop amplitude polling\n    stopAmplitudePolling();\n    \n    // Stop active recording\n    if (audioRecorder != null) {\n        try {\n        audioRecorder.stop();\n        } catch (Exception ignored) {\n        // ignore IllegalStateException if already stopped\n        }\n        try { audioRecorder.release(); } catch (Exception ignore) {}\n        audioRecorder = null;\n    }\n\n    // Stop any playback\n    if (audioPlayer != null) {\n        try {\n        if (audioPlayer.isPlaying()) audioPlayer.stop();\n        } catch (Exception ignore) {}\n        try { audioPlayer.release(); } catch (Exception ignore) {}\n        audioPlayer = null;\n    }\n\n    // Build result JSON your JS expects: it parses .file (Android) and .duration (iOS use-case)\n    try {\n        Activity activity = getCurrentActivity();\n        JSONObject res = new JSONObject();\n\n        if (fileName != null) {\n        res.put(\"path\", fileName);\n        if (activity != null) {\n            Uri contentUri = FileProvider.getUriForFile(\n            activity, activity.getPackageName() + \".fileprovider\", new File(fileName));\n            res.put(\"file\", contentUri.toString()); // your JS uses this\n        } else {\n            res.put(\"file\", \"file://\" + fileName);\n        }\n        } else {\n        res.put(\"file\", JSONObject.NULL);\n        res.put(\"path\", JSONObject.NULL);\n        }\n\n        // Android side: your JS ignores duration (it computes via timer)\n        res.put(\"duration\", 0);\n\n        callback.invoke(res.toString());\n    } catch (Exception e) {\n        callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n    }\n    }\n\n    @ReactMethod\n    public void deleteFile(Callback callback) {\n        if (fileName == null) {\n            callback.invoke(\"{\\\"success\\\": false}\");\n        } else {\n            try {\n                File file = new File(fileName);\n                boolean isDeleted = file.delete();\n                callback.invoke(\"{\\\"success\\\": \" + isDeleted +\"}\");\n            } catch (Exception e) {\n                callback.invoke(\"{\\\"success\\\": false}\");\n            }\n        }\n    }\n\n    // ==================== Segment-Based Recording Methods ====================\n\n    /**\n     * Finalize the current recording segment without stopping the recorder.\n     * This allows the user to preview the recorded audio while paused.\n     * @validates Requirements 11.1\n     */\n    @ReactMethod\n    public void finalizeSegment(Callback callback) {\n        stopAmplitudePolling();\n        \n        if (audioRecorder == null) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No active recording\\\"}\");\n            return;\n        }\n        \n        try {\n            audioRecorder.stop();\n            audioRecorder.release();\n            audioRecorder = null;\n            \n            // Add current file to segments (store the actual file path, not content URI)\n            if (fileName != null) {\n                segmentPaths.add(fileName);\n            }\n            \n            // Return the file path (not content URI) so JS can use it for playback\n            String jsonString = String.format(\n                \"{\\\"success\\\": true, \\\"segmentPath\\\": \\\"%s\\\", \\\"duration\\\": 0, \\\"segmentIndex\\\": %d}\",\n                fileName,\n                segmentPaths.size() - 1\n            );\n            callback.invoke(jsonString);\n        } catch (Exception e) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        }\n    }\n\n    /**\n     * Start recording a new segment after pausing.\n     * This creates a new audio file for the next segment.\n     * @validates Requirements 11.2\n     */\n    @ReactMethod\n    public void startNewSegment(Callback callback) {\n        Activity activity = getCurrentActivity();\n        if (activity == null) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"E_NO_ACTIVITY\\\"}\");\n            return;\n        }\n\n        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO)\n            != PackageManager.PERMISSION_GRANTED) {\n            callback.invoke(\"{\\\"granted\\\": false}\");\n            return;\n        }\n\n        try {\n            // Create new file for this segment\n            File dir = activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC);\n            if (dir == null) {\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"E_NO_DIR\\\"}\");\n                return;\n            }\n            String ts = new SimpleDateFormat(\"yyyyMMddHHmmss\").format(new Date());\n            fileName = new File(dir, \"audio-segment-\" + ts + \"-\" + segmentPaths.size() + \".m4a\").getAbsolutePath();\n\n            audioRecorder = new MediaRecorder();\n            audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);\n            audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);\n            audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);\n            audioRecorder.setAudioEncodingBitRate(128000);\n            audioRecorder.setAudioSamplingRate(44100);\n            audioRecorder.setOutputFile(fileName);\n\n            audioRecorder.prepare();\n            audioRecorder.start();\n            \n            // Start amplitude polling for real-time waveform\n            startAmplitudePolling();\n\n            String jsonString = String.format(\n                \"{\\\"success\\\": true, \\\"file\\\": \\\"%s\\\", \\\"segmentIndex\\\": %d}\",\n                fileName,\n                segmentPaths.size()\n            );\n            callback.invoke(jsonString);\n        } catch (Exception e) {\n            try { if (audioRecorder != null) audioRecorder.release(); } catch (Exception ignore) {}\n            audioRecorder = null;\n            stopAmplitudePolling();\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        }\n    }\n\n    /**\n     * Merge all recorded segments into a single audio file.\n     * Uses simple concatenation for M4A files.\n     * @validates Requirements 11.4\n     */\n    @ReactMethod\n    public void mergeSegments(ReadableArray segmentPathsArray, Callback callback) {\n        if (segmentPathsArray == null || segmentPathsArray.size() == 0) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No segments to merge\\\"}\");\n            return;\n        }\n\n        // Convert ReadableArray to List\n        List<String> paths = new ArrayList<>();\n        for (int i = 0; i < segmentPathsArray.size(); i++) {\n            paths.add(segmentPathsArray.getString(i));\n        }\n\n        // If only one segment, just return it and update fileName\n        if (paths.size() == 1) {\n            String segmentPath = paths.get(0);\n            // Handle content:// URIs - convert to file path\n            if (segmentPath.startsWith(\"content://\")) {\n                String[] parts = segmentPath.split(\"/external_files\");\n                if (parts.length > 1) {\n                    segmentPath = \"/storage/emulated/0\" + parts[1];\n                }\n            } else if (segmentPath.startsWith(\"file://\")) {\n                segmentPath = segmentPath.substring(7);\n            }\n            \n            // Update fileName so playAudio uses this file\n            fileName = segmentPath;\n            \n            Activity activity = getCurrentActivity();\n            String fileUri = segmentPath;\n            if (activity != null) {\n                try {\n                    Uri contentUri = FileProvider.getUriForFile(\n                        activity, activity.getPackageName() + \".fileprovider\", new File(segmentPath));\n                    fileUri = contentUri.toString();\n                } catch (Exception e) {\n                    // Use original path if FileProvider fails\n                    fileUri = \"file://\" + segmentPath;\n                }\n            }\n            callback.invoke(String.format(\"{\\\"success\\\": true, \\\"mergedPath\\\": \\\"%s\\\", \\\"totalDuration\\\": 0}\", fileUri));\n            return;\n        }\n\n        // Merge segments in background thread using MediaExtractor/MediaMuxer\n        ExecutorService executor = Executors.newSingleThreadExecutor();\n        executor.execute(() -> {\n            try {\n                Activity activity = getCurrentActivity();\n                if (activity == null) {\n                    new Handler(Looper.getMainLooper()).post(() -> \n                        callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No activity\\\"}\"));\n                    return;\n                }\n\n                // Create output file\n                File dir = activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC);\n                String ts = new SimpleDateFormat(\"yyyyMMddHHmmss\").format(new Date());\n                String mergedPath = new File(dir, \"audio-merged-\" + ts + \".m4a\").getAbsolutePath();\n\n                // Use MediaMuxer for proper AAC/M4A merging\n                android.media.MediaMuxer muxer = new android.media.MediaMuxer(mergedPath, android.media.MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);\n                int audioTrackIndex = -1;\n                boolean muxerStarted = false;\n                long totalDurationUs = 0;\n                \n                for (String path : paths) {\n                    // Handle content:// URIs and file:// URIs\n                    String actualPath = path;\n                    if (path.startsWith(\"content://\")) {\n                        String[] parts = path.split(\"/external_files\");\n                        if (parts.length > 1) {\n                            actualPath = \"/storage/emulated/0\" + parts[1];\n                        } else {\n                            Log.e(TAG, \"Cannot resolve content URI: \" + path);\n                            continue;\n                        }\n                    }\n                    if (actualPath.startsWith(\"file://\")) {\n                        actualPath = actualPath.substring(7);\n                    }\n                    \n                    File segmentFile = new File(actualPath);\n                    if (!segmentFile.exists()) {\n                        Log.e(TAG, \"Segment file not found: \" + actualPath);\n                        continue;\n                    }\n                    \n                    android.media.MediaExtractor extractor = new android.media.MediaExtractor();\n                    extractor.setDataSource(actualPath);\n                    \n                    // Find audio track\n                    int trackIndex = -1;\n                    for (int i = 0; i < extractor.getTrackCount(); i++) {\n                        android.media.MediaFormat format = extractor.getTrackFormat(i);\n                        String mime = format.getString(android.media.MediaFormat.KEY_MIME);\n                        if (mime != null && mime.startsWith(\"audio/\")) {\n                            trackIndex = i;\n                            if (!muxerStarted) {\n                                audioTrackIndex = muxer.addTrack(format);\n                                muxer.start();\n                                muxerStarted = true;\n                            }\n                            break;\n                        }\n                    }\n                    \n                    if (trackIndex < 0) {\n                        extractor.release();\n                        continue;\n                    }\n                    \n                    extractor.selectTrack(trackIndex);\n                    \n                    // Copy samples\n                    java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(1024 * 1024);\n                    android.media.MediaCodec.BufferInfo bufferInfo = new android.media.MediaCodec.BufferInfo();\n                    \n                    while (true) {\n                        int sampleSize = extractor.readSampleData(buffer, 0);\n                        if (sampleSize < 0) break;\n                        \n                        bufferInfo.offset = 0;\n                        bufferInfo.size = sampleSize;\n                        bufferInfo.presentationTimeUs = totalDurationUs + extractor.getSampleTime();\n                        bufferInfo.flags = extractor.getSampleFlags();\n                        \n                        muxer.writeSampleData(audioTrackIndex, buffer, bufferInfo);\n                        extractor.advance();\n                    }\n                    \n                    // Get duration of this segment\n                    android.media.MediaFormat format = extractor.getTrackFormat(trackIndex);\n                    if (format.containsKey(android.media.MediaFormat.KEY_DURATION)) {\n                        totalDurationUs += format.getLong(android.media.MediaFormat.KEY_DURATION);\n                    }\n                    \n                    extractor.release();\n                }\n                \n                if (muxerStarted) {\n                    muxer.stop();\n                }\n                muxer.release();\n\n                // Update fileName to merged file so playAudio uses it\n                fileName = mergedPath;\n\n                String fileUri = mergedPath;\n                try {\n                    Uri contentUri = FileProvider.getUriForFile(\n                        activity, activity.getPackageName() + \".fileprovider\", new File(mergedPath));\n                    fileUri = contentUri.toString();\n                } catch (Exception e) {\n                    // Use original path if FileProvider fails\n                    fileUri = \"file://\" + mergedPath;\n                }\n\n                final String finalUri = fileUri;\n                final double totalDurationSec = totalDurationUs / 1000000.0;\n                new Handler(Looper.getMainLooper()).post(() -> \n                    callback.invoke(String.format(\"{\\\"success\\\": true, \\\"mergedPath\\\": \\\"%s\\\", \\\"totalDuration\\\": %f}\", finalUri, totalDurationSec)));\n            } catch (Exception e) {\n                Log.e(TAG, \"Error merging segments: \" + e.getMessage());\n                new Handler(Looper.getMainLooper()).post(() -> \n                    callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\"));\n            }\n        });\n    }\n\n    /**\n     * Play multiple segments sequentially.\n     * @validates Requirements 11.4\n     */\n    @ReactMethod\n    public void playSegments(ReadableArray segmentPathsArray, Callback callback) {\n        if (segmentPathsArray == null || segmentPathsArray.size() == 0) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No segments to play\\\"}\");\n            return;\n        }\n\n        // Convert ReadableArray to List\n        final List<String> paths = new ArrayList<>();\n        for (int i = 0; i < segmentPathsArray.size(); i++) {\n            paths.add(segmentPathsArray.getString(i));\n        }\n\n        // Stop any existing playback\n        if (audioPlayer != null) {\n            try {\n                if (audioPlayer.isPlaying()) audioPlayer.stop();\n                audioPlayer.release();\n            } catch (Exception ignore) {}\n            audioPlayer = null;\n        }\n        if (nextAudioPlayer != null) {\n            try {\n                nextAudioPlayer.release();\n            } catch (Exception ignore) {}\n            nextAudioPlayer = null;\n        }\n\n        currentSegmentIndex = 0;\n        segmentPathsList = paths;\n        playNextSegmentGapless(callback);\n    }\n\n    // Store segment paths for gapless playback\n    private List<String> segmentPathsList = new ArrayList<>();\n    private MediaPlayer nextAudioPlayer = null;\n\n    private MediaPlayer createMediaPlayerForPath(String path) throws Exception {\n        MediaPlayer mp = new MediaPlayer();\n        \n        if (path.startsWith(\"file://\")) {\n            path = path.substring(7);\n        }\n        \n        if (path.startsWith(\"content://\")) {\n            Activity activity = getCurrentActivity();\n            if (activity != null) {\n                mp.setDataSource(activity, Uri.parse(path));\n            } else {\n                String[] parts = path.split(\"/external_files\");\n                if (parts.length > 1) {\n                    path = \"/storage/emulated/0\" + parts[1];\n                    mp.setDataSource(path);\n                } else {\n                    throw new Exception(\"Cannot resolve content URI: \" + path);\n                }\n            }\n        } else {\n            mp.setDataSource(path);\n        }\n        \n        mp.prepare();\n        return mp;\n    }\n\n    private void playNextSegmentGapless(final Callback callback) {\n        if (currentSegmentIndex >= segmentPathsList.size()) {\n            // All segments played\n            if (eventEmitter == null) {\n                eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n            }\n            WritableMap params = Arguments.createMap();\n            params.putString(\"state\", \"playbackComplete\");\n            eventEmitter.emit(\"status\", params);\n            return;\n        }\n\n        try {\n            String path = segmentPathsList.get(currentSegmentIndex);\n            \n            // Create current player\n            audioPlayer = createMediaPlayerForPath(path);\n            \n            // Pre-load next segment for gapless playback\n            if (currentSegmentIndex + 1 < segmentPathsList.size()) {\n                try {\n                    String nextPath = segmentPathsList.get(currentSegmentIndex + 1);\n                    nextAudioPlayer = createMediaPlayerForPath(nextPath);\n                    audioPlayer.setNextMediaPlayer(nextAudioPlayer);\n                } catch (Exception e) {\n                    Log.e(TAG, \"Error pre-loading next segment: \" + e.getMessage());\n                    // Continue without gapless - will have small gap\n                }\n            }\n            \n            audioPlayer.setOnCompletionListener(mp -> {\n                currentSegmentIndex++;\n                mp.release();\n                \n                // Move next player to current\n                if (nextAudioPlayer != null) {\n                    audioPlayer = nextAudioPlayer;\n                    nextAudioPlayer = null;\n                    \n                    // Pre-load the next-next segment\n                    if (currentSegmentIndex + 1 < segmentPathsList.size()) {\n                        try {\n                            String nextPath = segmentPathsList.get(currentSegmentIndex + 1);\n                            nextAudioPlayer = createMediaPlayerForPath(nextPath);\n                            audioPlayer.setNextMediaPlayer(nextAudioPlayer);\n                        } catch (Exception e) {\n                            Log.e(TAG, \"Error pre-loading next segment: \" + e.getMessage());\n                        }\n                    }\n                    \n                    // Set completion listener for the new current player\n                    audioPlayer.setOnCompletionListener(this::onSegmentComplete);\n                } else {\n                    audioPlayer = null;\n                    playNextSegmentGapless(callback);\n                }\n            });\n            \n            audioPlayer.start();\n            \n            if (currentSegmentIndex == 0) {\n                callback.invoke(String.format(\"{\\\"success\\\": true, \\\"segmentCount\\\": %d}\", segmentPathsList.size()));\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Error playing segment: \" + e.getMessage());\n            if (currentSegmentIndex == 0) {\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n            }\n        }\n    }\n\n    private void onSegmentComplete(MediaPlayer mp) {\n        currentSegmentIndex++;\n        mp.release();\n        \n        if (nextAudioPlayer != null) {\n            audioPlayer = nextAudioPlayer;\n            nextAudioPlayer = null;\n            \n            // Pre-load the next-next segment\n            if (currentSegmentIndex + 1 < segmentPathsList.size()) {\n                try {\n                    String nextPath = segmentPathsList.get(currentSegmentIndex + 1);\n                    nextAudioPlayer = createMediaPlayerForPath(nextPath);\n                    audioPlayer.setNextMediaPlayer(nextAudioPlayer);\n                } catch (Exception e) {\n                    Log.e(TAG, \"Error pre-loading next segment: \" + e.getMessage());\n                }\n            }\n            \n            audioPlayer.setOnCompletionListener(this::onSegmentComplete);\n        } else {\n            audioPlayer = null;\n            // Check if there are more segments\n            if (currentSegmentIndex < segmentPathsList.size()) {\n                playNextSegmentGapless(null);\n            } else {\n                // All done\n                if (eventEmitter == null) {\n                    eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n                }\n                WritableMap params = Arguments.createMap();\n                params.putString(\"state\", \"playbackComplete\");\n                eventEmitter.emit(\"status\", params);\n            }\n        }\n    }\n\n    // Keep old method for backward compatibility but redirect to new one\n    private void playNextSegment(final List<String> paths, final Callback callback) {\n        segmentPathsList = paths;\n        playNextSegmentGapless(callback);\n    }\n\n    /**\n     * Seek to a specific position in the audio playback.\n     * Uses OnSeekCompleteListener to wait for the async seek to complete\n     * before invoking the callback, preventing race conditions.\n     * @param positionMs Position in milliseconds\n     */\n    @ReactMethod\n    public void seekTo(int positionMs, Callback callback) {\n        if (audioPlayer != null) {\n            try {\n                // Set up listener to wait for seek to complete\n                audioPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {\n                    @Override\n                    public void onSeekComplete(MediaPlayer mp) {\n                        // Clear the listener after use\n                        mp.setOnSeekCompleteListener(null);\n                        // Get actual position after seek completes\n                        int actualPosition = mp.getCurrentPosition();\n                        callback.invoke(\"{\\\"success\\\": true, \\\"position\\\": \" + actualPosition + \"}\");\n                    }\n                });\n                audioPlayer.seekTo(positionMs);\n            } catch (Exception e) {\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n            }\n        } else {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio player active\\\"}\");\n        }\n    }\n\n    /**\n     * Get current playback position in milliseconds.\n     * Used for accurate waveform sync during playback.\n     */\n    @ReactMethod\n    public void getPlaybackPosition(Callback callback) {\n        if (audioPlayer != null) {\n            try {\n                int position = audioPlayer.getCurrentPosition();\n                int duration = audioPlayer.getDuration();\n                callback.invoke(\"{\\\"success\\\": true, \\\"position\\\": \" + position + \", \\\"duration\\\": \" + duration + \"}\");\n            } catch (Exception e) {\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n            }\n        } else {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio player active\\\"}\");\n        }\n    }\n\n    /**\n     * Start playback from a specific position.\n     * Uses OnSeekCompleteListener to ensure seek completes before starting playback.\n     * @param positionMs Position in milliseconds to start from\n     */\n    @ReactMethod\n    public void playFromPosition(int positionMs, Callback callback) {\n        if (fileName == null || fileName.isEmpty()) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio file to play\\\"}\");\n            return;\n        }\n        \n        // Stop any existing playback\n        if (audioPlayer != null) {\n            try {\n                if (audioPlayer.isPlaying()) audioPlayer.stop();\n                audioPlayer.release();\n            } catch (Exception ignore) {}\n            audioPlayer = null;\n        }\n        \n        try {\n            audioPlayer = new MediaPlayer();\n            \n            String playPath = fileName;\n            if (playPath.startsWith(\"content://\")) {\n                Activity activity = getCurrentActivity();\n                if (activity != null) {\n                    audioPlayer.setDataSource(activity, Uri.parse(playPath));\n                } else {\n                    String[] parts = playPath.split(\"/external_files\");\n                    if (parts.length > 1) {\n                        playPath = \"/storage/emulated/0\" + parts[1];\n                        audioPlayer.setDataSource(playPath);\n                    } else {\n                        throw new IOException(\"Cannot resolve content URI without activity\");\n                    }\n                }\n            } else {\n                if (playPath.startsWith(\"file://\")) {\n                    playPath = playPath.substring(7);\n                }\n                audioPlayer.setDataSource(playPath);\n            }\n            \n            // Set up completion listener to emit event when playback finishes\n            audioPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {\n                @Override\n                public void onCompletion(MediaPlayer mp) {\n                    // Emit playback complete event to JS\n                    if (eventEmitter == null) {\n                        eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n                    }\n                    WritableMap params = Arguments.createMap();\n                    params.putString(\"state\", \"playbackComplete\");\n                    eventEmitter.emit(\"status\", params);\n                }\n            });\n            \n            audioPlayer.prepare();\n            \n            // Use OnSeekCompleteListener to ensure seek completes before starting playback\n            audioPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {\n                @Override\n                public void onSeekComplete(MediaPlayer mp) {\n                    // Clear the listener after use\n                    mp.setOnSeekCompleteListener(null);\n                    // Start playback after seek completes\n                    mp.start();\n                    int actualPosition = mp.getCurrentPosition();\n                    callback.invoke(\"{\\\"success\\\": true, \\\"position\\\": \" + actualPosition + \"}\");\n                }\n            });\n            audioPlayer.seekTo(positionMs);\n            \n        } catch (Exception e) {\n            Log.e(\"TAG\", \"playFromPosition error: \" + e.getMessage());\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        }\n    }\n\n    /**\n     * Delete all segment files.\n     * @validates Requirements 11.5\n     */\n    @ReactMethod\n    public void deleteSegments(ReadableArray segmentPathsArray, Callback callback) {\n        if (segmentPathsArray == null || segmentPathsArray.size() == 0) {\n            callback.invoke(\"{\\\"success\\\": true, \\\"deletedCount\\\": 0}\");\n            return;\n        }\n\n        int deletedCount = 0;\n        for (int i = 0; i < segmentPathsArray.size(); i++) {\n            try {\n                String path = segmentPathsArray.getString(i);\n                // Handle content:// URIs\n                if (path.startsWith(\"file://\")) {\n                    path = path.substring(7);\n                }\n                if (path.startsWith(\"content://\")) {\n                    // Skip content URIs\n                    continue;\n                }\n                \n                File file = new File(path);\n                if (file.exists() && file.delete()) {\n                    deletedCount++;\n                }\n            } catch (Exception e) {\n                Log.e(TAG, \"Error deleting segment: \" + e.getMessage());\n            }\n        }\n\n        // Clear internal segments list\n        segmentPaths.clear();\n        currentSegmentIndex = 0;\n\n        callback.invoke(String.format(\"{\\\"success\\\": true, \\\"deletedCount\\\": %d}\", deletedCount));\n    }\n\n    /**\n     * Get all current segment paths.\n     */\n    @ReactMethod\n    public void getSegmentPaths(Callback callback) {\n        try {\n            JSONArray pathsArray = new JSONArray();\n            Activity activity = getCurrentActivity();\n            \n            for (String path : segmentPaths) {\n                String fileUri = path;\n                if (activity != null) {\n                    try {\n                        Uri contentUri = FileProvider.getUriForFile(\n                            activity, activity.getPackageName() + \".fileprovider\", new File(path));\n                        fileUri = contentUri.toString();\n                    } catch (Exception e) {\n                        // Use original path if FileProvider fails\n                    }\n                }\n                pathsArray.put(fileUri);\n            }\n            \n            callback.invoke(String.format(\"{\\\"success\\\": true, \\\"segments\\\": %s}\", pathsArray.toString()));\n        } catch (Exception e) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        }\n    }\n\n    /**\n     * Clear all segments and reset state.\n     */\n    @ReactMethod\n    public void clearSegments(Callback callback) {\n        segmentPaths.clear();\n        currentSegmentIndex = 0;\n        \n        if (audioPlayer != null) {\n            try {\n                if (audioPlayer.isPlaying()) audioPlayer.stop();\n                audioPlayer.release();\n            } catch (Exception ignore) {}\n            audioPlayer = null;\n        }\n        \n        callback.invoke(\"{\\\"success\\\": true}\");\n    }\n\n    @ReactMethod\n    public void openCamera(String fileType, Integer imageQuality, final Callback callback) {\n        IMAGE_QUALITY = imageQuality;\n        final Activity currentActivity = getCurrentActivity();\n        if (currentActivity == null) {\n            WritableMap errorMap = new WritableNativeMap();\n            errorMap.putString(\"error\", \"Activity doesn't exist\");\n            callback.invoke(errorMap);\n            return;\n        }\n\n        // Check and request permissions if needed, then proceed with camera launch\n        if (ContextCompat.checkSelfPermission(currentActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(currentActivity, new String[] {Manifest.permission.CAMERA}, REQUEST_LAUNCH_CAMERA);\n        } else {\n            launchCameraIntent(currentActivity, callback);\n        }\n    }\n\n    private void launchCameraIntent(Activity currentActivity, Callback callback) {\n        this.callback = callback;\n        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n        try {\n            cameraFile = createImageFile(); // Use the global variable here\n            if (cameraFile != null) {\n                Uri photoURI = FileProvider.getUriForFile(currentActivity,\n                        currentActivity.getApplicationContext().getPackageName() + \".fileprovider\",\n                        cameraFile);\n                cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);\n                currentActivity.startActivityForResult(cameraIntent, REQUEST_LAUNCH_CAMERA);\n            } else {\n                WritableMap errorMap = new WritableNativeMap();\n                errorMap.putString(\"error\", \"Could not create image file\");\n                callback.invoke(errorMap);\n            }\n        } catch (Exception e) {\n            WritableMap errorMap = new WritableNativeMap();\n            errorMap.putString(\"error\", \"Failed to launch camera: \" + e.getMessage());\n            callback.invoke(errorMap);\n        }\n    }\n\n    private File createImageFile() throws IOException {\n        // Create an image file name\n        String timeStamp = new SimpleDateFormat(\"yyyyMMdd_HHmmss\").format(new Date());\n        String imageFileName = \"JPEG_\" + timeStamp + \"_\";\n        File storageDir = getCurrentActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);\n        File image = File.createTempFile(\n                imageFileName,  /* prefix */\n                \".jpeg\",         /* suffix */\n                storageDir      /* directory */\n        );\n        return image;\n    }\n\n\n    @ReactMethod\n    public void openFileChooser(String fileType, Callback callback) {\n        try {\n            this.callback = callback;\n            Activity currentActivity = getCurrentActivity();\n            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n            intent.addCategory(Intent.CATEGORY_OPENABLE);\n\n            intent.setType(getType(fileType));\n//            intent.putExtra(Intent.EXTRA_MIME_TYPES, getType(fileType));\n\n            boolean multiple = false; //!args.isNull(OPTION_MULTIPLE) && args.getBoolean(OPTION_MULTIPLE);\n            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);\n\n            currentActivity.startActivityForResult(Intent.createChooser(intent, \"Select a file\"), READ_REQUEST_CODE, Bundle.EMPTY);\n        } catch (ActivityNotFoundException e) {\n            Log.e(TAG, \"openFileChooser: \" + e.getMessage());\n        } catch (Exception e) {\n            e.printStackTrace();\n            Log.e(TAG, \"openFileChooser: \" + e.getMessage());\n        }\n    }\n\n    @ReactMethod\n    public void checkAndDownload(String url, String name, Callback callback) {\n        Long downloadId = downloadFile(name, url);\n        try {\n            if (downloadId == null) {\n                callback.invoke(\"Download failed: ID is null\");\n                return;\n            }\n\n            obj = new JSONObject();\n            obj.put(\"downloadId\", downloadId.longValue());\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        callback.invoke(obj.toString());\n    }\n\n    @ReactMethod\n    public void openFile(String url, String name, Callback callback) {\n        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));\n        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        mContext.getApplicationContext().startActivity(i);\n        callback.invoke(\"{\\\"success\\\": true}\");\n    }\n\n\n    @ReactMethod\n    public void openFileWithOption(String fileName, Callback callback) {\n        try {\n            // Get the Downloads directory path\n            File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);\n            File file = new File(downloadsDir, fileName);\n    \n            if (!file.exists()) {\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"File not found\\\"}\");\n                return;\n            }\n    \n            // Use FileProvider for secure URI access\n            Uri uri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + \".fileprovider\", file);\n            String mimeType = mContext.getContentResolver().getType(uri);\n            Log.d(\"openFileWithOption: \", \" MIMETYPE \" + mimeType + \" \" + fileName);\n    \n            if (mimeType == null) {\n                mimeType = \"*/*\"; // Fallback MIME type\n            }\n    \n            Intent intent = new Intent(Intent.ACTION_VIEW);\n            intent.setDataAndType(uri, mimeType);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n    \n            Intent chooser = Intent.createChooser(intent, \"Open file with\");\n            chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n    \n            if (chooser.resolveActivity(mContext.getPackageManager()) != null) {\n                mContext.getApplicationContext().startActivity(chooser);\n                callback.invoke(\"{\\\"success\\\": true}\");\n            } else {\n                callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"No app available\\\"}\");\n            }\n        } catch (Exception e) {\n            callback.invoke(\"{\\\"success\\\": false, \\\"error\\\": \\\"\" + e.getMessage() + \"\\\"}\");\n        }\n    }\n    \n\n\n    @ReactMethod\n    public void doesFileExist(String fileName, Callback callback) {\n        try {\n            obj = new JSONObject();\n            obj.put(\"exists\", fileExists(fileName));\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        callback.invoke(obj.toString());\n    }\n\n    private void createDirectory(String var0) {\n        if (!(new File(var0)).exists()) {\n            boolean worked = (new File(var0)).mkdirs();\n            Log.e(TAG, \"createDirectory: \"+ worked);\n        }\n    }\n\n    private File makeEmptyFileWithTitle(Context context, String title) {\n        String dir;\n        if (Build.VERSION_CODES.R > Build.VERSION.SDK_INT) {\n            dir = Environment.getExternalStorageDirectory() + \"/cometchat/shared/\";\n        } else {\n            if (Environment.isExternalStorageManager()) {\n                dir = Environment.getExternalStorageState() + \"/cometchat/shared/\";\n            } else {\n                dir = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getPath() + \"/cometchat/shared/\";\n            }\n        }\n        createDirectory(dir);\n        return new File(dir, title);\n    }\n\n    private boolean fileExists(String fileName) {\n        File f = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);\n        return f.exists();\n    }\n\n    private Long downloadFile(String fileName, String url){\n        try {\n            if (fileExists(fileName)) {\n                return null;\n            }\n            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))\n                    .setTitle(fileName)\n                    .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)\n                    .setAllowedOverMetered(true)\n                    .setAllowedOverRoaming(false)\n                    .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);\n            DownloadManager downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);\n            Long downloadId = downloadManager.enqueue(request);\n            Log.d(\"FileCheck: \", \" yyyy \" + downloadId.longValue());\n\n            IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                mContext.registerReceiver(onDownloadComplete, filter, Context.RECEIVER_EXPORTED);\n            } else {\n                mContext.registerReceiver(onDownloadComplete, filter);\n            }\n            return downloadId;\n        } catch (Exception ex) {\n            Toast.makeText(mContext, \"Something went wrong.\", Toast.LENGTH_SHORT).show();\n        }\n        return null;\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return NAME;\n    }\n}"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/ImageManager.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.content.Intent;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.bridge.Callback;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\n\npublic class ImageManager extends ReactContextBaseJavaModule {\n    public static final String TAG = \"ImageManager\";\n\n    ImageManager(ReactApplicationContext context) {\n        super(context);\n    }\n\n    @ReactMethod\n    public void openImage(String url, Callback callback) {\n        try {\n            Intent i = new Intent(getReactApplicationContext(), CometChatImageViewer.class);\n            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            i.putExtra(\"imageUrl\", url);\n            getReactApplicationContext().startActivity(i);\n        } catch (Exception ex) {\n            Log.e(TAG, \"openImage: \" + ex.getMessage());\n            callback.invoke(\"error:\" + ex.getMessage());\n            return;\n        }\n        callback.invoke(\"success\");\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return \"ImageManager\";\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/ProximityModule.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.content.Context;\nimport android.os.PowerManager;\nimport android.os.PowerManager.WakeLock;\n\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.module.annotations.ReactModule;\nimport com.facebook.react.bridge.UiThreadUtil;\n\n@ReactModule(name = ProximityModule.NAME)\npublic class ProximityModule extends ReactContextBaseJavaModule {\n\n    public static final String NAME = \"Proximity\";\n    private WakeLock wakeLock;\n\n    public ProximityModule(ReactApplicationContext reactContext) {\n        super(reactContext);\n        try {\n            PowerManager pm = (PowerManager) reactContext.getSystemService(Context.POWER_SERVICE);\n            wakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, \"cometchat:\" + NAME);\n        } catch (Throwable t) {\n            wakeLock = null;\n        }\n    }\n\n    @Override\n    public String getName() {\n        return NAME;\n    }\n\n    @ReactMethod\n    public void setEnabled(final boolean enabled) {\n        if (wakeLock == null) return;\n\n        UiThreadUtil.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    if (enabled && !wakeLock.isHeld()) {\n                        wakeLock.acquire();\n                    } else if (!enabled && wakeLock.isHeld()) {\n                        wakeLock.release();\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/ReactVideoView.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.res.AssetFileDescriptor;\nimport android.graphics.Matrix;\nimport android.media.MediaPlayer;\nimport android.media.TimedMetaData;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.WindowManager;\nimport android.view.View;\nimport android.view.Window;\nimport android.webkit.CookieManager;\nimport android.widget.MediaController;\n\nimport com.zipfile.APKExpansionSupport;\nimport com.zipfile.ZipResourceFile;\nimport com.facebook.react.bridge.Arguments;\nimport com.facebook.react.bridge.LifecycleEventListener;\nimport com.facebook.react.bridge.ReadableMap;\nimport com.facebook.react.bridge.WritableMap;\nimport com.facebook.react.bridge.WritableArray;\nimport com.facebook.react.bridge.WritableNativeArray;\nimport com.facebook.react.uimanager.ThemedReactContext;\nimport com.facebook.react.uimanager.events.RCTEventEmitter;\nimport com.scalablevideoview.ScalableType;\nimport com.scalablevideoview.ScalableVideoView;\nimport com.scalablevideoview.ScaleManager;\nimport com.scalablevideoview.Size;\n\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.lang.Math;\nimport java.math.BigDecimal;\n\nimport javax.annotation.Nullable;\n\n@SuppressLint(\"ViewConstructor\")\npublic class ReactVideoView extends ScalableVideoView implements\n    MediaPlayer.OnPreparedListener,\n    MediaPlayer.OnErrorListener,\n    MediaPlayer.OnBufferingUpdateListener,\n    MediaPlayer.OnSeekCompleteListener,\n    MediaPlayer.OnCompletionListener,\n    MediaPlayer.OnInfoListener,\n    LifecycleEventListener,\n    MediaController.MediaPlayerControl {\n\n    public enum Events {\n        EVENT_LOAD_START(\"onVideoLoadStart\"),\n        EVENT_LOAD(\"onVideoLoad\"),\n        EVENT_ERROR(\"onVideoError\"),\n        EVENT_PROGRESS(\"onVideoProgress\"),\n        EVENT_TIMED_METADATA(\"onTimedMetadata\"),\n        EVENT_SEEK(\"onVideoSeek\"),\n        EVENT_END(\"onVideoEnd\"),\n        EVENT_STALLED(\"onPlaybackStalled\"),\n        EVENT_RESUME(\"onPlaybackResume\"),\n        EVENT_READY_FOR_DISPLAY(\"onReadyForDisplay\"),\n        EVENT_FULLSCREEN_WILL_PRESENT(\"onVideoFullscreenPlayerWillPresent\"),\n        EVENT_FULLSCREEN_DID_PRESENT(\"onVideoFullscreenPlayerDidPresent\"),\n        EVENT_FULLSCREEN_WILL_DISMISS(\"onVideoFullscreenPlayerWillDismiss\"),\n        EVENT_FULLSCREEN_DID_DISMISS(\"onVideoFullscreenPlayerDidDismiss\");\n\n        private final String mName;\n\n        Events(final String name) {\n            mName = name;\n        }\n\n        @Override\n        public String toString() {\n            return mName;\n        }\n    }\n\n    public static final String EVENT_PROP_FAST_FORWARD = \"canPlayFastForward\";\n    public static final String EVENT_PROP_SLOW_FORWARD = \"canPlaySlowForward\";\n    public static final String EVENT_PROP_SLOW_REVERSE = \"canPlaySlowReverse\";\n    public static final String EVENT_PROP_REVERSE = \"canPlayReverse\";\n    public static final String EVENT_PROP_STEP_FORWARD = \"canStepForward\";\n    public static final String EVENT_PROP_STEP_BACKWARD = \"canStepBackward\";\n\n    public static final String EVENT_PROP_DURATION = \"duration\";\n    public static final String EVENT_PROP_PLAYABLE_DURATION = \"playableDuration\";\n    public static final String EVENT_PROP_SEEKABLE_DURATION = \"seekableDuration\";\n    public static final String EVENT_PROP_CURRENT_TIME = \"currentTime\";\n    public static final String EVENT_PROP_SEEK_TIME = \"seekTime\";\n    public static final String EVENT_PROP_NATURALSIZE = \"naturalSize\";\n    public static final String EVENT_PROP_WIDTH = \"width\";\n    public static final String EVENT_PROP_HEIGHT = \"height\";\n    public static final String EVENT_PROP_ORIENTATION = \"orientation\";\n    public static final String EVENT_PROP_METADATA = \"metadata\";\n    public static final String EVENT_PROP_TARGET = \"target\";\n    public static final String EVENT_PROP_METADATA_IDENTIFIER = \"identifier\";\n    public static final String EVENT_PROP_METADATA_VALUE = \"value\";\n\n    public static final String EVENT_PROP_ERROR = \"error\";\n    public static final String EVENT_PROP_WHAT = \"what\";\n    public static final String EVENT_PROP_EXTRA = \"extra\";\n\n    private ThemedReactContext mThemedReactContext;\n    private RCTEventEmitter mEventEmitter;\n\n    private Handler mProgressUpdateHandler = new Handler();\n    private Runnable mProgressUpdateRunnable = null;\n    private Handler videoControlHandler = new Handler();\n    private MediaController mediaController;\n\n    private String mSrcUriString = null;\n    private String mSrcType = \"mp4\";\n    private ReadableMap mRequestHeaders = null;\n    private boolean mSrcIsNetwork = false;\n    private boolean mSrcIsAsset = false;\n    private ScalableType mResizeMode = ScalableType.LEFT_TOP;\n    private boolean mRepeat = false;\n    private boolean mPaused = false;\n    private boolean mMuted = false;\n    private boolean mPreventsDisplaySleepDuringVideoPlayback = true;\n    private float mVolume = 1.0f;\n    private float mStereoPan = 0.0f;\n    private float mProgressUpdateInterval = 250.0f;\n    private float mRate = 1.0f;\n    private float mActiveRate = 1.0f;\n    private long mSeekTime = 0;\n    private boolean mPlayInBackground = false;\n    private boolean mBackgroundPaused = false;\n    private boolean mIsFullscreen = false;\n\n    private int mMainVer = 0;\n    private int mPatchVer = 0;\n\n    private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, paused or completed state.\n\n    private int mVideoDuration = 0;\n    private int mVideoBufferedDuration = 0;\n    private boolean isCompleted = false;\n    private boolean mUseNativeControls = false;\n\n    public ReactVideoView(ThemedReactContext themedReactContext) {\n        super(themedReactContext);\n\n        mThemedReactContext = themedReactContext;\n        mEventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class);\n        themedReactContext.addLifecycleEventListener(this);\n\n        initializeMediaPlayerIfNeeded();\n        setSurfaceTextureListener(this);\n\n        mProgressUpdateRunnable = new Runnable() {\n            @Override\n            public void run() {\n\n                if (mMediaPlayerValid && !isCompleted && !mPaused && !mBackgroundPaused) {\n                    WritableMap event = Arguments.createMap();\n                    event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0);\n                    event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable\n                    event.putDouble(EVENT_PROP_SEEKABLE_DURATION, mVideoDuration / 1000.0);\n                    mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event);\n\n                    // Check for update after an interval\n                    mProgressUpdateHandler.postDelayed(mProgressUpdateRunnable, Math.round(mProgressUpdateInterval));\n                }\n            }\n        };\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (mUseNativeControls) {\n            initializeMediaControllerIfNeeded();\n            mediaController.show();\n        }\n\n        return super.onTouchEvent(event);\n    }\n\n    @Override\n    @SuppressLint(\"DrawAllocation\")\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n\n        if (!changed || !mMediaPlayerValid) {\n            return;\n        }\n\n        int videoWidth = getVideoWidth();\n        int videoHeight = getVideoHeight();\n\n        if (videoWidth == 0 || videoHeight == 0) {\n            return;\n        }\n\n        Size viewSize = new Size(getWidth(), getHeight());\n        Size videoSize = new Size(videoWidth, videoHeight);\n        ScaleManager scaleManager = new ScaleManager(viewSize, videoSize);\n        Matrix matrix = scaleManager.getScaleMatrix(mScalableType);\n        if (matrix != null) {\n            setTransform(matrix);\n        }\n    }\n\n    private void initializeMediaPlayerIfNeeded() {\n        if (mMediaPlayer == null) {\n            mMediaPlayerValid = false;\n            mMediaPlayer = new MediaPlayer();\n            mMediaPlayer.setOnVideoSizeChangedListener(this);\n            mMediaPlayer.setOnErrorListener(this);\n            mMediaPlayer.setOnPreparedListener(this);\n            mMediaPlayer.setOnBufferingUpdateListener(this);\n            mMediaPlayer.setOnSeekCompleteListener(this);\n            mMediaPlayer.setOnCompletionListener(this);\n            mMediaPlayer.setOnInfoListener(this);\n            if (Build.VERSION.SDK_INT >= 23) {\n                mMediaPlayer.setOnTimedMetaDataAvailableListener(new TimedMetaDataAvailableListener());\n            }\n        }\n    }\n\n    private void initializeMediaControllerIfNeeded() {\n        if (mediaController == null) {\n            mediaController = new MediaController(this.getContext());\n        }\n    }\n\n    public void cleanupMediaPlayerResources() {\n        if ( mediaController != null ) {\n            mediaController.hide();\n        }\n        if ( mMediaPlayer != null ) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                mMediaPlayer.setOnTimedMetaDataAvailableListener(null);\n            }\n            mMediaPlayerValid = false;\n            release();\n        }\n        if (mIsFullscreen) {\n            setFullscreen(false);\n        }\n        if (mThemedReactContext != null) {\n            mThemedReactContext.removeLifecycleEventListener(this);\n            mThemedReactContext = null;\n        }\n    }\n\n    public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders) {\n        setSrc(uriString, type, isNetwork, isAsset, requestHeaders, 0, 0);\n    }\n\n    public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders, final int expansionMainVersion, final int expansionPatchVersion) {\n\n        mSrcUriString = uriString;\n        mSrcType = type;\n        mSrcIsNetwork = isNetwork;\n        mSrcIsAsset = isAsset;\n        mRequestHeaders = requestHeaders;\n        mMainVer = expansionMainVersion;\n        mPatchVer = expansionPatchVersion;\n\n\n        mMediaPlayerValid = false;\n        mVideoDuration = 0;\n        mVideoBufferedDuration = 0;\n\n        initializeMediaPlayerIfNeeded();\n        mMediaPlayer.reset();\n\n        try {\n            if (isNetwork) {\n                // Use the shared CookieManager to access the cookies\n                // set by WebViews inside the same app\n                CookieManager cookieManager = CookieManager.getInstance();\n\n                Uri parsedUrl = Uri.parse(uriString);\n                Uri.Builder builtUrl = parsedUrl.buildUpon();\n\n                String cookie = cookieManager.getCookie(builtUrl.build().toString());\n\n                Map<String, String> headers = new HashMap<String, String>();\n\n                if (cookie != null) {\n                    headers.put(\"Cookie\", cookie);\n                }\n\n                if (mRequestHeaders != null) {\n                    headers.putAll(toStringMap(mRequestHeaders));\n                }\n\n                /* According to https://github.com/react-native-community/react-native-video/pull/537\n                 *   there is an issue with this where it can cause a IOException.\n                 * TODO: diagnose this exception and fix it\n                 */\n                setDataSource(mThemedReactContext, parsedUrl, headers);\n            } else if (isAsset) {\n                if (uriString.startsWith(\"content://\")) {\n                    Uri parsedUrl = Uri.parse(uriString);\n                    setDataSource(mThemedReactContext, parsedUrl);\n                } else {\n                    setDataSource(uriString);\n                }\n            } else {\n                ZipResourceFile expansionFile= null;\n                AssetFileDescriptor fd= null;\n                if(mMainVer>0) {\n                    try {\n                        expansionFile = APKExpansionSupport.getAPKExpansionZipFile(mThemedReactContext, mMainVer, mPatchVer);\n                        fd = expansionFile.getAssetFileDescriptor(uriString.replace(\".mp4\",\"\") + \".mp4\");\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    } catch (NullPointerException e) {\n                        e.printStackTrace();\n                    }\n                }\n                if(fd==null) {\n                    int identifier = mThemedReactContext.getResources().getIdentifier(\n                        uriString,\n                        \"drawable\",\n                        mThemedReactContext.getPackageName()\n                    );\n                    if (identifier == 0) {\n                        identifier = mThemedReactContext.getResources().getIdentifier(\n                            uriString,\n                            \"raw\",\n                            mThemedReactContext.getPackageName()\n                        );\n                    }\n                    setRawData(identifier);\n                }\n                else {\n                    setDataSource(fd.getFileDescriptor(), fd.getStartOffset(),fd.getLength());\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n            return;\n        }\n\n        WritableMap src = Arguments.createMap();\n\n        WritableMap wRequestHeaders = Arguments.createMap();\n        wRequestHeaders.merge(mRequestHeaders);\n\n        src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString);\n        src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type);\n        src.putMap(ReactVideoViewManager.PROP_SRC_HEADERS, wRequestHeaders);\n        src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork);\n        if(mMainVer>0) {\n            src.putInt(ReactVideoViewManager.PROP_SRC_MAINVER, mMainVer);\n            if(mPatchVer>0) {\n                src.putInt(ReactVideoViewManager.PROP_SRC_PATCHVER, mPatchVer);\n            }\n        }\n        WritableMap event = Arguments.createMap();\n        event.putMap(ReactVideoViewManager.PROP_SRC, src);\n        mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event);\n        isCompleted = false;\n\n        try {\n          prepareAsync(this);\n        } catch (Exception e) {\n          e.printStackTrace();\n        }\n    }\n\n    public void setResizeModeModifier(final ScalableType resizeMode) {\n        mResizeMode = resizeMode;\n\n        if (mMediaPlayerValid) {\n            setScalableType(resizeMode);\n            invalidate();\n        }\n    }\n\n    public void setRepeatModifier(final boolean repeat) {\n\n        mRepeat = repeat;\n\n        if (mMediaPlayerValid) {\n            setLooping(repeat);\n        }\n    }\n\n    public void setPausedModifier(final boolean paused) {\n        mPaused = paused;\n\n        if (!mMediaPlayerValid) {\n            return;\n        }\n\n        if (mPaused) {\n            if (mMediaPlayer.isPlaying()) {\n                pause();\n            }\n        } else {\n            if (!mMediaPlayer.isPlaying()) {\n                start();\n                // Setting the rate unpauses, so we have to wait for an unpause\n                if (mRate != mActiveRate) { \n                    setRateModifier(mRate);\n                }\n\n                // Also Start the Progress Update Handler\n                mProgressUpdateHandler.post(mProgressUpdateRunnable);\n            }\n        }\n        setKeepScreenOn(!mPaused && mPreventsDisplaySleepDuringVideoPlayback);\n    }\n\n    // reduces the volume based on stereoPan\n    private float calulateRelativeVolume() {\n        float relativeVolume = (mVolume * (1 - Math.abs(mStereoPan)));\n        // only one decimal allowed\n        BigDecimal roundRelativeVolume = new BigDecimal(relativeVolume).setScale(1, BigDecimal.ROUND_HALF_UP);\n        return roundRelativeVolume.floatValue();\n    }\n\n    public void setPreventsDisplaySleepDuringVideoPlaybackModifier(final boolean preventsDisplaySleepDuringVideoPlayback) {\n        mPreventsDisplaySleepDuringVideoPlayback = preventsDisplaySleepDuringVideoPlayback;\n\n        if (!mMediaPlayerValid) {\n            return;\n        }\n\n        mMediaPlayer.setScreenOnWhilePlaying(mPreventsDisplaySleepDuringVideoPlayback);\n        setKeepScreenOn(mPreventsDisplaySleepDuringVideoPlayback);\n    }\n\n    public void setMutedModifier(final boolean muted) {\n        mMuted = muted;\n\n        if (!mMediaPlayerValid) {\n            return;\n        }\n\n        if (mMuted) {\n            setVolume(0, 0);\n        } else if (mStereoPan < 0) {\n            // louder on the left channel\n            setVolume(mVolume, calulateRelativeVolume());\n        } else if (mStereoPan > 0) {\n            // louder on the right channel\n            setVolume(calulateRelativeVolume(), mVolume);\n        } else {\n            // same volume on both channels\n            setVolume(mVolume, mVolume);\n        }\n    }\n\n    public void setVolumeModifier(final float volume) {\n        mVolume = volume;\n        setMutedModifier(mMuted);\n    }\n\n    public void setStereoPan(final float stereoPan) {\n        mStereoPan = stereoPan;\n        setMutedModifier(mMuted);\n    }\n\n    public void setProgressUpdateInterval(final float progressUpdateInterval) {\n        mProgressUpdateInterval = progressUpdateInterval;\n    }\n\n    public void setRateModifier(final float rate) {\n        mRate = rate;\n\n        if (mMediaPlayerValid) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                if (!mPaused) { // Applying the rate while paused will cause the video to start\n                    /* Per https://stackoverflow.com/questions/39442522/setplaybackparams-causes-illegalstateexception\n                     * Some devices throw an IllegalStateException if you set the rate without first calling reset()\n                     * TODO: Call reset() then reinitialize the player\n                     */\n                    try {\n                        mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate));\n                        mActiveRate = rate;\n                    } catch (Exception e) {\n                        Log.e(ReactVideoViewManager.REACT_CLASS, \"Unable to set rate, unsupported on this device\");\n                    }\n                }\n            } else {\n                Log.e(ReactVideoViewManager.REACT_CLASS, \"Setting playback rate is not yet supported on Android versions below 6.0\");\n            }\n        }\n    }\n\n    public void setFullscreen(boolean isFullscreen) {\n        if (isFullscreen == mIsFullscreen) {\n            return; // Avoid generating events when nothing is changing\n        }\n        mIsFullscreen = isFullscreen;\n\n        Activity activity = mThemedReactContext.getCurrentActivity();\n        if (activity == null) {\n            return;\n        }\n        Window window = activity.getWindow();\n        View decorView = window.getDecorView();\n        int uiOptions;\n        if (mIsFullscreen) {\n            if (Build.VERSION.SDK_INT >= 19) { // 4.4+\n                uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION\n                        | SYSTEM_UI_FLAG_IMMERSIVE_STICKY\n                        | SYSTEM_UI_FLAG_FULLSCREEN;\n            } else {\n                uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION\n                        | SYSTEM_UI_FLAG_FULLSCREEN;\n            }\n            mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_WILL_PRESENT.toString(), null);\n            decorView.setSystemUiVisibility(uiOptions);\n            mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_DID_PRESENT.toString(), null);\n        } else {\n            uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;\n            mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_WILL_DISMISS.toString(), null);\n            decorView.setSystemUiVisibility(uiOptions);\n            mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_DID_DISMISS.toString(), null);\n        }\n    }\n\n    public void applyModifiers() {\n        setResizeModeModifier(mResizeMode);\n        setRepeatModifier(mRepeat);\n        setPausedModifier(mPaused);\n        setMutedModifier(mMuted);\n        setPreventsDisplaySleepDuringVideoPlaybackModifier(mPreventsDisplaySleepDuringVideoPlayback);\n        setProgressUpdateInterval(mProgressUpdateInterval);\n        setRateModifier(mRate);\n    }\n\n    public void setPlayInBackground(final boolean playInBackground) {\n\n        mPlayInBackground = playInBackground;\n    }\n\n    public void setControls(boolean controls) {\n        this.mUseNativeControls = controls;\n    }\n\n    @Override\n    public void onPrepared(MediaPlayer mp) {\n\n        mMediaPlayerValid = true;\n        mVideoDuration = mp.getDuration();\n\n        WritableMap naturalSize = Arguments.createMap();\n        naturalSize.putInt(EVENT_PROP_WIDTH, mp.getVideoWidth());\n        naturalSize.putInt(EVENT_PROP_HEIGHT, mp.getVideoHeight());\n        if (mp.getVideoWidth() > mp.getVideoHeight())\n            naturalSize.putString(EVENT_PROP_ORIENTATION, \"landscape\");\n        else\n            naturalSize.putString(EVENT_PROP_ORIENTATION, \"portrait\");\n\n        WritableMap event = Arguments.createMap();\n        event.putDouble(EVENT_PROP_DURATION, mVideoDuration / 1000.0);\n        event.putDouble(EVENT_PROP_CURRENT_TIME, mp.getCurrentPosition() / 1000.0);\n        event.putMap(EVENT_PROP_NATURALSIZE, naturalSize);\n        // TODO: Actually check if you can.\n        event.putBoolean(EVENT_PROP_FAST_FORWARD, true);\n        event.putBoolean(EVENT_PROP_SLOW_FORWARD, true);\n        event.putBoolean(EVENT_PROP_SLOW_REVERSE, true);\n        event.putBoolean(EVENT_PROP_REVERSE, true);\n        event.putBoolean(EVENT_PROP_FAST_FORWARD, true);\n        event.putBoolean(EVENT_PROP_STEP_BACKWARD, true);\n        event.putBoolean(EVENT_PROP_STEP_FORWARD, true);\n        mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD.toString(), event);\n\n        applyModifiers();\n\n        if (mUseNativeControls) {\n            initializeMediaControllerIfNeeded();\n            mediaController.setMediaPlayer(this);\n            mediaController.setAnchorView(this);\n\n            videoControlHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    mediaController.setEnabled(true);\n                    mediaController.show();\n                }\n            });\n        }\n\n        selectTimedMetadataTrack(mp);\n    }\n\n    @Override\n    public boolean onError(MediaPlayer mp, int what, int extra) {\n\n        WritableMap error = Arguments.createMap();\n        error.putInt(EVENT_PROP_WHAT, what);\n        error.putInt(EVENT_PROP_EXTRA, extra);\n        WritableMap event = Arguments.createMap();\n        event.putMap(EVENT_PROP_ERROR, error);\n        mEventEmitter.receiveEvent(getId(), Events.EVENT_ERROR.toString(), event);\n        return true;\n    }\n\n    @Override\n    public boolean onInfo(MediaPlayer mp, int what, int extra) {\n        switch (what) {\n            case MediaPlayer.MEDIA_INFO_BUFFERING_START:\n                mEventEmitter.receiveEvent(getId(), Events.EVENT_STALLED.toString(), Arguments.createMap());\n                break;\n            case MediaPlayer.MEDIA_INFO_BUFFERING_END:\n                mEventEmitter.receiveEvent(getId(), Events.EVENT_RESUME.toString(), Arguments.createMap());\n                break;\n            case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:\n                mEventEmitter.receiveEvent(getId(), Events.EVENT_READY_FOR_DISPLAY.toString(), Arguments.createMap());\n                break;\n\n            default:\n        }\n        return false;\n    }\n\n    @Override\n    public void onBufferingUpdate(MediaPlayer mp, int percent) {\n        selectTimedMetadataTrack(mp);\n        mVideoBufferedDuration = (int) Math.round((double) (mVideoDuration * percent) / 100.0);\n    }\n\n    public void onSeekComplete(MediaPlayer mp) {\n        WritableMap event = Arguments.createMap();\n        event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0);\n        event.putDouble(EVENT_PROP_SEEK_TIME, mSeekTime / 1000.0);\n        mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event);\n        mSeekTime = 0;\n    }\n\n    @Override\n    public void seekTo(int msec) {\n        if (mMediaPlayerValid) {\n            mSeekTime = msec;\n            super.seekTo(msec);\n            if (isCompleted && mVideoDuration != 0 && msec < mVideoDuration) {\n                isCompleted = false;\n            }\n        }\n    }\n\n    @Override\n    public int getBufferPercentage() {\n        return 0;\n    }\n\n    @Override\n    public boolean canPause() {\n        return true;\n    }\n\n    @Override\n    public boolean canSeekBackward() {\n        return true;\n    }\n\n    @Override\n    public boolean canSeekForward() {\n        return true;\n    }\n\n    @Override\n    public int getAudioSessionId() {\n        return 0;\n    }\n\n    @Override\n    public void onCompletion(MediaPlayer mp) {\n        isCompleted = true;\n        mEventEmitter.receiveEvent(getId(), Events.EVENT_END.toString(), null);\n        if (!mRepeat) {\n            setKeepScreenOn(false);\n        }\n    }\n        \n    // This is not fully tested and does not work for all forms of timed metadata\n    @TargetApi(23) // 6.0\n    public class TimedMetaDataAvailableListener\n            implements MediaPlayer.OnTimedMetaDataAvailableListener\n    {\n        public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData data) {\n            WritableMap event = Arguments.createMap();\n\n            try {\n                String rawMeta  = new String(data.getMetaData(), \"UTF-8\");\n                WritableMap id3 = Arguments.createMap();\n\n                id3.putString(EVENT_PROP_METADATA_VALUE, rawMeta.substring(rawMeta.lastIndexOf(\"\\u0003\") + 1));\n                id3.putString(EVENT_PROP_METADATA_IDENTIFIER, \"id3/TDEN\");\n\n                WritableArray metadata = new WritableNativeArray();\n\n                metadata.pushMap(id3);\n\n                event.putArray(EVENT_PROP_METADATA, metadata);\n                event.putDouble(EVENT_PROP_TARGET, getId());\n            } catch (UnsupportedEncodingException e) {\n                e.printStackTrace();\n            }\n\n            mEventEmitter.receiveEvent(getId(), Events.EVENT_TIMED_METADATA.toString(), event);\n        }\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        mMediaPlayerValid = false;\n        super.onDetachedFromWindow();\n        setKeepScreenOn(false);\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n\n        if(mMainVer>0) {\n            setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders, mMainVer, mPatchVer);\n        }\n        else {\n            setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders);\n        }\n        setKeepScreenOn(mPreventsDisplaySleepDuringVideoPlayback);\n    }\n\n    @Override\n    public void onHostPause() {\n        if (mMediaPlayerValid && !mPaused && !mPlayInBackground) {\n            /* Pause the video in background\n             * Don't update the paused prop, developers should be able to update it on background\n             *  so that when you return to the app the video is paused\n             */\n            mBackgroundPaused = true;\n            mMediaPlayer.pause();\n        }\n    }\n\n    @Override\n    public void onHostResume() {\n        mBackgroundPaused = false;\n        if (mMediaPlayerValid && !mPlayInBackground && !mPaused) {\n            new Handler().post(new Runnable() {\n                @Override\n                public void run() {\n                    // Restore original state\n                    setPausedModifier(false);\n                }\n            });\n        }\n    }\n\n    @Override\n    public void onHostDestroy() {\n    }\n\n    /**\n     * toStringMap converts a {@link ReadableMap} into a HashMap.\n     *\n     * @param readableMap The ReadableMap to be conveted.\n     * @return A HashMap containing the data that was in the ReadableMap.\n     * @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java'\n     */\n    public static Map<String, String> toStringMap(@Nullable ReadableMap readableMap) {\n        Map<String, String> result = new HashMap<>();\n        if (readableMap == null)\n            return result;\n\n        com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator();\n        while (iterator.hasNextKey()) {\n            String key = iterator.nextKey();\n            result.put(key, readableMap.getString(key));\n        }\n\n        return result;\n    }\n        \n    // Select track (so we can use it to listen to timed meta data updates)\n    private void selectTimedMetadataTrack(MediaPlayer mp) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            return;\n        }\n        try { // It's possible this could throw an exception if the framework doesn't support getting track info\n            MediaPlayer.TrackInfo[] trackInfo = mp.getTrackInfo();\n            for (int i = 0; i < trackInfo.length; ++i) {\n                if (trackInfo[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {\n                    mp.selectTrack(i);\n                    break;\n                }\n            }\n        } catch (Exception e) {}\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/ReactVideoViewManager.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport com.reactnativecometchatuikit.ReactVideoView.Events;\nimport com.facebook.react.bridge.Arguments;\nimport com.facebook.react.bridge.ReadableMap;\nimport com.facebook.react.bridge.WritableMap;\nimport com.facebook.react.common.MapBuilder;\nimport com.facebook.react.uimanager.annotations.ReactProp;\nimport com.facebook.react.uimanager.SimpleViewManager;\nimport com.facebook.react.uimanager.ThemedReactContext;\nimport com.facebook.react.uimanager.events.RCTEventEmitter;\nimport com.scalablevideoview.ScalableType;\n\nimport javax.annotation.Nullable;\nimport java.util.Map;\n\npublic class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {\n\n    public static final String REACT_CLASS = \"CCRCTVideo\";\n\n    public static final String PROP_SRC = \"src\";\n    public static final String PROP_SRC_URI = \"uri\";\n    public static final String PROP_SRC_TYPE = \"type\";\n    public static final String PROP_SRC_HEADERS = \"requestHeaders\";\n    public static final String PROP_SRC_IS_NETWORK = \"isNetwork\";\n    public static final String PROP_SRC_MAINVER = \"mainVer\";\n    public static final String PROP_SRC_PATCHVER = \"patchVer\";\n    public static final String PROP_SRC_IS_ASSET = \"isAsset\";\n    public static final String PROP_RESIZE_MODE = \"resizeMode\";\n    public static final String PROP_REPEAT = \"repeat\";\n    public static final String PROP_PAUSED = \"paused\";\n    public static final String PROP_MUTED = \"muted\";\n    public static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = \"preventsDisplaySleepDuringVideoPlayback\";\n    public static final String PROP_VOLUME = \"volume\";\n    public static final String PROP_STEREO_PAN = \"stereoPan\";\n    public static final String PROP_PROGRESS_UPDATE_INTERVAL = \"progressUpdateInterval\";\n    public static final String PROP_SEEK = \"seek\";\n    public static final String PROP_RATE = \"rate\";\n    public static final String PROP_FULLSCREEN = \"fullscreen\";\n    public static final String PROP_PLAY_IN_BACKGROUND = \"playInBackground\";\n    public static final String PROP_CONTROLS = \"controls\";\n\n    @Override\n    public String getName() {\n        return REACT_CLASS;\n    }\n\n    @Override\n    protected ReactVideoView createViewInstance(ThemedReactContext themedReactContext) {\n        return new ReactVideoView(themedReactContext);\n    }\n\n    @Override\n    public void onDropViewInstance(ReactVideoView view) {\n        super.onDropViewInstance(view);\n        view.cleanupMediaPlayerResources();\n    }\n\n    @Override\n    @Nullable\n    public Map getExportedCustomDirectEventTypeConstants() {\n        MapBuilder.Builder builder = MapBuilder.builder();\n        for (Events event : Events.values()) {\n            builder.put(event.toString(), MapBuilder.of(\"registrationName\", event.toString()));\n        }\n        return builder.build();\n    }\n\n    @Override\n    @Nullable\n    public Map getExportedViewConstants() {\n        return MapBuilder.of(\n                \"ScaleNone\", Integer.toString(ScalableType.LEFT_TOP.ordinal()),\n                \"ScaleToFill\", Integer.toString(ScalableType.FIT_XY.ordinal()),\n                \"ScaleAspectFit\", Integer.toString(ScalableType.FIT_CENTER.ordinal()),\n                \"ScaleAspectFill\", Integer.toString(ScalableType.CENTER_CROP.ordinal())\n        );\n    }\n\n    @ReactProp(name = PROP_SRC)\n    public void setSrc(final ReactVideoView videoView, @Nullable ReadableMap src) {\n        int mainVer = src.getInt(PROP_SRC_MAINVER);\n        int patchVer = src.getInt(PROP_SRC_PATCHVER);\n        if(mainVer<0) { mainVer = 0; }\n        if(patchVer<0) { patchVer = 0; }\n        if(mainVer>0) {\n            videoView.setSrc(\n                    src.getString(PROP_SRC_URI),\n                    src.getString(PROP_SRC_TYPE),\n                    src.getBoolean(PROP_SRC_IS_NETWORK),\n                    src.getBoolean(PROP_SRC_IS_ASSET),\n                    src.getMap(PROP_SRC_HEADERS),\n                    mainVer,\n                    patchVer\n            );\n        }\n        else {\n            videoView.setSrc(\n                    src.getString(PROP_SRC_URI),\n                    src.getString(PROP_SRC_TYPE),\n                    src.getBoolean(PROP_SRC_IS_NETWORK),\n                    src.getBoolean(PROP_SRC_IS_ASSET),\n                    src.getMap(PROP_SRC_HEADERS)\n                    );\n        }\n    }\n\n    @ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK)\n    public void setPropPreventsDisplaySleepDuringVideoPlayback(final ReactVideoView videoView, final boolean doPreventSleep) {\n        videoView.setPreventsDisplaySleepDuringVideoPlaybackModifier(doPreventSleep);\n    }\n\n    @ReactProp(name = PROP_RESIZE_MODE)\n    public void setResizeMode(final ReactVideoView videoView, final String resizeModeOrdinalString) {\n        videoView.setResizeModeModifier(ScalableType.values()[Integer.parseInt(resizeModeOrdinalString)]);\n    }\n\n    @ReactProp(name = PROP_REPEAT, defaultBoolean = false)\n    public void setRepeat(final ReactVideoView videoView, final boolean repeat) {\n        videoView.setRepeatModifier(repeat);\n    }\n\n    @ReactProp(name = PROP_PAUSED, defaultBoolean = false)\n    public void setPaused(final ReactVideoView videoView, final boolean paused) {\n        videoView.setPausedModifier(paused);\n    }\n\n    @ReactProp(name = PROP_MUTED, defaultBoolean = false)\n    public void setMuted(final ReactVideoView videoView, final boolean muted) {\n        videoView.setMutedModifier(muted);\n    }\n\n    @ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f)\n    public void setVolume(final ReactVideoView videoView, final float volume) {\n        videoView.setVolumeModifier(volume);\n    }\n\n    @ReactProp(name = PROP_STEREO_PAN)\n    public void setStereoPan(final ReactVideoView videoView, final float stereoPan) {\n        videoView.setStereoPan(stereoPan);\n    }\n\n    @ReactProp(name = PROP_PROGRESS_UPDATE_INTERVAL, defaultFloat = 250.0f)\n    public void setProgressUpdateInterval(final ReactVideoView videoView, final float progressUpdateInterval) {\n        videoView.setProgressUpdateInterval(progressUpdateInterval);\n    }\n\n    @ReactProp(name = PROP_SEEK)\n    public void setSeek(final ReactVideoView videoView, final float seek) {\n        videoView.seekTo(Math.round(seek * 1000.0f));\n    }\n\n    @ReactProp(name = PROP_RATE)\n    public void setRate(final ReactVideoView videoView, final float rate) {\n        videoView.setRateModifier(rate);\n    }\n\n    @ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false)\n    public void setFullscreen(final ReactVideoView videoView, final boolean fullscreen) {\n        videoView.setFullscreen(fullscreen);\n    }\n\n    @ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false)\n    public void setPlayInBackground(final ReactVideoView videoView, final boolean playInBackground) {\n        videoView.setPlayInBackground(playInBackground);\n    }\n\n    @ReactProp(name = PROP_CONTROLS, defaultBoolean = false)\n    public void setControls(final ReactVideoView videoView, final boolean controls) {\n        videoView.setControls(controls);\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/RichTextEditorPackage.kt",
    "content": "package com.reactnativecometchatuikit\n\nimport com.facebook.react.TurboReactPackage\nimport com.facebook.react.bridge.NativeModule\nimport com.facebook.react.bridge.ReactApplicationContext\nimport com.facebook.react.module.model.ReactModuleInfo\nimport com.facebook.react.module.model.ReactModuleInfoProvider\nimport com.facebook.react.uimanager.ViewManager\n\nclass RichTextEditorPackage : TurboReactPackage() {\n    override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {\n        return null\n    }\n\n    override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {\n        return ReactModuleInfoProvider {\n            mapOf()\n        }\n    }\n\n    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {\n        return listOf(RichTextEditorViewManager())\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/RichTextEditorView.kt",
    "content": "package com.reactnativecometchatuikit\n\nimport android.content.ClipboardManager\nimport android.content.ClipData\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Typeface\nimport android.graphics.drawable.GradientDrawable\nimport android.text.Editable\nimport android.text.Html\nimport android.text.SpannableStringBuilder\nimport android.text.Spanned\nimport android.text.TextWatcher\nimport android.text.Layout\nimport android.text.method.ScrollingMovementMethod\nimport android.text.style.*\nimport android.view.GestureDetector\nimport android.view.Gravity\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport android.view.inputmethod.EditorInfo\nimport android.widget.PopupWindow\nimport android.widget.FrameLayout\nimport android.content.res.Configuration\nimport android.app.AlertDialog\nimport android.widget.EditText\nimport android.widget.LinearLayout\nimport com.facebook.react.bridge.Arguments\nimport com.facebook.react.bridge.ReactContext\nimport com.facebook.react.bridge.WritableArray\nimport com.facebook.react.bridge.WritableMap\nimport com.facebook.react.uimanager.UIManagerHelper\nimport com.facebook.react.uimanager.events.Event\nimport com.facebook.react.uimanager.events.RCTEventEmitter\n\nclass CodeBlockBorderSpan(initDensity: Float = 1f) : LineBackgroundSpan {\n    companion object {\n        const val CORNER_RADIUS = 6f  // 6dp — structured editor feel, not bubble-like\n        const val PADDING = 12f  // 12dp per Figma padding_3\n        const val VERTICAL_PADDING = 10f  // 10dp vertical breathing room above/below text\n        const val CODE_BLOCK_FONT_SIZE = 16  // sp — matches default text size for consistent cursor height\n\n        // Per Figma: light #FAFAFA bg, #E8E8E8 border; dark subtle white overlay\n        fun borderColor(isDark: Boolean): Int =\n            if (isDark) Color.parseColor(\"#33FFFFFF\") else Color.parseColor(\"#E8E8E8\")\n\n        fun bgColor(isDark: Boolean): Int =\n            if (isDark) Color.parseColor(\"#1AFFFFFF\") else Color.parseColor(\"#FAFAFA\")\n\n        fun inlineCodeBg(isDark: Boolean): Int =\n            if (isDark) Color.parseColor(\"#33FFFFFF\") else Color.parseColor(\"#1A000000\")\n\n        // Pre-allocated Paint for container drawing (shared across all instances;\n        // each drawBackground call configures it before use)\n        val containerPaint = Paint().apply { isAntiAlias = true }\n    }\n\n    // Region-awareness flags — set by applyListIndentation().\n    var isFirstLine = false\n    var isLastLine = false\n    // Density for dp→px conversion — initialized from constructor, updated by applyListIndentation()\n    var density = initDensity\n    // Dark mode flag, set by applyListIndentation()\n    var isDark = false\n\n    override fun drawBackground(\n        canvas: Canvas, paint: Paint,\n        left: Int, right: Int, top: Int, baseline: Int, bottom: Int,\n        text: CharSequence, start: Int, end: Int, lineNumber: Int\n    ) {\n        // No-op: container drawing is handled centrally in RichTextEditorView.onDraw()\n        // using a unified rect per code block region (matching iOS approach).\n    }\n}\n\n/**\n * Marker span for inline code. Drawing is handled in RichTextEditorView.onDraw()\n * for tight-fitting bordered containers around glyph bounds (same pattern as CodeBlockBorderSpan).\n */\nclass InlineCodeSpan : CharacterStyle() {\n    override fun updateDrawState(tp: android.text.TextPaint?) {\n        // No-op: visual rendering is handled by custom onDraw() in RichTextEditorView\n    }\n}\n\n/**\n * Marker span for mentions. Stores uid and displayName so that mention metadata\n * can be preserved across code block toggle cycles (Req 19.1, 19.2, 19.3).\n */\nclass MentionSpan(val uid: String, val displayName: String) : CharacterStyle() {\n    override fun updateDrawState(tp: android.text.TextPaint?) {\n        // No-op: mention styling is handled by the JS layer\n    }\n}\n\n/**\n * Custom URLSpan that suppresses the default underline drawn by ClickableSpan.\n * Links are visually distinguished by ForegroundColorSpan (blue) only, matching iOS parity.\n */\nclass NoUnderlineURLSpan(url: String) : URLSpan(url) {\n    override fun updateDrawState(ds: android.text.TextPaint) {\n        super.updateDrawState(ds)\n        ds.isUnderlineText = false\n    }\n}\n\nclass RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompatEditText(context) {\n\n    private var placeholder: String = \"\"\n    private var maxHeightValue: Int = 0\n    private var numberOfLinesValue: Int = 0\n    private var showToolbar: Boolean = true\n    private var variant: String = \"outlined\"\n    private var density: Float = 1f\n    private var isInternalChange = false\n    private var lastUserEditTime: Long = 0L\n    private var lastReportedHeight: Float = 0f\n    private var calculatedHeight: Float = 0f\n    private var minHeightPx: Float = 0f\n    private var isInitialized = false\n\n    private var previousText: String = \"\"\n    private var pendingDelta: Map<String, Any>? = null\n    private var pendingPrefixDeletion: Pair<Int, Int>? = null // (lineStart, prefixLength) for backspace-in-prefix\n    // Saved selected text for URL-paste-over-selection detection in TextWatcher\n    private var savedSelectedTextForPaste: String? = null\n\n    /// When true, the ``` shortcut in detectBlockMarkdownShortcut is suppressed.\n    /// Set after a code-block shortcut fires; cleared when the user types a\n    /// non-backtick character or the text becomes empty. This prevents stale\n    /// backtick spans/characters from re-triggering code block mode.\n    private var suppressCodeBlockShortcut = false\n\n    // For flat variant bottom border\n    private val bottomBorderPaint = Paint().apply {\n        color = Color.parseColor(\"#E0E0E0\")\n        strokeWidth = 1f\n        style = Paint.Style.STROKE\n    }\n    private var drawBottomBorder = false\n\n    // Pre-allocated paint for code block / inline code container drawing (avoids GC in onDraw)\n    private val codeBlockPaint = Paint().apply { isAntiAlias = true }\n\n    // Stored mention metadata for code block toggle round-trip (Req 19.1, 19.2, 19.3)\n    data class StoredMention(\n        val displayName: String,\n        val uid: String,\n        val originalRange: IntRange\n    )\n    private val storedMentionMetadata = mutableMapOf<String, StoredMention>()\n\n    /// Produces a stable map key for an IntRange.\n    private fun rangeKey(range: IntRange): String = \"${range.first}_${range.last}\"\n\n    // Theme-derived colors for inline code container drawing (set via React props)\n    var inlineCodeBackgroundColor: Int? = null\n    var inlineCodeBorderColor: Int? = null\n    var inlineCodeTextColor: Int? = null\n    var inlineCodeFontSize: Float? = null\n\n    // Undo/Redo stacks\n    private val undoStack = mutableListOf<CharSequence>()\n    private val redoStack = mutableListOf<CharSequence>()\n    private var lastSavedText: CharSequence = \"\"\n\n    private fun isDarkMode(): Boolean {\n        val nightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK\n        return nightMode == Configuration.UI_MODE_NIGHT_YES\n    }\n    private var toolbarPopup: PopupWindow? = null\n    private var toolbarOptions: List<String>? = null\n\n    // Pending styles for type-ahead formatting (Slack-style)\n    private val pendingStyles = mutableSetOf<String>()\n    private val explicitlyOffStyles = mutableSetOf<String>()  // Styles user explicitly turned off at cursor\n    private var pendingStylesInsertPos = -1\n\n    // Store selection for toolbar actions (selection might be lost when clicking toolbar)\n    private var savedSelectionStart: Int = 0\n    private var savedSelectionEnd: Int = 0\n\n    // Pre-compiled regex for numbered list prefix detection in applyListIndentation (ENG-31433)\n    private val numberedPrefixRegex = Regex(\"^(\\\\d+)\\\\.\\\\s\")\n    private val quoteNumberedPrefixRegex = Regex(\"^▎ (\\\\d+)\\\\.\\\\s\")\n\n    // Reference to parent container for event routing (set by ViewManager)\n    var containerView: android.view.View? = null\n\n    // Enter key behavior: \"newLine\" (default) or \"sendMessage\" (Android only)\n    var enterKeyBehavior: String = \"newLine\"\n\n    // When true, Bold/Italic/Underline/Strikethrough appear in the text selection context menu\n    var showTextSelectionMenuItems: Boolean = true\n        set(value) {\n            field = value\n            // Re-install or remove the custom selection action mode callback\n            updateSelectionActionModeCallback()\n        }\n\n    // Gesture detector for double-tap word selection\n    private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {\n        override fun onDoubleTap(e: MotionEvent): Boolean {\n            selectWordAtPosition(e.x, e.y)\n            return true\n        }\n\n        override fun onSingleTapUp(e: MotionEvent): Boolean {\n            // Check if tap landed on a link span — intercept and emit to RN\n            val offset = getOffsetForPosition(e.x, e.y)\n            val spanned = text as? Spanned\n            if (spanned != null && offset >= 0 && offset < spanned.length) {\n                val urlSpans = spanned.getSpans(offset, offset, URLSpan::class.java)\n                if (urlSpans.isNotEmpty()) {\n                    val span = urlSpans[0]\n                    val start = spanned.getSpanStart(span)\n                    val end = spanned.getSpanEnd(span)\n                    val linkText = spanned.subSequence(start, end).toString()\n                    emitLinkTapEvent(span.url, linkText, start, end - start)\n                    return true\n                }\n            }\n            return false\n        }\n    })\n\n    init {\n        density = context.resources.displayMetrics.density\n        minHeightPx = 64 * density\n        calculatedHeight = minHeightPx\n        bottomBorderPaint.strokeWidth = density\n\n        val paddingHorizontal = (12 * density).toInt()\n        val paddingVertical = (0 * density).toInt()\n\n        setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)\n        textSize = 16f\n        setLineSpacing(0f, 1.3f)  // Consistent line height multiplier\n        setTextColor(Color.BLACK)\n        setHintTextColor(Color.parseColor(\"#9E9E9E\"))\n        gravity = Gravity.TOP or Gravity.START\n        isFocusable = true\n        isFocusableInTouchMode = true\n        inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE\n\n        // Disable vertical scrolling by default\n        isVerticalScrollBarEnabled = false\n\n        // Use simple (greedy) line breaking so that text after list markers\n        // fills the first line as much as possible. The default HIGH_QUALITY\n        // strategy may wrap entire words to the next line, orphaning the\n        // marker (e.g. \"1.\") on its own line. SIMPLE strategy greedily fills\n        // each line, keeping the marker and content together.\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {\n            breakStrategy = android.text.Layout.BREAK_STRATEGY_SIMPLE\n        }\n\n        // Set transparent background by default\n        setBackgroundColor(Color.TRANSPARENT)\n\n        // Default outlined style\n        applyVariantStyle()\n\n        // Setup toolbar\n        setupToolbar()\n\n        addTextChangedListener(object : TextWatcher {\n            private var changeStart = 0\n            private var removedCount = 0\n            private var addedCount = 0\n\n            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {\n                changeStart = start\n                removedCount = count\n                addedCount = after\n                // Save selected text for URL-paste-over-selection detection.\n                // When count > 0 and after > 0, it's a replace (paste over selection).\n                if (count > 0 && after > 0 && s != null && start + count <= s.length) {\n                    savedSelectedTextForPaste = s.subSequence(start, start + count).toString()\n                } else {\n                    savedSelectedTextForPaste = null\n                }\n                detectBackspaceInListPrefix(s, start, count, after)\n            }\n\n            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {\n                // Capture delta information\n                if (!isInternalChange && s != null) {\n                    val newText = s.toString()\n                    val deltaMap = mutableMapOf<String, Any>()\n\n                    when {\n                        removedCount == 0 && addedCount > 0 -> {\n                            // Insert\n                            deltaMap[\"type\"] = \"insert\"\n                            deltaMap[\"position\"] = changeStart\n                            deltaMap[\"text\"] = newText.substring(start, start + count)\n                        }\n                        removedCount > 0 && addedCount == 0 -> {\n                            // Delete\n                            deltaMap[\"type\"] = \"delete\"\n                            deltaMap[\"position\"] = changeStart\n                            deltaMap[\"length\"] = removedCount\n                        }\n                        removedCount > 0 && addedCount > 0 -> {\n                            // Replace\n                            deltaMap[\"type\"] = \"replace\"\n                            deltaMap[\"position\"] = changeStart\n                            deltaMap[\"length\"] = removedCount\n                            deltaMap[\"text\"] = newText.substring(start, start + count)\n                        }\n                    }\n\n                    if (deltaMap.isNotEmpty()) {\n                        pendingDelta = deltaMap\n                    }\n                }\n            }\n\n            override fun afterTextChanged(s: Editable?) {\n                if (!isInternalChange) {\n                    // enterKeyBehavior=\"sendMessage\": intercept Enter key press.\n                    // When a single newline is inserted (not part of a paste or list continuation),\n                    // remove it and emit a send request event instead.\n                    if (enterKeyBehavior == \"sendMessage\" && addedCount == 1 && removedCount == 0 && s != null) {\n                        val insertedChar = s.subSequence(changeStart, (changeStart + 1).coerceAtMost(s.length)).toString()\n                        if (insertedChar == \"\\n\") {\n                            isInternalChange = true\n                            s.delete(changeStart, changeStart + 1)\n                            isInternalChange = false\n                            emitSendRequestEvent()\n                            return\n                        }\n                    }\n\n                    // Track when user last typed — used to ignore stale text prop updates\n                    lastUserEditTime = System.currentTimeMillis()\n\n                    // Clear the code-block shortcut suppression when the user types\n                    // a non-backtick character, proving they've moved past the stale\n                    // backtick state. Also clear when text becomes empty.\n                    if (suppressCodeBlockShortcut && s != null) {\n                        if (s.isEmpty()) {\n                            suppressCodeBlockShortcut = false\n                        } else if (addedCount > 0 && removedCount == 0) {\n                            val inserted = s.subSequence(changeStart, (changeStart + addedCount).coerceAtMost(s.length)).toString()\n                            if (inserted.any { it != '`' }) {\n                                suppressCodeBlockShortcut = false\n                            }\n                        }\n                    }\n\n                    // Apply pending styles to newly typed text\n                    if (addedCount > 0 && removedCount == 0 && s != null) {\n                        val spanStart = changeStart\n                        val spanEnd = changeStart + addedCount\n                        if (spanEnd <= s.length) {\n                            isInternalChange = true\n\n                            // Apply pending ON styles\n                            if (pendingStyles.isNotEmpty()) {\n                                for (style in pendingStyles) {\n                                    when (style) {\n                                        \"bold\" -> s.setSpan(StyleSpan(Typeface.BOLD), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                        \"italic\" -> s.setSpan(StyleSpan(Typeface.ITALIC), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                        \"underline\" -> s.setSpan(UnderlineSpan(), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                        \"strikethrough\" -> s.setSpan(StrikethroughSpan(), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                        \"code\" -> {\n                                            val fontSize = inlineCodeFontSize ?: 12f\n                                            s.setSpan(AbsoluteSizeSpan(fontSize.toInt(), true), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                            s.setSpan(InlineCodeSpan(), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                            val codeColor = inlineCodeTextColor ?: Color.parseColor(\"#6852D6\")\n                                            s.setSpan(ForegroundColorSpan(codeColor), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                        }\n                                        \"codeBlock\" -> {\n                                            s.setSpan(TypefaceSpan(\"monospace\"), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                            // Apply dark-mode-aware text color for readability\n                                            val cbTextColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n                                            s.setSpan(ForegroundColorSpan(cbTextColor), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                            // Extend existing CodeBlockBorderSpan or create new one\n                                            val existingBorderSpans = s.getSpans(0, s.length, CodeBlockBorderSpan::class.java)\n                                            var extended = false\n                                            for (bs in existingBorderSpans) {\n                                                val bsEnd = s.getSpanEnd(bs)\n                                                // If the new text is adjacent to an existing code block span, extend it\n                                                if (bsEnd == spanStart || bsEnd == spanStart - 1) {\n                                                    val bsStart = s.getSpanStart(bs)\n                                                    s.removeSpan(bs)\n                                                    s.setSpan(CodeBlockBorderSpan(density), bsStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                                    extended = true\n                                                    break\n                                                }\n                                            }\n                                            if (!extended) {\n                                                s.setSpan(CodeBlockBorderSpan(density), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n\n                            // Remove explicitly-off styles from newly typed text\n                            if (explicitlyOffStyles.isNotEmpty()) {\n                                for (offStyle in explicitlyOffStyles) {\n                                    when (offStyle) {\n                                        \"bold\" -> s.getSpans(spanStart, spanEnd, StyleSpan::class.java)\n                                            .filter { it.style == Typeface.BOLD }\n                                            .forEach { span ->\n                                                val ss = s.getSpanStart(span)\n                                                val se = s.getSpanEnd(span)\n                                                s.removeSpan(span)\n                                                // Re-apply to part before new text only\n                                                if (ss < spanStart) s.setSpan(StyleSpan(Typeface.BOLD), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                // Re-apply to part after new text only\n                                                if (se > spanEnd) s.setSpan(StyleSpan(Typeface.BOLD), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                            }\n                                        \"italic\" -> s.getSpans(spanStart, spanEnd, StyleSpan::class.java)\n                                            .filter { it.style == Typeface.ITALIC }\n                                            .forEach { span ->\n                                                val ss = s.getSpanStart(span)\n                                                val se = s.getSpanEnd(span)\n                                                s.removeSpan(span)\n                                                if (ss < spanStart) s.setSpan(StyleSpan(Typeface.ITALIC), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                if (se > spanEnd) s.setSpan(StyleSpan(Typeface.ITALIC), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                            }\n                                        \"underline\" -> s.getSpans(spanStart, spanEnd, UnderlineSpan::class.java)\n                                            .forEach { span ->\n                                                val ss = s.getSpanStart(span)\n                                                val se = s.getSpanEnd(span)\n                                                s.removeSpan(span)\n                                                if (ss < spanStart) s.setSpan(UnderlineSpan(), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                if (se > spanEnd) s.setSpan(UnderlineSpan(), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                            }\n                                        \"strikethrough\" -> s.getSpans(spanStart, spanEnd, StrikethroughSpan::class.java)\n                                            .forEach { span ->\n                                                val ss = s.getSpanStart(span)\n                                                val se = s.getSpanEnd(span)\n                                                s.removeSpan(span)\n                                                if (ss < spanStart) s.setSpan(StrikethroughSpan(), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                if (se > spanEnd) s.setSpan(StrikethroughSpan(), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                            }\n                                        \"code\" -> {\n                                            s.getSpans(spanStart, spanEnd, InlineCodeSpan::class.java)\n                                                .forEach { span ->\n                                                    val ss = s.getSpanStart(span)\n                                                    val se = s.getSpanEnd(span)\n                                                    s.removeSpan(span)\n                                                    if (ss < spanStart) s.setSpan(InlineCodeSpan(), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                    if (se > spanEnd) s.setSpan(InlineCodeSpan(), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                }\n                                            s.getSpans(spanStart, spanEnd, ForegroundColorSpan::class.java)\n                                                .forEach { span ->\n                                                    val ss = s.getSpanStart(span)\n                                                    val se = s.getSpanEnd(span)\n                                                    s.removeSpan(span)\n                                                    if (ss < spanStart) s.setSpan(ForegroundColorSpan(span.foregroundColor), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                    if (se > spanEnd) s.setSpan(ForegroundColorSpan(span.foregroundColor), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                }\n                                            s.getSpans(spanStart, spanEnd, AbsoluteSizeSpan::class.java)\n                                                .forEach { span ->\n                                                    val ss = s.getSpanStart(span)\n                                                    val se = s.getSpanEnd(span)\n                                                    s.removeSpan(span)\n                                                    if (ss < spanStart) s.setSpan(AbsoluteSizeSpan(span.size, true), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                    if (se > spanEnd) s.setSpan(AbsoluteSizeSpan(span.size, true), spanEnd, se, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                                }\n                                        }\n                                    }\n                                }\n                            }\n\n                            isInternalChange = false\n                        }\n                    }\n\n                    // Detect markdown syntax in pasted text and convert to styled\n                    // spannable. This catches pastes that bypass onTextContextMenuItem\n                    // (e.g., system clipboard paste, IME paste).\n                    if (addedCount > 1 && removedCount == 0 && s != null) {\n                        val insertedStart = changeStart\n                        val insertedEnd = (changeStart + addedCount).coerceAtMost(s.length)\n                        val insertedText = s.subSequence(insertedStart, insertedEnd).toString()\n                        if (looksLikeMarkdown(insertedText)) {\n                            // Check that the inserted text doesn't already have styled spans\n                            // (would mean it came through onTextContextMenuItem or blocksToSpannable)\n                            val existingUrlSpans = s.getSpans(insertedStart, insertedEnd, URLSpan::class.java)\n                            val existingStyleSpans = s.getSpans(insertedStart, insertedEnd, StyleSpan::class.java)\n                            if (existingUrlSpans.isEmpty() && existingStyleSpans.isEmpty()) {\n                                isInternalChange = true\n                                val result = markdownToSpannable(insertedText)\n                                s.replace(insertedStart, insertedEnd, result)\n                                isInternalChange = false\n                            }\n                        }\n                    }\n\n                    // URL-paste-over-selection fallback: catches pastes that bypass\n                    // onTextContextMenuItem (e.g., system clipboard, IME paste).\n                    // When text was replaced (removedCount > 0 && addedCount > 0)\n                    // and the inserted text is a URL, undo the replace and create\n                    // a styled hyperlink using the original selected text.\n                    if (addedCount > 0 && removedCount > 0 && s != null && savedSelectedTextForPaste != null) {\n                        val insertedStart = changeStart\n                        val insertedEnd = (changeStart + addedCount).coerceAtMost(s.length)\n                        val insertedText = s.subSequence(insertedStart, insertedEnd).toString()\n                        // Check if inserted text is a URL and doesn't already have link spans\n                        // (link spans would mean onTextContextMenuItem already handled it)\n                        val existingUrlSpans = s.getSpans(insertedStart, insertedEnd, URLSpan::class.java)\n                        if (isURL(insertedText) && existingUrlSpans.isEmpty()) {\n                            val selectedText = savedSelectedTextForPaste!!\n                            val trimmedURL = insertedText.trim()\n                            isInternalChange = true\n                            // Remove the pasted URL text\n                            s.delete(insertedStart, insertedEnd)\n                            // Insert styled link with original selected text as display\n                            val normalizedUrl = if (!trimmedURL.lowercase().startsWith(\"http://\") &&\n                                !trimmedURL.lowercase().startsWith(\"https://\") &&\n                                !trimmedURL.lowercase().startsWith(\"mailto:\") &&\n                                !trimmedURL.lowercase().startsWith(\"tel:\")) {\n                                \"https://$trimmedURL\"\n                            } else trimmedURL\n                            val linkSpannable = SpannableStringBuilder(selectedText)\n                            linkSpannable.setSpan(NoUnderlineURLSpan(normalizedUrl), 0, selectedText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                            linkSpannable.setSpan(ForegroundColorSpan(Color.parseColor(\"#2196F3\")), 0, selectedText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                            s.insert(insertedStart, linkSpannable)\n                            isInternalChange = false\n                            savedSelectedTextForPaste = null\n                            sendContentChange()\n                        }\n                    }\n\n                    // Split InlineCodeSpan at newline boundaries so the container\n                    // does not visually extend across line breaks in the composer.\n                    if (addedCount > 0 && s != null) {\n                        splitInlineCodeSpansAtNewlines(s)\n                    }\n                    // When Enter is pressed while inline code is active, the split\n                    // removes the span from the newline so the new line has nothing.\n                    // Re-add \"code\" to pendingStyles so subsequent typing gets styled.\n                    if (addedCount > 0 && s != null) {\n                        val inserted = s.subSequence(changeStart, (changeStart + addedCount).coerceAtMost(s.length)).toString()\n                        if (inserted.contains(\"\\n\")) {\n                            // Check if char before the newline had InlineCodeSpan\n                            val nlPos = changeStart + inserted.indexOf('\\n')\n                            if (nlPos > 0 && s.getSpans(nlPos - 1, nlPos, InlineCodeSpan::class.java).isNotEmpty()) {\n                                if (!explicitlyOffStyles.contains(\"code\")) {\n                                    pendingStyles.add(\"code\")\n                                    pendingStylesInsertPos = selectionStart\n                                }\n                            }\n                        }\n                    }\n                    var handled = handleBackspaceInListPrefix(s)\n                    if (!handled) {\n                        handled = autoContinueListOnEnter(s)\n                    }\n                    if (!handled) {\n                        handled = checkTripleEnterExitCodeBlock(s)\n                    }\n                    if (!handled) {\n                        isInternalChange = true\n                        renumberNumberedLists()\n                        isInternalChange = false\n                    }\n                    // Apply hanging indent for lines with list/blockquote prefixes (ENG-31433)\n                    applyListIndentation()\n                    // Issue 1: When text becomes empty, reset all pending/explicit style toggles\n                    if (s != null && s.isEmpty()) {\n                        pendingStyles.clear()\n                        explicitlyOffStyles.clear()\n                        pendingStylesInsertPos = -1\n                        suppressCodeBlockShortcut = false\n                        // Also remove all zero-length style spans that survive in the empty Editable.\n                        // SPAN_EXCLUSIVE_INCLUSIVE spans shrink to zero-length but still expand when\n                        // new text is typed, causing ghost bold/italic/underline/strikethrough.\n                        s.getSpans(0, 0, StyleSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, UnderlineSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, StrikethroughSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, InlineCodeSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, ForegroundColorSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, AbsoluteSizeSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, CodeBlockBorderSpan::class.java).forEach { s.removeSpan(it) }\n                        s.getSpans(0, 0, TypefaceSpan::class.java).forEach { s.removeSpan(it) }\n                    }\n\n                    // Issue 2: Clean up zero-length spans left behind after backspace\n                    // These ghost spans cause the toolbar to show active state incorrectly\n                    if (removedCount > 0 && s != null && s.isNotEmpty()) {\n                        val cursorPos = selectionStart.coerceIn(0, s.length)\n                        // Check for zero-length style spans at cursor\n                        s.getSpans(cursorPos, cursorPos, StyleSpan::class.java).forEach { span ->\n                            if (s.getSpanStart(span) == s.getSpanEnd(span)) {\n                                s.removeSpan(span)\n                            }\n                        }\n                        s.getSpans(cursorPos, cursorPos, UnderlineSpan::class.java).forEach { span ->\n                            if (s.getSpanStart(span) == s.getSpanEnd(span)) {\n                                s.removeSpan(span)\n                            }\n                        }\n                        s.getSpans(cursorPos, cursorPos, StrikethroughSpan::class.java).forEach { span ->\n                            if (s.getSpanStart(span) == s.getSpanEnd(span)) {\n                                s.removeSpan(span)\n                            }\n                        }\n                    }\n\n                    // Detect markdown shortcuts (e.g., **text** → bold) and convert to\n                    // live formatting. Skip if inside a code block (Req 20.6).\n                    detectMarkdownShortcut(s)\n\n                    // Issue 3: Prevent link span bleed — when typing immediately after\n                    // a link, URLSpan/ForegroundColorSpan/UnderlineSpan from the link\n                    // extend into the newly typed characters. Strip them so new text\n                    // appears unstyled (Req 18.4).\n                    if (addedCount > 0 && removedCount == 0 && s != null && s.isNotEmpty()) {\n                        val spanStart = changeStart\n                        val spanEnd = (changeStart + addedCount).coerceAtMost(s.length)\n                        if (spanEnd > spanStart) {\n                            isInternalChange = true\n                            s.getSpans(spanStart, spanEnd, URLSpan::class.java).forEach { span ->\n                                val ss = s.getSpanStart(span)\n                                val se = s.getSpanEnd(span)\n                                // Only trim if the span was extended into the new text\n                                // (original span ended at or before the insert position)\n                                if (ss < spanStart && se >= spanEnd) {\n                                    s.removeSpan(span)\n                                    s.setSpan(span, ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                }\n                            }\n                            s.getSpans(spanStart, spanEnd, ForegroundColorSpan::class.java).forEach { span ->\n                                val ss = s.getSpanStart(span)\n                                val se = s.getSpanEnd(span)\n                                if (ss < spanStart && se >= spanEnd) {\n                                    // Only trim color spans that co-occur with a URLSpan (link color)\n                                    val hasUrl = s.getSpans(ss, spanStart, URLSpan::class.java).isNotEmpty()\n                                    if (hasUrl) {\n                                        s.removeSpan(span)\n                                        s.setSpan(ForegroundColorSpan(span.foregroundColor), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                    }\n                                }\n                            }\n                            s.getSpans(spanStart, spanEnd, UnderlineSpan::class.java).forEach { span ->\n                                val ss = s.getSpanStart(span)\n                                val se = s.getSpanEnd(span)\n                                if (ss < spanStart && se >= spanEnd) {\n                                    val hasUrl = s.getSpans(ss, spanStart, URLSpan::class.java).isNotEmpty()\n                                    if (hasUrl) {\n                                        s.removeSpan(span)\n                                        s.setSpan(UnderlineSpan(), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                    }\n                                }\n                            }\n                            isInternalChange = false\n                        }\n                    }\n\n                    // Before bleed prevention, check if the newly typed text\n                    // landed inside an existing CodeBlockBorderSpan. If so, the\n                    // user IS typing inside a code block and pendingStyles just\n                    // hasn't been updated yet (onSelectionChanged fires after\n                    // afterTextChanged). Add \"codeBlock\" to pendingStyles so the\n                    // bleed prevention below correctly skips trimming.\n                    //\n                    // IMPORTANT: check the actual insertion point (changeStart),\n                    // not changeStart-1. After triple-enter exit the span uses\n                    // SPAN_EXCLUSIVE_EXCLUSIVE and ends right before changeStart,\n                    // so checking changeStart-1 would falsely detect the cursor\n                    // as inside the code block. Checking changeStart directly\n                    // only matches when the span truly covers the new text\n                    // (i.e. SPAN_EXCLUSIVE_INCLUSIVE auto-extended it).\n                    if (addedCount > 0 && s != null && s.isNotEmpty()\n                        && !pendingStyles.contains(\"codeBlock\")) {\n                        val insertEnd = (changeStart + addedCount).coerceAtMost(s.length)\n                        if (insertEnd > changeStart) {\n                            val cbSpans = s.getSpans(changeStart, insertEnd, CodeBlockBorderSpan::class.java)\n                            val insideCodeBlock = cbSpans.any { span ->\n                                val ss = s.getSpanStart(span)\n                                val se = s.getSpanEnd(span)\n                                // The span must strictly contain the inserted text,\n                                // not just be adjacent (ss < changeStart rules out\n                                // spans that start at changeStart from a separate block)\n                                ss < changeStart && se >= insertEnd\n                            }\n                            if (insideCodeBlock) {\n                                pendingStyles.add(\"codeBlock\")\n                                pendingStylesInsertPos = selectionStart\n                            }\n                        }\n                    }\n\n                    // Prevent CodeBlockBorderSpan bleed — SPAN_EXCLUSIVE_INCLUSIVE\n                    // causes the span to auto-expand when text is typed at its end\n                    // boundary. If the user is NOT in code block mode, trim any\n                    // CodeBlockBorderSpan (and its companion TypefaceSpan monospace)\n                    // that leaked into the newly typed characters.\n                    if (addedCount > 0 && removedCount == 0 && s != null && s.isNotEmpty()\n                        && !pendingStyles.contains(\"codeBlock\")) {\n                        val spanStart = changeStart\n                        val spanEnd = (changeStart + addedCount).coerceAtMost(s.length)\n                        if (spanEnd > spanStart) {\n                            isInternalChange = true\n                            s.getSpans(spanStart, spanEnd, CodeBlockBorderSpan::class.java).forEach { span ->\n                                val ss = s.getSpanStart(span)\n                                val se = s.getSpanEnd(span)\n                                // Only trim if the span was extended into the new text\n                                if (ss < spanStart && se >= spanEnd) {\n                                    s.removeSpan(span)\n                                    if (ss < spanStart) {\n                                        s.setSpan(CodeBlockBorderSpan(density), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                    }\n                                }\n                            }\n                            s.getSpans(spanStart, spanEnd, TypefaceSpan::class.java)\n                                .filter { it.family == \"monospace\" }\n                                .forEach { span ->\n                                    val ss = s.getSpanStart(span)\n                                    val se = s.getSpanEnd(span)\n                                    if (ss < spanStart && se >= spanEnd) {\n                                        s.removeSpan(span)\n                                        if (ss < spanStart) {\n                                            s.setSpan(TypefaceSpan(\"monospace\"), ss, spanStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                                        }\n                                    }\n                                }\n                            isInternalChange = false\n                        }\n                    }\n\n                    sendContentChangeWithDelta()\n                    saveToUndoStack()\n                    pendingDelta = null\n                }\n                previousText = s?.toString() ?: \"\"\n                updatePlaceholderVisibility()\n                post {\n                    updateContentSize()\n                    invalidate()\n                    // If line count changed (soft-wrap), schedule another redraw\n                    // after the framework's layout pass to ensure onDraw sees\n                    // the updated Layout with correct line positions.\n                    val currentLineCount = layout?.lineCount ?: 0\n                    if (currentLineCount != lastKnownLineCount) {\n                        lastKnownLineCount = currentLineCount\n                        post { invalidate() }\n                    }\n                }\n            }\n        })\n\n        setOnFocusChangeListener { _, hasFocus ->\n            if (hasFocus) {\n                sendEvent(\"onEditorFocus\", Arguments.createMap())\n            } else {\n                hideToolbar()\n                sendEvent(\"onEditorBlur\", Arguments.createMap())\n            }\n        }\n\n        // Install text selection context menu (conditionally adds Bold/Italic/Underline/Strikethrough)\n        updateSelectionActionModeCallback()\n\n        isInitialized = true\n    }\n\n    private fun setupToolbar() {\n        toolbarPopup = PopupWindow(context).apply {\n            width = WindowManager.LayoutParams.WRAP_CONTENT\n            height = WindowManager.LayoutParams.WRAP_CONTENT\n            isOutsideTouchable = true\n            isFocusable = false  // Don't take focus away from EditText\n            isTouchable = true\n            elevation = 10 * density\n\n            // Don't dim the background\n            setBackgroundDrawable(null)\n        }\n\n        // Install text selection context menu (conditionally adds Bold/Italic/Underline/Strikethrough)\n        updateSelectionActionModeCallback()\n\n        customInsertionActionModeCallback = object : android.view.ActionMode.Callback {\n            override fun onCreateActionMode(mode: android.view.ActionMode?, menu: android.view.Menu?): Boolean {\n                return true\n            }\n\n            override fun onPrepareActionMode(mode: android.view.ActionMode?, menu: android.view.Menu?): Boolean {\n                menu?.clear()\n                return true\n            }\n\n            override fun onActionItemClicked(mode: android.view.ActionMode?, item: android.view.MenuItem?): Boolean {\n                return false\n            }\n\n            override fun onDestroyActionMode(mode: android.view.ActionMode?) {}\n        }\n    }\n\n    /**\n     * Install or update the text selection action mode callback.\n     * When showTextSelectionMenuItems is true, adds Bold/Italic/Underline/Strikethrough\n     * to the context menu. When false, only system defaults (Cut/Copy/Paste) appear.\n     */\n    private fun updateSelectionActionModeCallback() {\n        customSelectionActionModeCallback = object : android.view.ActionMode.Callback {\n            override fun onCreateActionMode(mode: android.view.ActionMode?, menu: android.view.Menu?): Boolean {\n                return true\n            }\n\n            override fun onPrepareActionMode(mode: android.view.ActionMode?, menu: android.view.Menu?): Boolean {\n                menu?.clear()\n                // Re-add standard Cut/Copy/Paste/Select All\n                menu?.add(0, android.R.id.cut, 0, android.R.string.cut)\n                menu?.add(0, android.R.id.copy, 1, android.R.string.copy)\n                menu?.add(0, android.R.id.paste, 2, android.R.string.paste)\n                menu?.add(0, android.R.id.selectAll, 3, android.R.string.selectAll)\n                // Add formatting options only when enabled and not in code block\n                if (showTextSelectionMenuItems && !isCursorInCodeBlock()) {\n                    menu?.add(0, 100, 10, \"Bold\")\n                    menu?.add(0, 101, 11, \"Italic\")\n                    menu?.add(0, 102, 12, \"Underline\")\n                    menu?.add(0, 103, 13, \"Strikethrough\")\n                }\n                return true\n            }\n\n            override fun onActionItemClicked(mode: android.view.ActionMode?, item: android.view.MenuItem?): Boolean {\n                when (item?.itemId) {\n                    android.R.id.cut -> { onTextContextMenuItem(android.R.id.cut); mode?.finish(); return true }\n                    android.R.id.copy -> { onTextContextMenuItem(android.R.id.copy); mode?.finish(); return true }\n                    android.R.id.paste -> { onTextContextMenuItem(android.R.id.paste); mode?.finish(); return true }\n                    android.R.id.selectAll -> { onTextContextMenuItem(android.R.id.selectAll); return true }\n                    100 -> { applyFormatAndDismiss(mode) { onBoldClick() }; return true }\n                    101 -> { applyFormatAndDismiss(mode) { onItalicClick() }; return true }\n                    102 -> { applyFormatAndDismiss(mode) { onUnderlineClick() }; return true }\n                    103 -> { applyFormatAndDismiss(mode) { onStrikethroughClick() }; return true }\n                }\n                return false\n            }\n\n            override fun onDestroyActionMode(mode: android.view.ActionMode?) {}\n        }\n    }\n\n    /**\n     * Applies a formatting action from the native text selection action bar,\n     * then dismisses the action bar and collapses the selection so subsequent\n     * typing does not carry the formatting (WhatsApp-style behavior).\n     */\n    private fun applyFormatAndDismiss(mode: android.view.ActionMode?, action: () -> Unit) {\n        val selEnd = selectionEnd.coerceIn(0, text?.length ?: 0)\n        action()\n        updateToolbarButtonStates()\n        // Dismiss the contextual action bar\n        mode?.finish()\n        // Collapse selection to end so next typed chars are unstyled\n        setSelection(selEnd)\n        // Clear any pending/explicitly-off styles to prevent carry-over\n        pendingStyles.clear()\n        explicitlyOffStyles.clear()\n        pendingStylesInsertPos = -1\n        emitActiveStyles()\n    }\n\n    override fun onSelectionChanged(selStart: Int, selEnd: Int) {\n        super.onSelectionChanged(selStart, selEnd)\n\n        // Skip if not initialized yet (during construction)\n        if (!isInitialized) return\n\n        // Clear pending/explicitly-off styles when cursor moves WITHOUT text changing\n        // (i.e., user tapped somewhere else or used arrow keys)\n        if ((pendingStyles.isNotEmpty() || explicitlyOffStyles.isNotEmpty()) && pendingStylesInsertPos >= 0) {\n            if (selStart != pendingStylesInsertPos) {\n                val currentLen = text?.length ?: 0\n                // Allow cursor to advance by 1 (typing) — don't clear in that case\n                if (selStart != pendingStylesInsertPos + 1 || currentLen == previousText.length) {\n                    // Preserve \"codeBlock\" across cursor movements — it's a persistent\n                    // block-level style that should survive until explicitly toggled off\n                    // or exited via triple-Enter. Without this, the clear-and-re-add\n                    // cycle via the re-activation block below can miss edge cases where\n                    // the CodeBlockBorderSpan hasn't been extended yet.\n                    val hadCodeBlock = pendingStyles.contains(\"codeBlock\")\n                    pendingStyles.clear()\n                    explicitlyOffStyles.clear()\n                    pendingStylesInsertPos = -1\n                    if (hadCodeBlock) {\n                        pendingStyles.add(\"codeBlock\")\n                        pendingStylesInsertPos = selStart\n                    }\n                } else {\n                    // Cursor advanced by 1 due to typing — update insert pos\n                    pendingStylesInsertPos = selStart\n                }\n            }\n        }\n\n        // Re-activate code block mode when cursor moves INTO a code block region.\n        // Mirrors iOS isCodeBlockModeActive re-activation in textViewDidChangeSelection.\n        // After a triple-enter exit, pendingStyles is cleared and the CodeBlockBorderSpan\n        // flag is EXCLUSIVE_EXCLUSIVE, so new typing won't get code block spans unless\n        // we re-add \"codeBlock\" to pendingStyles here.\n        // Only check the character immediately before the cursor (selStart - 1) to avoid\n        // false positives when the cursor is on the line right after a code block.\n        if (!isInternalChange && !pendingStyles.contains(\"codeBlock\") && selStart == selEnd) {\n            val editable = text\n            if (editable != null && editable.isNotEmpty()) {\n                val checkPos = if (selStart > 0) selStart - 1 else 0\n                val cursorInCodeBlock = checkPos in 0 until editable.length &&\n                    editable.getSpans(checkPos, checkPos + 1, CodeBlockBorderSpan::class.java).isNotEmpty()\n                if (cursorInCodeBlock) {\n                    pendingStyles.add(\"codeBlock\")\n                    pendingStylesInsertPos = selStart\n                }\n            }\n        }\n\n        android.util.Log.d(\"RichTextEditor\", \"onSelectionChanged: start=$selStart, end=$selEnd, showToolbar=$showToolbar, hasFocus=${hasFocus()}\")\n\n        // Save selection for toolbar actions\n        if (selStart != selEnd) {\n            savedSelectionStart = selStart\n            savedSelectionEnd = selEnd\n        } else {\n            // Clear saved selection when cursor collapses (no selection)\n            savedSelectionStart = 0\n            savedSelectionEnd = 0\n        }\n\n        // Send selection change event — adjust positions to account for zero-width\n        // spaces (\\u200B) that are stripped from text in sendContentChange().\n        // Without this, JS-side cursor positions don't match the cleaned text length.\n        val rawText = text?.toString() ?: \"\"\n        var zwspBeforeStart = 0\n        for (i in 0 until selStart.coerceAtMost(rawText.length)) {\n            if (rawText[i] == '\\u200B') zwspBeforeStart++\n        }\n        val adjustedStart = selStart - zwspBeforeStart\n        val adjustedEnd: Int\n        if (selStart == selEnd) {\n            adjustedEnd = adjustedStart\n        } else {\n            var zwspBeforeEnd = zwspBeforeStart\n            for (i in selStart until selEnd.coerceAtMost(rawText.length)) {\n                if (rawText[i] == '\\u200B') zwspBeforeEnd++\n            }\n            adjustedEnd = selEnd - zwspBeforeEnd\n        }\n        val map = Arguments.createMap()\n        map.putInt(\"start\", adjustedStart)\n        map.putInt(\"end\", adjustedEnd)\n        sendEvent(\"onSelectionChange\", map)\n\n        // Emit active styles synchronously for instant toolbar updates\n        emitActiveStyles()\n\n        // Show/hide toolbar based on selection\n        if (selStart != selEnd && showToolbar && hasFocus()) {\n            android.util.Log.d(\"RichTextEditor\", \"Should show toolbar - selection exists\")\n            removeCallbacks(hideToolbarRunnable)\n            // Use postDelayed to ensure layout is complete\n            postDelayed({ showToolbarAtSelection() }, 50)\n        } else {\n            // Delay hiding to prevent flicker during selection changes\n            android.util.Log.d(\"RichTextEditor\", \"Scheduling hide toolbar\")\n            postDelayed(hideToolbarRunnable, 200)\n        }\n\n        // Update toolbar button states\n        if (selStart != selEnd) {\n            updateToolbarButtonStates()\n        }\n    }\n\n    // Redraw custom overlays (code block containers, inline code borders, blockquote bars)\n    // when the user scrolls so they stay in sync with the text content.\n    override fun onScrollChanged(horiz: Int, vert: Int, oldHoriz: Int, oldVert: Int) {\n        super.onScrollChanged(horiz, vert, oldHoriz, oldVert)\n        invalidate()\n    }\n\n    // Draw unified code block borders BEHIND text (iOS-matching approach:\n    // single rounded rect per code block region, full-width of the view).\n    override fun onDraw(canvas: Canvas) {\n        // Compute the vertical offset that TextView applies for gravity.\n        // Layout line positions are relative to the Layout origin (0,0).\n        // With CENTER_VERTICAL gravity, TextView shifts the Layout down by\n        // (viewHeight - padding - layoutHeight) / 2. We must apply the same\n        // offset so our custom-drawn containers align with the text.\n        val textLayout = layout\n        val gravityOffsetY = if (textLayout != null) {\n            ((height - paddingTop - paddingBottom - textLayout.height) / 2f).coerceAtLeast(0f)\n        } else 0f\n        val textBaseY = paddingTop + gravityOffsetY\n\n        // ── Code block containers (unified rect per region, matching iOS) ──\n        val spannable = text as? Spanned\n        if (spannable != null && textLayout != null) {\n            // 1. Find all code block regions by merging overlapping CodeBlockBorderSpan ranges.\n            //    Only merge spans that truly overlap or are directly adjacent within the\n            //    same code block. A gap of even one character (e.g. a normal-text newline\n            //    between two separate code blocks) must NOT be merged — otherwise two\n            //    distinct code blocks render as one giant container.\n            val cbSpans = spannable.getSpans(0, spannable.length, CodeBlockBorderSpan::class.java)\n            if (cbSpans.isNotEmpty()) {\n                val rawRanges = mutableListOf<Pair<Int, Int>>()\n                for (sp in cbSpans) {\n                    val ss = spannable.getSpanStart(sp)\n                    val se = spannable.getSpanEnd(sp)\n                    if (ss >= 0 && se > ss) rawRanges.add(Pair(ss, se))\n                }\n                rawRanges.sortBy { it.first }\n\n                // Merge only truly overlapping or touching ranges (no +1 tolerance).\n                // Two spans [A..B] and [C..D] merge only when C <= B (overlap/touch).\n                val codeRegions = mutableListOf<Pair<Int, Int>>()\n                for ((s, e) in rawRanges) {\n                    if (codeRegions.isNotEmpty() && s <= codeRegions.last().second) {\n                        codeRegions[codeRegions.lastIndex] =\n                            Pair(codeRegions.last().first, maxOf(codeRegions.last().second, e))\n                    } else {\n                        codeRegions.add(Pair(s, e))\n                    }\n                }\n\n                val cornerRadiusPx = CodeBlockBorderSpan.CORNER_RADIUS * density\n                val dark = isDarkMode()\n\n                val totalLines = textLayout.lineCount\n\n                for ((regionStart, regionEnd) in codeRegions) {\n                    val clampedEnd = (regionEnd - 1).coerceAtLeast(regionStart)\n                        .coerceAtMost((spannable.length - 1).coerceAtLeast(0))\n                    val firstLine = textLayout.getLineForOffset(regionStart)\n                    var lastLine = textLayout.getLineForOffset(clampedEnd)\n\n                    // Match iOS: when a code block region ends with '\\n', the\n                    // empty line below it (where the cursor sits after pressing\n                    // Enter) is not covered by clampedEnd. Extend the container\n                    // to include that next visual line so the background/border\n                    // wraps the cursor immediately, not one keystroke later.\n                    // BUT: only extend if the next character is ALSO inside a\n                    // code block span. After triple-enter exit, the text after\n                    // the \\n is normal text — extending would overlap it.\n                    if (regionEnd > 0 && clampedEnd < spannable.length) {\n                        val lastChar = spannable[clampedEnd]\n                        if (lastChar == '\\n') {\n                            if (regionEnd < spannable.length) {\n                                // Trailing \\n with text after — only extend if\n                                // the next character is inside a code block span\n                                val nextCharCB = spannable.getSpans(\n                                    regionEnd, (regionEnd + 1).coerceAtMost(spannable.length),\n                                    CodeBlockBorderSpan::class.java\n                                )\n                                if (nextCharCB.isNotEmpty()) {\n                                    val nextLine = textLayout.getLineForOffset(regionEnd)\n                                    if (nextLine > lastLine) lastLine = nextLine\n                                }\n                            } else {\n                                // Trailing \\n at very end of text — the cursor is on\n                                // an extra line that has no layout line entry yet.\n                                // Extend by one line height so the container covers it.\n                                lastLine = (totalLines - 1).coerceAtLeast(lastLine)\n                            }\n                        }\n                    }\n\n                    // No setPadding() is used — all visual padding is handled\n                    // purely in onDraw. Extend the container by vPadPx above\n                    // the first line and below the last line for breathing room.\n                    // Clamp padding so the container never overlaps adjacent\n                    // non-code-block lines (matching iOS clamping behavior).\n                    val vPadPx = CodeBlockBorderSpan.VERTICAL_PADDING * density\n\n                    // Clamp top padding: if there's content above the code block,\n                    // limit padding to half the gap between the previous line's\n                    // bottom and the code block's first line top.\n                    val topPad: Float\n                    if (regionStart > 0) {\n                        val prevLine = textLayout.getLineForOffset((regionStart - 1).coerceAtLeast(0))\n                        val prevLineBottom = textLayout.getLineBottom(prevLine).toFloat() + textBaseY\n                        val codeLineTop = textLayout.getLineTop(firstLine).toFloat() + textBaseY\n                        val gap = codeLineTop - prevLineBottom\n                        topPad = minOf(vPadPx, maxOf(gap / 2f, 0f))\n                    } else {\n                        topPad = vPadPx\n                    }\n\n                    // Clamp bottom padding: if there's content below the code block,\n                    // limit padding to half the gap between the code block's last\n                    // line bottom and the next line's top.\n                    val bottomPad: Float\n                    if (regionEnd < spannable.length) {\n                        val nextLine = textLayout.getLineForOffset(regionEnd.coerceAtMost(spannable.length - 1))\n                        val nextLineTop = textLayout.getLineTop(nextLine).toFloat() + textBaseY\n                        val codeLineBottom = textLayout.getLineBottom(lastLine).toFloat() + textBaseY\n                        val gap = nextLineTop - codeLineBottom\n                        bottomPad = minOf(vPadPx, maxOf(gap / 2f, 0f))\n                    } else {\n                        bottomPad = vPadPx\n                    }\n\n                    val codeTop = textLayout.getLineTop(firstLine).toFloat() + textBaseY - topPad\n                    val codeBottom = textLayout.getLineBottom(lastLine).toFloat() + textBaseY + bottomPad\n\n                    // Clamp topY so the container's top rounded corner is never\n                    // clipped by the view's clip bounds (minimum 1px from edge).\n                    val topY = codeTop.coerceAtLeast(1f)\n                    val bottomY = codeBottom\n\n                    // Full width of the view content area (matching iOS textContainer width)\n                    val leftX = paddingLeft.toFloat()\n                    val rightX = (width - paddingRight).toFloat()\n\n                    val containerRect = android.graphics.RectF(leftX, topY, rightX, bottomY)\n                    val path = android.graphics.Path()\n                    path.addRoundRect(containerRect, cornerRadiusPx, cornerRadiusPx,\n                        android.graphics.Path.Direction.CW)\n\n                    // Background fill\n                    codeBlockPaint.color = CodeBlockBorderSpan.bgColor(dark)\n                    codeBlockPaint.style = Paint.Style.FILL\n                    codeBlockPaint.strokeWidth = 0f\n                    canvas.drawPath(path, codeBlockPaint)\n\n                    // Border stroke\n                    codeBlockPaint.color = CodeBlockBorderSpan.borderColor(dark)\n                    codeBlockPaint.style = Paint.Style.STROKE\n                    codeBlockPaint.strokeWidth = 1f * density\n                    canvas.drawPath(path, codeBlockPaint)\n                }\n            }\n        }\n\n        // Draw inline code bordered containers (tight-fitting around glyph bounds)\n        if (spannable != null && textLayout != null) {\n            val inlineCodeSpans = spannable.getSpans(0, spannable.length, InlineCodeSpan::class.java)\n            for (span in inlineCodeSpans) {\n                val ss = spannable.getSpanStart(span)\n                val se = spannable.getSpanEnd(span)\n                if (ss < 0 || se < 0 || ss >= se) continue\n                // Skip if inside a code block (code block takes precedence)\n                val overlapsCodeBlock = spannable.getSpans(ss, se, CodeBlockBorderSpan::class.java).isNotEmpty()\n                if (overlapsCodeBlock) continue\n\n                val firstLine = textLayout.getLineForOffset(ss)\n                val lastLine = textLayout.getLineForOffset(se)\n\n                val cornerRadius = 4f * density\n                val hPad = 4f * density\n                val vPad = 2f * density\n\n                val bgColor = inlineCodeBackgroundColor\n                    ?: if (isDarkMode()) Color.parseColor(\"#272727\") else Color.parseColor(\"#F5F5F5\")\n                val borderColor = inlineCodeBorderColor\n                    ?: if (isDarkMode()) Color.parseColor(\"#383838\") else Color.parseColor(\"#E8E8E8\")\n\n                // Draw per-line containers (Slack-style: each line gets its own rounded rect)\n                for (line in firstLine..lastLine) {\n                    val lineStart = textLayout.getLineStart(line)\n                    val lineEnd = textLayout.getLineEnd(line)\n                    // Intersect with the inline code span range\n                    val drawStart = maxOf(ss, lineStart)\n                    val drawEnd = minOf(se, lineEnd)\n                    if (drawStart >= drawEnd) continue\n\n                    val leftX = textLayout.getPrimaryHorizontal(drawStart) + paddingLeft - hPad\n                    val rightX = textLayout.getPrimaryHorizontal(drawEnd) + paddingLeft + hPad\n                    val topY = textLayout.getLineTop(line).toFloat() + textBaseY - vPad\n                    val bottomY = textLayout.getLineBottom(line).toFloat() + textBaseY + vPad\n\n                    val rect = android.graphics.RectF(leftX, topY, rightX, bottomY)\n\n                    // Background fill\n                    codeBlockPaint.color = bgColor\n                    codeBlockPaint.style = Paint.Style.FILL\n                    canvas.drawRoundRect(rect, cornerRadius, cornerRadius, codeBlockPaint)\n\n                    // Border stroke\n                    codeBlockPaint.color = borderColor\n                    codeBlockPaint.style = Paint.Style.STROKE\n                    codeBlockPaint.strokeWidth = 1f * density\n                    canvas.drawRoundRect(rect, cornerRadius, cornerRadius, codeBlockPaint)\n                }\n            }\n        }\n\n        // Draw blockquote vertical bars (continuous bar for merged adjacent quote lines)\n        // Same pattern as iOS: ▎ character is made invisible, bar is custom-drawn\n        if (textLayout != null) {\n            val plainText = text?.toString() ?: \"\"\n            val lines = plainText.split(\"\\n\")\n            // Merge adjacent quote lines into regions (startOffset, endOffset)\n            val quoteRegions = mutableListOf<Pair<Int, Int>>()\n            var offset = 0\n            for (line in lines) {\n                val lineEnd = offset + line.length\n                if (line.startsWith(\"▎\")) {\n                    if (quoteRegions.isNotEmpty() && offset <= quoteRegions.last().second + 1) {\n                        quoteRegions[quoteRegions.lastIndex] = Pair(quoteRegions.last().first, lineEnd)\n                    } else {\n                        quoteRegions.add(Pair(offset, lineEnd))\n                    }\n                }\n                offset = lineEnd + 1 // +1 for the \\n\n            }\n\n            for ((regionStart, regionEnd) in quoteRegions) {\n                val clampedEnd = regionEnd.coerceAtMost((text?.length ?: 0) - 1).coerceAtLeast(regionStart)\n                val firstLine = textLayout.getLineForOffset(regionStart)\n                val lastLine = textLayout.getLineForOffset(clampedEnd)\n\n                val topY = textLayout.getLineTop(firstLine).toFloat() + textBaseY\n                val bottomY = textLayout.getLineBottom(lastLine).toFloat() + textBaseY\n\n                val barWidth = 3f * density\n                val barX = paddingLeft.toFloat() + 1f\n                val barRadius = barWidth / 2f\n\n                val barRect = android.graphics.RectF(barX, topY, barX + barWidth, bottomY)\n                codeBlockPaint.isAntiAlias = true\n                codeBlockPaint.style = Paint.Style.FILL\n                codeBlockPaint.strokeWidth = 0f\n                codeBlockPaint.color = if (isDarkMode()) Color.parseColor(\"#DCDCDC\") else Color.parseColor(\"#DCDCDC\")\n                canvas.drawRoundRect(barRect, barRadius, barRadius, codeBlockPaint)\n            }\n        }\n\n        // Draw text on top of code block containers\n        super.onDraw(canvas)\n\n        // Draw flat variant bottom border on top of everything\n        if (drawBottomBorder) {\n            val y = scrollY + height.toFloat() - bottomBorderPaint.strokeWidth / 2\n            canvas.drawLine(0f, y, width.toFloat(), y, bottomBorderPaint)\n        }\n    }\n\n    // Synchronous style detection - emits current active styles to JS\n    private fun emitActiveStyles() {\n        val start = selectionStart\n        val end = selectionEnd\n        val spannable = text as? Spanned\n\n        var hasBold = false\n        var hasItalic = false\n        var hasUnderline = false\n        var hasStrikethrough = false\n        var hasCode = false\n        var hasHighlight = false\n        var blockType = \"paragraph\"\n        var alignment = \"left\"\n\n        if (spannable != null && start <= end) {\n            // For cursor position (no selection), check spans touching the cursor from the left\n            // This ensures we detect bold/italic when cursor is at the end of a styled span\n            // But when text is empty, skip span detection entirely\n            val textLen = spannable.length\n            if (textLen == 0) {\n                // Empty text — no spans to detect, only pending styles matter\n            } else {\n                val checkStart = if (start == end && start > 0) start - 1 else start\n                val checkEnd = end.coerceAtLeast(start + 1).coerceAtMost(textLen)\n\n            // Check for style spans\n            spannable.getSpans(checkStart, checkEnd, StyleSpan::class.java).forEach { span ->\n                when (span.style) {\n                    Typeface.BOLD -> hasBold = true\n                    Typeface.ITALIC -> hasItalic = true\n                    Typeface.BOLD_ITALIC -> {\n                        hasBold = true\n                        hasItalic = true\n                    }\n                }\n            }\n\n            hasUnderline = spannable.getSpans(checkStart, checkEnd, UnderlineSpan::class.java).isNotEmpty()\n            hasStrikethrough = spannable.getSpans(checkStart, checkEnd, StrikethroughSpan::class.java).isNotEmpty()\n            hasCode = spannable.getSpans(checkStart, checkEnd, InlineCodeSpan::class.java).isNotEmpty()\n\n            // When cursor is on a new line after Enter, checkStart points to the\n            // newline char which has no InlineCodeSpan (splitInlineCodeSpansAtNewlines\n            // strips it). Check the character before the newline for the marker.\n            if (!hasCode && start == end && start >= 2) {\n                val prevChar = spannable[start - 1]\n                if (prevChar == '\\n') {\n                    hasCode = spannable.getSpans(start - 2, start - 1, InlineCodeSpan::class.java).isNotEmpty()\n                }\n            }\n\n            // Check highlight (but not code background)\n            val bgSpans = spannable.getSpans(checkStart, checkEnd, BackgroundColorSpan::class.java)\n            hasHighlight = bgSpans.any {\n                val color = it.backgroundColor\n                color == Color.parseColor(\"#80FFFF00\") || color == Color.YELLOW\n            }\n\n            // Check block type from line content\n            val lineText = getCurrentLineText()\n            val hasQuotePrefix = lineText.startsWith(\"▎ \")\n            // For combined blockquote+list, strip the quote prefix before checking list type\n            val contentAfterQuote = if (hasQuotePrefix) lineText.substring(2) else lineText\n            blockType = when {\n                hasQuotePrefix && contentAfterQuote.startsWith(\"• \") -> \"quoteBullet\"\n                hasQuotePrefix && contentAfterQuote.matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) -> \"quoteNumbered\"\n                contentAfterQuote.startsWith(\"• \") -> \"bullet\"\n                contentAfterQuote.matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) -> \"numbered\"\n                lineText.startsWith(\"☐ \") || lineText.startsWith(\"☑ \") -> \"checklist\"\n                hasQuotePrefix -> \"quote\"\n                spannable.getSpans(checkStart, checkEnd, RelativeSizeSpan::class.java).any { it.sizeChange > 1.2f } -> \"heading\"\n                else -> \"paragraph\"\n            }\n\n            // Check alignment\n            spannable.getSpans(checkStart, checkEnd, AlignmentSpan.Standard::class.java).firstOrNull()?.let { span ->\n                alignment = when (span.alignment) {\n                    Layout.Alignment.ALIGN_CENTER -> \"center\"\n                    Layout.Alignment.ALIGN_OPPOSITE -> \"right\"\n                    else -> \"left\"\n                }\n            }\n            } // end else (textLen > 0)\n        }\n\n        val map = Arguments.createMap()\n        val boldActive = (hasBold || pendingStyles.contains(\"bold\")) && !explicitlyOffStyles.contains(\"bold\")\n        val italicActive = (hasItalic || pendingStyles.contains(\"italic\")) && !explicitlyOffStyles.contains(\"italic\")\n        val underlineActive = (hasUnderline || pendingStyles.contains(\"underline\")) && !explicitlyOffStyles.contains(\"underline\")\n        val strikethroughActive = (hasStrikethrough || pendingStyles.contains(\"strikethrough\")) && !explicitlyOffStyles.contains(\"strikethrough\")\n        map.putBoolean(\"bold\", boldActive)\n        map.putBoolean(\"italic\", italicActive)\n        map.putBoolean(\"underline\", underlineActive)\n        map.putBoolean(\"strikethrough\", strikethroughActive)\n        val codeActive = (hasCode || pendingStyles.contains(\"code\") || pendingStyles.contains(\"codeBlock\")) && !explicitlyOffStyles.contains(\"code\")\n        // Detect code block separately for Slack-style toolbar hiding\n        var hasCodeBlock = false\n        if (spannable != null && start <= end) {\n            val textLen = spannable.length\n            if (textLen > 0) {\n                val checkStart = if (start == end && start > 0) start - 1 else start\n                val checkEnd = end.coerceAtLeast(start + 1).coerceAtMost(textLen)\n                hasCodeBlock = spannable.getSpans(checkStart, checkEnd, CodeBlockBorderSpan::class.java).isNotEmpty()\n            }\n        }\n        val codeBlockActive = hasCodeBlock || pendingStyles.contains(\"codeBlock\")\n        map.putBoolean(\"code\", codeActive)\n        map.putBoolean(\"codeBlock\", codeBlockActive)\n        map.putBoolean(\"highlight\", hasHighlight)\n        map.putString(\"blockType\", blockType)\n        map.putString(\"alignment\", alignment)\n        sendEvent(\"onActiveStylesChange\", map)\n\n        // Also update inline toolbar button states if present\n    }\n\n    private val hideToolbarRunnable = Runnable {\n        android.util.Log.d(\"RichTextEditor\", \"hideToolbarRunnable: selectionStart=$selectionStart, selectionEnd=$selectionEnd\")\n        if (selectionStart == selectionEnd) {\n            hideToolbar()\n        }\n    }\n\n    private fun showToolbarAtSelection() {\n        if (!showToolbar || toolbarPopup == null) return\n        if (!isAttachedToWindow) return\n\n        val selStart = selectionStart\n        val selEnd = selectionEnd\n        if (selStart == selEnd) return\n\n        try {\n            val textLayout = layout ?: return\n\n            // Get the line of the end of selection\n            val endLine = textLayout.getLineForOffset(selEnd)\n            val lineBottom = textLayout.getLineBottom(endLine)\n\n            val location = IntArray(2)\n            getLocationOnScreen(location)\n\n            val toolbarWidth = (300 * density).toInt()\n            val toolbarHeight = (52 * density).toInt()\n\n            // Center horizontally\n            val screenWidth = context.resources.displayMetrics.widthPixels\n            var x = (screenWidth - toolbarWidth) / 2\n\n            // Ensure x is not negative\n            if (x < 0) x = 0\n\n            // Position BELOW the selection (like iOS: convertedRect.maxY + 8)\n            var y = location[1] + lineBottom + paddingTop + (8 * density).toInt()\n\n            // If toolbar would go off screen at bottom, show above selection\n            val screenHeight = context.resources.displayMetrics.heightPixels\n            if (y + toolbarHeight > screenHeight - (100 * density).toInt()) {\n                val startLine = textLayout.getLineForOffset(selStart)\n                val lineTop = textLayout.getLineTop(startLine)\n                y = location[1] + lineTop + paddingTop - toolbarHeight - (8 * density).toInt()\n            }\n\n            // Ensure y is not negative\n            if (y < 0) y = (8 * density).toInt()\n\n            android.util.Log.d(\"RichTextEditor\", \"Showing toolbar at x=$x, y=$y, width=$toolbarWidth, height=$toolbarHeight\")\n\n            toolbarPopup?.width = toolbarWidth\n            toolbarPopup?.height = toolbarHeight\n\n            // Use windowToken to get the activity's window\n            val token = windowToken\n            if (token == null) {\n                android.util.Log.e(\"RichTextEditor\", \"Window token is null, cannot show popup\")\n                return\n            }\n\n            if (toolbarPopup?.isShowing == true) {\n                toolbarPopup?.update(x, y, toolbarWidth, toolbarHeight)\n            } else {\n                // Show at the root window using absolute coordinates\n                val decorView = (context as? android.app.Activity)?.window?.decorView\n                    ?: rootView\n                toolbarPopup?.showAtLocation(decorView, Gravity.NO_GRAVITY, x, y)\n                android.util.Log.d(\"RichTextEditor\", \"Toolbar popup shown: ${toolbarPopup?.isShowing}\")\n            }\n        } catch (e: Exception) {\n            android.util.Log.e(\"RichTextEditor\", \"Error showing toolbar\", e)\n            e.printStackTrace()\n        }\n    }\n\n    private fun hideToolbar() {\n        try {\n            if (toolbarPopup?.isShowing == true) {\n                android.util.Log.d(\"RichTextEditor\", \"Hiding toolbar\")\n                toolbarPopup?.dismiss()\n            }\n        } catch (e: Exception) {\n            android.util.Log.e(\"RichTextEditor\", \"Error hiding toolbar\", e)\n            e.printStackTrace()\n        }\n    }\n\n    private fun updateToolbarButtonStates() {\n        val start = selectionStart\n        val end = selectionEnd\n        if (start == end || text == null) {\n            // No selection — delegate to emitActiveStyles which handles\n            // pending styles, explicitly-off styles, AND actual spans at cursor\n            emitActiveStyles()\n            return\n        }\n\n        val spannable = text as? Spanned ?: return\n\n        var hasBold = false\n        var hasItalic = false\n        var hasUnderline = false\n        var hasStrikethrough = false\n        var hasCode = false\n        var hasHighlight = false\n\n        // Check for style spans in selection\n        spannable.getSpans(start, end, StyleSpan::class.java).forEach { span ->\n            when (span.style) {\n                Typeface.BOLD -> hasBold = true\n                Typeface.ITALIC -> hasItalic = true\n                Typeface.BOLD_ITALIC -> {\n                    hasBold = true\n                    hasItalic = true\n                }\n            }\n        }\n\n        hasUnderline = spannable.getSpans(start, end, UnderlineSpan::class.java).isNotEmpty()\n        hasStrikethrough = spannable.getSpans(start, end, StrikethroughSpan::class.java).isNotEmpty()\n        hasCode = spannable.getSpans(start, end, InlineCodeSpan::class.java).isNotEmpty()\n        hasHighlight = spannable.getSpans(start, end, BackgroundColorSpan::class.java).isNotEmpty()\n\n        // Check for list prefixes\n        val lineText = getCurrentLineText()\n        val hasBullet = lineText.startsWith(\"• \") || lineText.startsWith(\"▎ • \")\n        val hasNumbered = lineText.matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) || lineText.matches(Regex(\"^▎ \\\\d+\\\\.\\\\s.*\"))\n        val hasQuote = lineText.startsWith(\"▎ \")\n        val hasChecklist = lineText.startsWith(\"☐ \") || lineText.startsWith(\"☑ \")\n\n        // Check alignment\n        var alignLeft = true\n        var alignCenter = false\n        var alignRight = false\n\n        spannable.getSpans(start, end, AlignmentSpan.Standard::class.java).firstOrNull()?.let { span ->\n            when (span.alignment) {\n                Layout.Alignment.ALIGN_CENTER -> {\n                    alignLeft = false\n                    alignCenter = true\n                }\n                Layout.Alignment.ALIGN_OPPOSITE -> {\n                    alignLeft = false\n                    alignRight = true\n                }\n                else -> {}\n            }\n        }\n\n        // Detect code block state to disable inline format buttons (Req 5.1, 5.3)\n        val codeBlockActive = isCursorInCodeBlock()\n\n\n    }\n\n    private fun getCurrentLineText(): String {\n        val text = text?.toString() ?: return \"\"\n        val cursorPos = selectionStart\n        if (cursorPos < 0) return \"\"\n\n        var lineStart = cursorPos\n        while (lineStart > 0 && text[lineStart - 1] != '\\n') {\n            lineStart--\n        }\n\n        var lineEnd = cursorPos\n        while (lineEnd < text.length && text[lineEnd] != '\\n') {\n            lineEnd++\n        }\n\n        return text.substring(lineStart, lineEnd)\n    }\n\n    /**\n     * Returns true if the cursor (or selection) is currently inside a code block.\n     * Used to disable inline formatting when code block is active (Req 5.1, 5.2).\n     */\n    private fun isCursorInCodeBlock(): Boolean {\n        val spannable = text as? Spanned ?: return false\n        val textLen = spannable.length\n        if (textLen == 0) return false\n        val start = selectionStart\n        val end = selectionEnd\n        if (start < 0) return false\n        val checkStart = if (start == end && start > 0) start - 1 else start\n        val checkEnd = end.coerceAtLeast(start + 1).coerceAtMost(textLen)\n        return spannable.getSpans(checkStart, checkEnd, CodeBlockBorderSpan::class.java).isNotEmpty()\n                || pendingStyles.contains(\"codeBlock\")\n    }\n\n    /**\n     * Detects markdown shortcut patterns (e.g., **text**, _text_, ~~text~~, `text`, <u>text</u>)\n     * ending at the cursor position and converts them to live rich text formatting.\n     * Skips conversion inside code blocks (Req 20.6).\n     */\n    private fun detectMarkdownShortcut(s: Editable?) {\n        if (s == null || s.isEmpty()) return\n        val cursorPos = selectionStart\n        if (cursorPos <= 0) return\n\n        // Skip if inside a code block (Req 20.6)\n        if (isCursorInCodeBlock()) return\n\n        val fullText = s.toString()\n\n        // Pre-check: detect if there is an unmatched opening ``` in the text\n        // before the cursor. If so, suppress single-backtick inline code matching\n        // entirely — the user is in the middle of typing a triple-backtick code\n        // block and we must wait for the closing ``` to complete.\n        val textBeforeCursor = fullText.substring(0, cursorPos)\n        val hasUnmatchedTripleBacktick: Boolean = run {\n            var count = 0\n            var i = 0\n            while (i < textBeforeCursor.length) {\n                if (textBeforeCursor[i] == '`' && i + 2 < textBeforeCursor.length\n                    && textBeforeCursor[i + 1] == '`' && textBeforeCursor[i + 2] == '`') {\n                    count++\n                    i += 3\n                } else {\n                    i++\n                }\n            }\n            count % 2 != 0\n        }\n\n        // Patterns ordered longest-delimiter-first so that multi-char delimiters\n        // (***,  **, ~~, __) are checked before single-char ones (*, _, ~, `)\n        // to avoid false matches. Slack-style: *bold*, **bold**, ***bold+italic***,\n        // _italic_, ~strike~, ~~strike~~, `code`.\n        data class MdPattern(val open: String, val close: String, val styles: List<String>)\n        val patterns = listOf(\n            MdPattern(\"***\", \"***\", listOf(\"bold\", \"italic\")),\n            MdPattern(\"```\", \"```\", listOf(\"codeBlock\")),\n            MdPattern(\"**\", \"**\", listOf(\"bold\")),\n            MdPattern(\"~~\", \"~~\", listOf(\"strikethrough\")),\n            MdPattern(\"<u>\", \"</u>\", listOf(\"underline\")),\n            MdPattern(\"*\", \"*\", listOf(\"bold\")),\n            MdPattern(\"~\", \"~\", listOf(\"strikethrough\")),\n            MdPattern(\"`\", \"`\", listOf(\"code\")),\n            MdPattern(\"_\", \"_\", listOf(\"italic\")),\n        )\n\n        for (pattern in patterns) {\n            val closeLen = pattern.close.length\n            val openLen = pattern.open.length\n\n            // Skip single-backtick inline code when an unmatched ``` exists —\n            // the user is typing a triple-backtick code block.\n            if (pattern.open == \"`\" && pattern.close == \"`\" && hasUnmatchedTripleBacktick) continue\n\n            // Text must end with the closing delimiter at cursor position\n            if (cursorPos < closeLen) continue\n            val closeStart = cursorPos - closeLen\n            val closeStr = fullText.substring(closeStart, cursorPos)\n            if (closeStr != pattern.close) continue\n\n            // Skip single-backtick inline code when the line actually ends with\n            // triple backticks — let detectBlockMarkdownShortcut handle it instead.\n            if (pattern.open == \"`\" && cursorPos >= 3) {\n                val last3 = fullText.substring(cursorPos - 3, cursorPos)\n                if (last3 == \"```\") continue\n            }\n\n            // For backtick-based patterns, enforce exact delimiter boundaries:\n            // the closing delimiter must not be immediately preceded by an extra\n            // backtick (beyond the delimiter itself), which would mean the user\n            // typed more backticks than the pattern expects (e.g., ```` instead of ```).\n            if ((pattern.open == \"`\" || pattern.open == \"```\") && closeStart > 0) {\n                if (fullText[closeStart - 1] == '`') continue\n            }\n\n            // Search backwards for the opening delimiter before the closing one\n            val searchEnd = closeStart\n            if (searchEnd <= openLen - 1) continue\n\n            var found = false\n            var openStart = searchEnd - 1\n            while (openStart >= openLen - 1) {\n                val candidateStart = openStart - (openLen - 1)\n                val candidateStr = fullText.substring(candidateStart, candidateStart + openLen)\n                if (candidateStr == pattern.open) {\n                    val contentStart = candidateStart + openLen\n                    val contentLength = searchEnd - contentStart\n                    if (contentLength > 0) {\n                        val content = fullText.substring(contentStart, searchEnd)\n                        // Content must not be only whitespace\n                        if (content.isNotBlank()) {\n                            // For inline code, skip if content is only backticks —\n                            // prevents `(`)` from matching when user types ```\n                            if (pattern.open == \"`\" && content.all { it == '`' }) {\n                                break\n                            }\n                            // For backtick-based patterns, enforce exact opening delimiter:\n                            // skip if the character before the opening delimiter is also a\n                            // backtick (e.g., ```` instead of ``` or `` instead of `).\n                            if ((pattern.open == \"`\" || pattern.open == \"```\") && candidateStart > 0) {\n                                if (fullText[candidateStart - 1] == '`') break\n                            }\n                            // For single backtick, skip if the opening backtick is part of a\n                            // triple-backtick sequence (e.g., ```asdfasdf` should not match)\n                            if (pattern.open == \"`\" && candidateStart >= 2) {\n                                val preceding2 = fullText.substring(candidateStart - 2, candidateStart)\n                                if (preceding2 == \"``\") break\n                            }\n                            // Found a valid match — replace delimiters with formatted text\n                            isInternalChange = true\n\n                            // Delete closing delimiter\n                            s.delete(closeStart, cursorPos)\n                            // Delete opening delimiter\n                            s.delete(candidateStart, candidateStart + openLen)\n\n                            // The content is now at candidateStart with length contentLength\n                            val fmtStart = candidateStart\n                            val fmtEnd = candidateStart + contentLength\n\n                            // Apply all formatting spans for this pattern\n                            for (style in pattern.styles) {\n                                when (style) {\n                                    \"bold\" -> s.setSpan(\n                                        StyleSpan(Typeface.BOLD), fmtStart, fmtEnd,\n                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                    )\n                                    \"italic\" -> s.setSpan(\n                                        StyleSpan(Typeface.ITALIC), fmtStart, fmtEnd,\n                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                    )\n                                    \"underline\" -> s.setSpan(\n                                        UnderlineSpan(), fmtStart, fmtEnd,\n                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                    )\n                                    \"strikethrough\" -> s.setSpan(\n                                        StrikethroughSpan(), fmtStart, fmtEnd,\n                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                    )\n                                    \"code\" -> {\n                                        val fontSize = inlineCodeFontSize ?: 12f\n                                        val codeColor = inlineCodeTextColor ?: Color.parseColor(\"#6852D6\")\n                                        s.setSpan(\n                                            InlineCodeSpan(), fmtStart, fmtEnd,\n                                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                        )\n                                        s.setSpan(\n                                            AbsoluteSizeSpan(fontSize.toInt(), true), fmtStart, fmtEnd,\n                                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                        )\n                                        s.setSpan(\n                                            ForegroundColorSpan(codeColor), fmtStart, fmtEnd,\n                                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                                        )\n                                    }\n                                    \"codeBlock\" -> {\n                                        // Remove any existing inline styles\n                                        s.getSpans(fmtStart, fmtEnd, StyleSpan::class.java)\n                                            .forEach { s.removeSpan(it) }\n                                        s.getSpans(fmtStart, fmtEnd, UnderlineSpan::class.java)\n                                            .forEach { s.removeSpan(it) }\n                                        s.getSpans(fmtStart, fmtEnd, StrikethroughSpan::class.java)\n                                            .forEach { s.removeSpan(it) }\n                                        s.getSpans(fmtStart, fmtEnd, InlineCodeSpan::class.java)\n                                            .forEach { s.removeSpan(it) }\n                                        s.getSpans(fmtStart, fmtEnd, ForegroundColorSpan::class.java)\n                                            .forEach { s.removeSpan(it) }\n                                        // Apply code block spans: monospace + border\n                                        s.setSpan(\n                                            TypefaceSpan(\"monospace\"), fmtStart, fmtEnd,\n                                            Spanned.SPAN_EXCLUSIVE_INCLUSIVE\n                                        )\n                                        s.setSpan(\n                                            CodeBlockBorderSpan(density), fmtStart, fmtEnd,\n                                            Spanned.SPAN_EXCLUSIVE_INCLUSIVE\n                                        )\n                                        // Apply dark-mode-aware text color for readability\n                                        val cbColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n                                        s.setSpan(\n                                            ForegroundColorSpan(cbColor), fmtStart, fmtEnd,\n                                            Spanned.SPAN_EXCLUSIVE_INCLUSIVE\n                                        )\n                                    }\n                                }\n                            }\n\n                            // Place cursor after the formatted text\n                            setSelection(fmtEnd)\n\n                            // For code block shortcut, preserve pending style so typing continues in code block\n                            if (pattern.styles.contains(\"codeBlock\")) {\n                                pendingStyles.clear()\n                                pendingStyles.add(\"codeBlock\")\n                                pendingStylesInsertPos = fmtEnd\n                            } else {\n                                // Clear pending styles so subsequent typing is unstyled\n                                pendingStyles.clear()\n                                pendingStylesInsertPos = -1\n                            }\n                            explicitlyOffStyles.clear()\n\n                            isInternalChange = false\n                            invalidate()\n                            found = true\n                        }\n                    }\n                    break // Found the opening delimiter\n                }\n                openStart--\n            }\n            if (found) return\n        }\n\n        // --- HTML underline: <u>text</u> ---\n        detectHtmlUnderlineShortcut(s, cursorPos)\n\n        // --- Link: [text](url) ---\n        detectLinkShortcut(s, cursorPos)\n\n        // --- Block-level shortcuts: ```, - , 1. , > ---\n        detectBlockMarkdownShortcut(s, cursorPos)\n    }\n\n    /// Detects <u>text</u> pattern and converts to underline formatting.\n    private fun detectHtmlUnderlineShortcut(s: Editable, cursorPos: Int) {\n        val fullText = s.toString()\n        val closeTag = \"</u>\"\n        if (cursorPos < closeTag.length) return\n        val closeStart = cursorPos - closeTag.length\n        val closeStr = fullText.substring(closeStart, cursorPos)\n        if (!closeStr.equals(closeTag, ignoreCase = true)) return\n\n        // Search backwards for <u>\n        val openTag = \"<u>\"\n        val searchArea = fullText.substring(0, closeStart)\n        val openIdx = searchArea.lastIndexOf(openTag, ignoreCase = true)\n        if (openIdx < 0) return\n\n        val contentStart = openIdx + openTag.length\n        val contentLength = closeStart - contentStart\n        if (contentLength <= 0) return\n        val content = fullText.substring(contentStart, closeStart)\n        if (content.isBlank()) return\n\n        isInternalChange = true\n        // Delete closing tag\n        s.delete(closeStart, cursorPos)\n        // Delete opening tag\n        s.delete(openIdx, openIdx + openTag.length)\n\n        val fmtStart = openIdx\n        val fmtEnd = openIdx + contentLength\n        s.setSpan(UnderlineSpan(), fmtStart, fmtEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        setSelection(fmtEnd)\n        pendingStyles.clear()\n        explicitlyOffStyles.clear()\n        pendingStylesInsertPos = -1\n        isInternalChange = false\n        invalidate()\n    }\n\n    /// Detects [text](url) pattern and converts to a styled hyperlink.\n    private fun detectLinkShortcut(s: Editable, cursorPos: Int) {\n        val fullText = s.toString()\n        if (cursorPos < 1) return\n        if (fullText[cursorPos - 1] != ')') return\n\n        // Find matching ( before )\n        val parenOpen = fullText.lastIndexOf('(', cursorPos - 2)\n        if (parenOpen < 0) return\n\n        // Find ]( sequence\n        if (parenOpen < 1 || fullText[parenOpen - 1] != ']') return\n\n        // Find matching [ before ]\n        val bracketOpen = fullText.lastIndexOf('[', parenOpen - 2)\n        if (bracketOpen < 0) return\n\n        val linkText = fullText.substring(bracketOpen + 1, parenOpen - 1)\n        val url = fullText.substring(parenOpen + 1, cursorPos - 1)\n        if (linkText.isEmpty() || url.isEmpty()) return\n\n        // Normalize URL\n        val normalizedUrl = if (!url.lowercase().startsWith(\"http://\") &&\n            !url.lowercase().startsWith(\"https://\") &&\n            !url.lowercase().startsWith(\"mailto:\") &&\n            !url.lowercase().startsWith(\"tel:\")) {\n            \"https://$url\"\n        } else url\n\n        isInternalChange = true\n        // Delete the entire [text](url) pattern\n        s.delete(bracketOpen, cursorPos)\n\n        // Insert styled link text\n        val linkSpannable = SpannableStringBuilder(linkText)\n        linkSpannable.setSpan(NoUnderlineURLSpan(normalizedUrl), 0, linkText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        linkSpannable.setSpan(ForegroundColorSpan(Color.parseColor(\"#2196F3\")), 0, linkText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        s.insert(bracketOpen, linkSpannable)\n\n        setSelection(bracketOpen + linkText.length)\n        pendingStyles.clear()\n        explicitlyOffStyles.clear()\n        pendingStylesInsertPos = -1\n        isInternalChange = false\n        invalidate()\n    }\n\n    /// Detects block-level markdown shortcuts at the start of a line:\n    /// ``` → code block, - → bullet list, 1. → numbered list, > → blockquote\n    private fun detectBlockMarkdownShortcut(s: Editable, cursorPos: Int) {\n        val fullText = s.toString()\n\n        // Find current line start\n        var lineStart = cursorPos\n        while (lineStart > 0 && fullText[lineStart - 1] != '\\n') lineStart--\n\n        val lineText = fullText.substring(lineStart, cursorPos)\n\n        // Triple backtick → code block\n        // Only trigger if the backticks are plain (no InlineCodeSpan) — leftover\n        // formatted backticks from a previous inline code conversion should not count.\n        if (lineText == \"```\") {\n            val hasInlineCodeSpan = s.getSpans(lineStart, cursorPos, InlineCodeSpan::class.java).isNotEmpty()\n            if (!hasInlineCodeSpan && !suppressCodeBlockShortcut) {\n                isInternalChange = true\n                s.delete(lineStart, cursorPos)\n                isInternalChange = false\n                suppressCodeBlockShortcut = true\n                toggleCodeBlock()\n                saveToUndoStack()\n                return\n            }\n        }\n\n        // \"- \" → bullet list\n        if (lineText == \"- \") {\n            isInternalChange = true\n            s.delete(lineStart, cursorPos)\n            isInternalChange = false\n            onBulletListClick()\n            saveToUndoStack()\n            return\n        }\n\n        // \"1. \" → numbered list\n        if (lineText.matches(Regex(\"^\\\\d+\\\\. $\"))) {\n            isInternalChange = true\n            s.delete(lineStart, cursorPos)\n            isInternalChange = false\n            applyNumberedList()\n            saveToUndoStack()\n            return\n        }\n\n        // \"> \" → blockquote\n        if (lineText == \"> \") {\n            isInternalChange = true\n            s.delete(lineStart, cursorPos)\n            isInternalChange = false\n            toggleQuote()\n            saveToUndoStack()\n            return\n        }\n    }\n\n    private fun getLineRange(): Pair<Int, Int> {\n        val text = text?.toString() ?: return Pair(0, 0)\n        val start = selectionStart\n        val end = selectionEnd\n\n        var lineStart = start\n        while (lineStart > 0 && text[lineStart - 1] != '\\n') {\n            lineStart--\n        }\n\n        var lineEnd = end\n        while (lineEnd < text.length && text[lineEnd] != '\\n') {\n            lineEnd++\n        }\n\n        return Pair(lineStart, lineEnd)\n    }\n\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        // When the gesture detector handles a link tap (onSingleTapUp returns true),\n        // consume the event so the default URL-opening behavior is suppressed.\n        if (gestureDetector.onTouchEvent(event)) {\n            return true\n        }\n        return super.onTouchEvent(event)\n    }\n\n    /// Checks whether a string looks like a URL.\n    /// Accepts any scheme with a host, www. prefixed strings, and bare\n    /// domain names (e.g. \"example.com\"). insertLink normalizes schemeless\n    /// URLs by prepending https://.\n    private fun isURL(string: String): Boolean {\n        val trimmed = string.trim()\n        if (trimmed.isEmpty() || trimmed.contains(\" \")) return false\n\n        // 1. Any scheme:// with a host (http, https, ftp, custom, etc.)\n        try {\n            val uri = java.net.URI(trimmed)\n            if (uri.scheme != null && uri.host != null) return true\n        } catch (_: Exception) {}\n\n        // 2. Bare domain: contains a dot, no spaces, and parses as valid\n        //    when https:// is prepended (e.g. \"example.com\", \"sub.example.co.uk/path\")\n        if (trimmed.contains(\".\")) {\n            return try {\n                val uri = java.net.URI(\"https://$trimmed\")\n                uri.host != null\n            } catch (_: Exception) { false }\n        }\n        return false\n    }\n\n    // Override copy/paste to handle rich content and URL-on-selected-text link embedding\n    // (Req 18.1, 18.2, 18.3, 1.1, 1.2, 1.3)\n    override fun onTextContextMenuItem(id: Int): Boolean {\n        // Rich copy: place both HTML and markdown fallback on clipboard (Req 18.1)\n        if (id == android.R.id.copy || id == android.R.id.cut) {\n            val s = text as? Spanned\n            val start = selectionStart\n            val end = selectionEnd\n            if (s != null && start >= 0 && end > start && end <= s.length) {\n                val selectedSpan = s.subSequence(start, end) as Spanned\n                val htmlContent = Html.toHtml(SpannableStringBuilder(selectedSpan), Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)\n                val markdownContent = spannedToMarkdown(selectedSpan)\n\n                val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager\n                if (clipboard != null) {\n                    val clipData = ClipData.newHtmlText(\"rich text\", markdownContent, htmlContent)\n                    clipboard.setPrimaryClip(clipData)\n\n                    // For cut, also remove the selected text\n                    if (id == android.R.id.cut) {\n                        val editable = text as? Editable ?: return true\n                        isInternalChange = true\n                        editable.delete(start, end)\n                        isInternalChange = false\n                        sendContentChangeWithDelta()\n                        saveToUndoStack()\n                    }\n                    return true\n                }\n            }\n            return super.onTextContextMenuItem(id)\n        }\n\n        // Rich paste: check for HTML first, then URL detection, then plain text\n        // (Req 18.2, 18.3, 1.1, 1.2, 1.3)\n        if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {\n            val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager\n            val clip = clipboard?.primaryClip\n\n            // 1. Check for HTML rich content first (Req 18.2)\n            if (clip != null && clip.description.hasMimeType(\"text/html\")) {\n                val htmlText = clip.getItemAt(0)?.htmlText\n                if (htmlText != null && htmlText.isNotEmpty()) {\n                    val richSpanned = Html.fromHtml(htmlText, Html.FROM_HTML_MODE_COMPACT)\n                    if (richSpanned.isNotEmpty()) {\n                        val s = text as? Editable ?: return super.onTextContextMenuItem(id)\n                        val start = selectionStart\n                        val end = selectionEnd\n\n                        isInternalChange = true\n                        s.replace(start.coerceAtMost(end), start.coerceAtLeast(end), richSpanned)\n                        isInternalChange = false\n\n                        sendContentChangeWithDelta()\n                        saveToUndoStack()\n                        return true\n                    }\n                }\n            }\n\n            // 2. Fall through to URL-on-selected-text link embedding and plain text\n            val pastedString = clip?.getItemAt(0)?.text?.toString()\n\n            if (pastedString != null) {\n                val s = text as? Editable ?: return super.onTextContextMenuItem(id)\n                val start = selectionStart\n                val end = selectionEnd\n                val hasSelection = start != end && start >= 0 && end >= 0\n\n                if (isURL(pastedString) && hasSelection) {\n                    // URL on clipboard + text selected → create a styled hyperlink\n                    // (not raw markdown) using the existing insertLink method\n                    val selectedText = s.subSequence(start, end).toString()\n                    val trimmedURL = pastedString.trim()\n                    // insertLink replaces the current selection, so ensure it's set\n                    setSelection(start.coerceAtMost(end), start.coerceAtLeast(end))\n                    insertLink(trimmedURL, selectedText)\n                    saveToUndoStack()\n                    return true\n                }\n\n                // Detect markdown content (links, bold, italic, etc.) and convert\n                // to rich spannable. This handles copy from message bubbles which\n                // place markdown plain text on the clipboard (no HTML).\n                if (looksLikeMarkdown(pastedString)) {\n                    val result = markdownToSpannable(pastedString)\n                    val insertPos = start.coerceAtMost(end)\n                    isInternalChange = true\n                    s.replace(insertPos, start.coerceAtLeast(end), result)\n                    isInternalChange = false\n                    sendContentChangeWithDelta()\n                    saveToUndoStack()\n                    return true\n                }\n\n                // Plain text fallback (Req 18.3)\n                isInternalChange = true\n                s.replace(start.coerceAtMost(end), start.coerceAtLeast(end), pastedString)\n                isInternalChange = false\n                sendContentChangeWithDelta()\n                saveToUndoStack()\n                return true\n            }\n        }\n        return super.onTextContextMenuItem(id)\n    }\n\n    /// Converts a Spanned substring to a markdown string for clipboard fallback.\n    /// Handles bold, italic, underline, strikethrough, inline code, and\n    /// block-level prefixes (bullet, numbered, quote).\n    /// Empty list items are preserved as `- ` or `N. ` markers (Req 8.1).\n    private fun spannedToMarkdown(spanned: Spanned): String {\n        val text = spanned.toString().replace(\"\\u200B\", \"\")\n        if (text.isEmpty()) return \"\"\n\n        val markerMap = mapOf(\n            \"bold\" to \"**\", \"italic\" to \"_\",\n            \"strikethrough\" to \"~~\", \"code\" to \"`\"\n        )\n        // Underline uses asymmetric HTML tags <u>...</u>\n        val openMarkerMap = markerMap + mapOf(\"underline\" to \"<u>\")\n        val closeMarkerMap = markerMap + mapOf(\"underline\" to \"</u>\")\n        val styleOrder = listOf(\"bold\", \"underline\", \"strikethrough\", \"italic\", \"code\")\n        val numberedRegex = Regex(\"^(\\\\d+)\\\\.\\\\s\")\n\n        // Process line-by-line to handle block-level prefixes (Req 8.1)\n        val lines = text.split(\"\\n\")\n        val resultLines = mutableListOf<String>()\n        var charOffset = 0\n\n        for (line in lines) {\n            var blockPrefix = \"\"\n            var contentStart = 0\n\n            // Detect and convert native block prefixes to markdown format\n            when {\n                line.startsWith(\"• \") -> {\n                    blockPrefix = \"- \"\n                    contentStart = 2\n                }\n                line.startsWith(\"▎ \") -> {\n                    blockPrefix = \"> \"\n                    contentStart = 2\n                }\n                else -> {\n                    val match = numberedRegex.find(line)\n                    if (match != null) {\n                        val num = match.groupValues[1]\n                        blockPrefix = \"$num. \"\n                        contentStart = match.value.length\n                    }\n                }\n            }\n\n            val content = line.substring(contentStart)\n\n            // If content is empty, just output the markdown prefix (preserves empty list items)\n            if (content.isEmpty()) {\n                resultLines.add(blockPrefix)\n                charOffset += line.length + 1\n                continue\n            }\n\n            // Build style ranges for this line's content\n            data class StyleRange(val style: String, val start: Int, val end: Int)\n            val styles = mutableListOf<StyleRange>()\n\n            val lineContentStart = charOffset + contentStart\n            val lineContentEnd = charOffset + line.length\n\n            if (lineContentStart < spanned.length && lineContentEnd > lineContentStart) {\n                val rangeEnd = minOf(lineContentEnd, spanned.length)\n                val spans = spanned.getSpans(lineContentStart, rangeEnd, Any::class.java)\n                for (span in spans) {\n                    val spanStart = maxOf(spanned.getSpanStart(span), lineContentStart) - lineContentStart\n                    val spanEnd = minOf(spanned.getSpanEnd(span), rangeEnd) - lineContentStart\n                    if (spanEnd <= spanStart) continue\n                    when (span) {\n                        is URLSpan -> {\n                            val url = span.url ?: \"\"\n                            if (url.isNotEmpty()) {\n                                styles.add(StyleRange(\"link:$url\", spanStart, spanEnd))\n                            }\n                        }\n                        is StyleSpan -> {\n                            when (span.style) {\n                                Typeface.BOLD -> styles.add(StyleRange(\"bold\", spanStart, spanEnd))\n                                Typeface.ITALIC -> styles.add(StyleRange(\"italic\", spanStart, spanEnd))\n                                Typeface.BOLD_ITALIC -> {\n                                    styles.add(StyleRange(\"bold\", spanStart, spanEnd))\n                                    styles.add(StyleRange(\"italic\", spanStart, spanEnd))\n                                }\n                            }\n                        }\n                        is UnderlineSpan -> {\n                            // Skip underline if co-located with a URLSpan (links have underline by default)\n                            val hasUrlSpan = spanned.getSpans(\n                                lineContentStart + spanStart, lineContentStart + spanEnd, URLSpan::class.java\n                            ).isNotEmpty()\n                            if (!hasUrlSpan) {\n                                styles.add(StyleRange(\"underline\", spanStart, spanEnd))\n                            }\n                        }\n                        is StrikethroughSpan -> styles.add(StyleRange(\"strikethrough\", spanStart, spanEnd))\n                        is TypefaceSpan -> {\n                            if (span.family == \"monospace\") {\n                                styles.add(StyleRange(\"code\", spanStart, spanEnd))\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (styles.isEmpty()) {\n                resultLines.add(blockPrefix + content)\n                charOffset += line.length + 1\n                continue\n            }\n\n            // Separate link styles from non-link styles.\n            // Links are rendered as [text](url); non-link styles (bold, italic, etc.)\n            // use marker pairs. Both must be applied together so bold+link produces\n            // **[text](url)** instead of dropping the bold markers.\n            val linkStyles = styles.filter { it.style.startsWith(\"link:\") }\n            val nonLinkStyles = styles.filter { !it.style.startsWith(\"link:\") }\n\n            if (nonLinkStyles.isEmpty() && linkStyles.isEmpty()) {\n                resultLines.add(blockPrefix + content)\n                charOffset += line.length + 1\n                continue\n            }\n\n            // Apply markers using boundary-based approach, integrating link wrapping\n            val boundaries = sortedSetOf(0, content.length)\n            for (s in nonLinkStyles) {\n                boundaries.add(maxOf(0, s.start))\n                boundaries.add(minOf(content.length, s.end))\n            }\n            // Include link boundaries so segments align with link edges\n            for (s in linkStyles) {\n                boundaries.add(maxOf(0, s.start))\n                boundaries.add(minOf(content.length, s.end))\n            }\n            val sorted = boundaries.toList()\n\n            val lineResult = StringBuilder()\n            val openStack = mutableListOf<String>()\n\n            for (i in 0 until sorted.size - 1) {\n                val segStart = sorted[i]\n                val segEnd = sorted[i + 1]\n                if (segEnd <= segStart) continue\n\n                val segment = content.substring(segStart, segEnd)\n\n                val active = mutableListOf<String>()\n                for (s in nonLinkStyles) {\n                    val sStart = maxOf(0, s.start)\n                    val sEnd = minOf(content.length, s.end)\n                    if (sStart <= segStart && sEnd >= segEnd) {\n                        if (openMarkerMap.containsKey(s.style) && !active.contains(s.style)) {\n                            active.add(s.style)\n                        }\n                    }\n                }\n                val desired = styleOrder.filter { active.contains(it) }\n\n                val toReopen = mutableListOf<String>()\n                if (openStack.isNotEmpty()) {\n                    var closeFrom = -1\n                    for (j in openStack.size - 1 downTo 0) {\n                        if (!desired.contains(openStack[j])) {\n                            closeFrom = j\n                            break\n                        }\n                    }\n                    if (closeFrom >= 0) {\n                        for (j in openStack.size - 1 downTo closeFrom) {\n                            lineResult.append(closeMarkerMap[openStack[j]] ?: \"\")\n                            if (desired.contains(openStack[j])) {\n                                toReopen.add(openStack[j])\n                            }\n                        }\n                        while (openStack.size > closeFrom) {\n                            openStack.removeAt(openStack.size - 1)\n                        }\n                        for (s in toReopen.reversed()) {\n                            lineResult.append(openMarkerMap[s] ?: \"\")\n                            openStack.add(s)\n                        }\n                    }\n                }\n\n                for (s in desired) {\n                    if (!openStack.contains(s)) {\n                        lineResult.append(openMarkerMap[s] ?: \"\")\n                        openStack.add(s)\n                    }\n                }\n\n                // Check if this segment falls inside a link range\n                var linkUrl: String? = null\n                for (link in linkStyles) {\n                    val lStart = maxOf(0, link.start)\n                    val lEnd = minOf(content.length, link.end)\n                    if (lStart <= segStart && lEnd >= segEnd) {\n                        linkUrl = link.style.removePrefix(\"link:\")\n                        break\n                    }\n                }\n\n                if (linkUrl != null) {\n                    lineResult.append(\"[$segment]($linkUrl)\")\n                } else {\n                    lineResult.append(segment)\n                }\n            }\n\n            for (j in openStack.size - 1 downTo 0) {\n                lineResult.append(closeMarkerMap[openStack[j]] ?: \"\")\n            }\n\n            resultLines.add(blockPrefix + lineResult.toString())\n            charOffset += line.length + 1\n        }\n\n        return resultLines.joinToString(\"\\n\")\n    }\n\n    /** Returns true if the string contains markdown syntax that should be parsed into rich text. */\n    private fun looksLikeMarkdown(str: String): Boolean {\n        if (str.contains(\"[\") && str.contains(\"](\")) return true\n        if (str.contains(\"**\")) return true\n        if (str.contains(\"~~\")) return true\n        if (str.contains(\"<u>\")) return true\n        if (str.count { it == '`' } >= 2) return true\n        // Italic: _text_ — avoid false positives on plain underscores\n        if (Regex(\"(?<![_\\\\w])_(?!_)(.+?)(?<!_)_(?![_\\\\w])\").containsMatchIn(str)) return true\n        return false\n    }\n\n    /** Normalizes a URL by prepending https:// if no recognized scheme is present. */\n    private fun normalizeURL(url: String): String {\n        val lower = url.lowercase()\n        if (lower.startsWith(\"http://\") || lower.startsWith(\"https://\") ||\n            lower.startsWith(\"mailto:\") || lower.startsWith(\"tel:\")) {\n            return url\n        }\n        return \"https://$url\"\n    }\n\n    /**\n     * Parses a markdown string into a SpannableStringBuilder with proper styling.\n     * Handles: **bold**, _italic_, <u>underline</u>, ~~strikethrough~~, `code`,\n     * and [text](url) links. Mirrors the markers used by spannedToMarkdown.\n     */\n    private fun markdownToSpannable(markdown: String): SpannableStringBuilder {\n        val result = SpannableStringBuilder()\n        val chars = markdown.toCharArray()\n        val len = chars.size\n        var i = 0\n        var currentText = StringBuilder()\n\n        // Active style state\n        var isBold = false\n        var isItalic = false\n        var isUnderline = false\n        var isStrikethrough = false\n        var isCode = false\n\n        // Flushes accumulated text into result with current styles applied\n        fun flush() {\n            if (currentText.isEmpty()) return\n            val segStart = result.length\n            result.append(currentText)\n            val segEnd = result.length\n\n            if (isBold) {\n                result.setSpan(StyleSpan(Typeface.BOLD), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n            if (isItalic) {\n                result.setSpan(StyleSpan(Typeface.ITALIC), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n            if (isUnderline) {\n                result.setSpan(UnderlineSpan(), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n            if (isStrikethrough) {\n                result.setSpan(StrikethroughSpan(), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n            if (isCode) {\n                result.setSpan(InlineCodeSpan(), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                val codeColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n                result.setSpan(ForegroundColorSpan(codeColor), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n            currentText.clear()\n        }\n\n        while (i < len) {\n            // Markdown link: [text](url)\n            if (chars[i] == '[') {\n                val linkResult = parseLinkAt(chars, i)\n                if (linkResult != null) {\n                    flush()\n                    val (linkText, linkURL, fullEnd) = linkResult\n                    val normalizedURL = normalizeURL(linkURL)\n                    val linkStart = result.length\n                    result.append(linkText)\n                    val linkEnd = result.length\n                    result.setSpan(NoUnderlineURLSpan(normalizedURL), linkStart, linkEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    result.setSpan(ForegroundColorSpan(Color.parseColor(\"#2196F3\")), linkStart, linkEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    // Carry over active styles to link text\n                    if (isBold) result.setSpan(StyleSpan(Typeface.BOLD), linkStart, linkEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    if (isItalic) result.setSpan(StyleSpan(Typeface.ITALIC), linkStart, linkEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    if (isStrikethrough) result.setSpan(StrikethroughSpan(), linkStart, linkEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    i = fullEnd\n                    continue\n                }\n            }\n\n            // ** bold toggle\n            if (i + 1 < len && chars[i] == '*' && chars[i + 1] == '*') {\n                flush(); isBold = !isBold; i += 2; continue\n            }\n\n            // ~~ strikethrough toggle\n            if (i + 1 < len && chars[i] == '~' && chars[i + 1] == '~') {\n                flush(); isStrikethrough = !isStrikethrough; i += 2; continue\n            }\n\n            // <u> and </u> HTML tags for underline (used by blocksToMarkdown)\n            if (i + 2 < len && chars[i] == '<' && chars[i + 1] == 'u' && chars[i + 2] == '>') {\n                flush(); isUnderline = true; i += 3; continue\n            }\n            if (i + 3 < len && chars[i] == '<' && chars[i + 1] == '/' && chars[i + 2] == 'u' && chars[i + 3] == '>') {\n                flush(); isUnderline = false; i += 4; continue\n            }\n\n            // Triple-backtick code block fence: ```content``` or ```\\ncontent\\n```\n            if (i + 2 < len && chars[i] == '`' && chars[i + 1] == '`' && chars[i + 2] == '`') {\n                flush()\n                i += 3\n                // Find closing ```\n                val codeContent = StringBuilder()\n                var foundClose = false\n                while (i < len) {\n                    if (i + 2 < len && chars[i] == '`' && chars[i + 1] == '`' && chars[i + 2] == '`') {\n                        i += 3\n                        foundClose = true\n                        break\n                    }\n                    codeContent.append(chars[i])\n                    i += 1\n                }\n                // Determine if this is a true fenced block (contains newlines) or inline usage\n                val rawCode = codeContent.toString()\n                val isFencedBlock = rawCode.contains('\\n') || rawCode.contains('\\r')\n                val trimmedCode = rawCode.trim('\\n', '\\r')\n                if (trimmedCode.isNotEmpty()) {\n                    val segStart = result.length\n                    result.append(trimmedCode)\n                    val segEnd = result.length\n                    if (isFencedBlock) {\n                        // True fenced code block — use EXCLUSIVE_EXCLUSIVE to prevent\n                        // span from auto-extending when subsequent text is appended\n                        result.setSpan(TypefaceSpan(\"monospace\"), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        result.setSpan(CodeBlockBorderSpan(density), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        val cbTextColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n                        result.setSpan(ForegroundColorSpan(cbTextColor), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    } else {\n                        // Inline triple-backtick — treat as inline code, not a block\n                        result.setSpan(InlineCodeSpan(), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        result.setSpan(TypefaceSpan(\"monospace\"), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        val icTextColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n                        result.setSpan(ForegroundColorSpan(icTextColor), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    }\n                }\n                // Only add newline separator for true fenced blocks (multi-line content),\n                // not for inline triple-backtick usage like text```code```text\n                if (isFencedBlock && foundClose && i < len) {\n                    result.append(\"\\n\")\n                }\n                continue\n            }\n\n            // ` inline code toggle\n            if (chars[i] == '`') {\n                flush(); isCode = !isCode; i += 1; continue\n            }\n\n            // _ italic toggle (single underscore — __ already handled above)\n            if (chars[i] == '_') {\n                flush(); isItalic = !isItalic; i += 1; continue\n            }\n\n            currentText.append(chars[i])\n            i += 1\n        }\n\n        flush()\n        return result\n    }\n\n    /**\n     * Parses a markdown link [text](url) starting at position `from` in the char array.\n     * Returns a Triple of (linkText, linkURL, fullEndIndex) or null if no valid pattern found.\n     */\n    private fun parseLinkAt(chars: CharArray, from: Int): Triple<String, String, Int>? {\n        if (chars[from] != '[') return null\n        var j = from + 1\n        while (j < chars.size && chars[j] != ']') j++\n        if (j >= chars.size) return null\n        if (j + 1 >= chars.size || chars[j + 1] != '(') return null\n        val urlStart = j + 2\n        var k = urlStart\n        while (k < chars.size && chars[k] != ')') k++\n        if (k >= chars.size) return null\n        val linkText = String(chars, from + 1, j - from - 1)\n        val linkURL = String(chars, urlStart, k - urlStart)\n        return Triple(linkText, linkURL, k + 1)\n    }\n\n    private fun selectWordAtPosition(x: Float, y: Float) {\n        val layout = layout ?: return\n        val textContent = text?.toString() ?: return\n\n        // Convert touch position to text offset\n        val line = layout.getLineForVertical(y.toInt() - paddingTop)\n        val offset = layout.getOffsetForHorizontal(line, x - paddingLeft)\n\n        if (offset < 0 || offset > textContent.length) return\n\n        // Find word boundaries\n        var start = offset\n        var end = offset\n\n        // Move start to beginning of word\n        while (start > 0 && !Character.isWhitespace(textContent[start - 1])) {\n            start--\n        }\n\n        // Move end to end of word\n        while (end < textContent.length && !Character.isWhitespace(textContent[end])) {\n            end++\n        }\n\n        // Select the word if valid\n        if (start < end) {\n            setSelection(start, end)\n        }\n    }\n\n    // Combined: draw code block borders + flat variant bottom border\n    // (first onDraw removed — merged here)\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n\n        val textLayout = layout\n        if (textLayout != null) {\n            val lineCount = textLayout.lineCount\n            if (lineCount > 0) {\n                val effectiveLineCount = if (numberOfLinesValue > 0 && !isEnabled) {\n                    minOf(numberOfLinesValue, lineCount)\n                } else {\n                    lineCount\n                }\n\n                var desiredHeight = textLayout.getLineBottom(effectiveLineCount - 1).toFloat() + paddingTop + paddingBottom\n\n                // Add extra height for code block visual padding at document\n                // edges. The onDraw() container extends VERTICAL_PADDING above\n                // the first line and below the last line — the view must be\n                // tall enough to avoid clipping those visual extensions.\n                desiredHeight += codeBlockEdgePaddingPx()\n\n                if (desiredHeight < minHeightPx) {\n                    desiredHeight = minHeightPx\n                }\n\n                if (maxHeightValue > 0) {\n                    val maxHeightPx = maxHeightValue * density\n                    if (desiredHeight > maxHeightPx) {\n                        desiredHeight = maxHeightPx\n                    }\n                }\n\n                calculatedHeight = desiredHeight\n\n                val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)\n                setMeasuredDimension(measuredWidth, kotlin.math.ceil(desiredHeight.toDouble()).toInt())\n            }\n        }\n    }\n\n    // Track line count to detect soft-wrap changes that need container redraw\n    private var lastKnownLineCount = 0\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        super.onLayout(changed, left, top, right, bottom)\n        updateContentSize()\n        // Redraw code block containers after layout recalculation.\n        // When text soft-wraps to a new visual line, onDraw() may run before\n        // the Layout has updated line positions. This ensures a redraw with\n        // the correct line metrics so the container covers all wrapped lines.\n        val currentLineCount = layout?.lineCount ?: 0\n        if (currentLineCount != lastKnownLineCount) {\n            lastKnownLineCount = currentLineCount\n            invalidate()\n        }\n    }\n\n    private fun updateContentSize() {\n        val textLayout = layout ?: return\n\n        val lineCount = textLayout.lineCount\n        if (lineCount == 0) return\n\n        val effectiveLineCount = if (numberOfLinesValue > 0 && !isEnabled) {\n            minOf(numberOfLinesValue, lineCount)\n        } else {\n            lineCount\n        }\n\n        var newHeightPx = textLayout.getLineBottom(effectiveLineCount - 1).toFloat() + paddingTop + paddingBottom\n\n        // Add extra height for code block visual padding at document edges.\n        newHeightPx += codeBlockEdgePaddingPx()\n\n        if (newHeightPx < minHeightPx) {\n            newHeightPx = minHeightPx\n        }\n\n        if (maxHeightValue > 0) {\n            val maxHeightPx = maxHeightValue * density\n            if (newHeightPx > maxHeightPx) {\n                isVerticalScrollBarEnabled = true\n                newHeightPx = maxHeightPx\n            } else {\n                isVerticalScrollBarEnabled = false\n            }\n        }\n\n        val previousHeight = calculatedHeight\n        calculatedHeight = newHeightPx\n\n        val newHeightDp = newHeightPx / density\n\n        // Standard height reporting — no special stabilization needed\n        // since TOP gravity doesn't reposition text on relayout.\n        if (kotlin.math.abs(newHeightDp - lastReportedHeight) > 0.5f) {\n            lastReportedHeight = newHeightDp\n            val map = Arguments.createMap()\n            map.putInt(\"height\", kotlin.math.ceil(newHeightDp.toDouble()).toInt())\n            sendEvent(\"onSizeChange\", map)\n\n            if (kotlin.math.abs(previousHeight - calculatedHeight) > 1f) {\n                requestLayout()\n            }\n        }\n\n        if (maxHeightValue > 0 && textLayout.lineCount > 0 && !(numberOfLinesValue > 0 && !isEnabled)) {\n            val cursorPos = selectionEnd.coerceAtLeast(0)\n            val cursorLine = textLayout.getLineForOffset(cursorPos)\n            val cursorBottom = textLayout.getLineBottom(cursorLine)\n            val visibleBottom = scrollY + height - paddingBottom\n            if (cursorBottom > visibleBottom) {\n                scrollTo(0, cursorBottom - height + paddingBottom)\n            } else if (textLayout.getLineTop(cursorLine) < scrollY + paddingTop) {\n                scrollTo(0, textLayout.getLineTop(cursorLine) - paddingTop)\n            }\n        }\n    }\n\n    /**\n     * Returns the extra height (in px) needed for code block container drawing\n     * that extends beyond the text's intrinsic bounds at document edges.\n     * The onDraw() container rect extends VERTICAL_PADDING above the first\n     * line and below the last line, so the view must be tall enough to\n     * prevent clipping at the top/bottom edges.\n     */\n    private fun codeBlockEdgePaddingPx(): Float {\n        val spannable = text as? Spanned ?: return 0f\n        val cbSpans = spannable.getSpans(0, spannable.length, CodeBlockBorderSpan::class.java)\n        if (cbSpans.isEmpty()) return 0f\n\n        val vPad = CodeBlockBorderSpan.VERTICAL_PADDING * density\n        var extra = 0f\n\n        // Check if a code block touches the first line\n        for (sp in cbSpans) {\n            val ss = spannable.getSpanStart(sp)\n            if (ss == 0) { extra += vPad; break }\n        }\n        // Check if a code block touches the last line\n        val textLen = spannable.length\n        for (sp in cbSpans) {\n            val se = spannable.getSpanEnd(sp)\n            if (se >= textLen) { extra += vPad; break }\n        }\n        return extra\n    }\n\n    // Tracks whether a code block is present in the plain variant.\n    // Used by onDraw to draw the container and by codeBlockEdgePaddingPx\n    // to add extra height for the visual padding.\n    private var codeBlockPaddingApplied = false\n\n    /**\n     * Tracks code block presence for the plain variant.\n     *\n     * Does NOT call setPadding() or change gravity — all visual padding\n     * is handled purely in onDraw() and codeBlockEdgePaddingPx(). This\n     * avoids layout thrashing and timing races between setPadding() and\n     * updateContentSize().\n     */\n    private fun updateCodeBlockEdgePadding() {\n        val spannable = text as? Spanned\n        val cbSpans = spannable?.getSpans(0, spannable.length, CodeBlockBorderSpan::class.java)\n        val hasCodeBlock = cbSpans != null && cbSpans.isNotEmpty()\n\n        if (variant == \"plain\") {\n            if (hasCodeBlock && !codeBlockPaddingApplied) {\n                codeBlockPaddingApplied = true\n            } else if (!hasCodeBlock && codeBlockPaddingApplied) {\n                codeBlockPaddingApplied = false\n            }\n        }\n    }\n\n    private fun applyVariantStyle() {\n        if (variant == \"flat\") {\n            background = null\n            setBackgroundColor(Color.TRANSPARENT)\n            drawBottomBorder = true\n        } else if (variant == \"plain\") {\n            background = null\n            setBackgroundColor(Color.TRANSPARENT)\n            drawBottomBorder = false\n            setPadding(0, 0, 0, 0)\n            gravity = Gravity.CENTER_VERTICAL or Gravity.START\n            // Let JS side control height via style prop\n            minHeightPx = 0f\n        } else {\n            drawBottomBorder = false\n            val bg = GradientDrawable().apply {\n                setColor(Color.WHITE)\n                cornerRadius = 8 * density\n                setStroke((1 * density).toInt(), Color.parseColor(\"#E0E0E0\"))\n            }\n            background = bg\n        }\n        invalidate()\n    }\n\n    private fun sendEvent(eventName: String, params: WritableMap) {\n        try {\n            val reactContext = context as? ReactContext ?: return\n            // Use container's ID if available (when wrapped in LinearLayout by ViewManager)\n            val viewId = containerView?.id ?: id\n\n            // Convert event name to Fabric \"top\" prefix format\n            val fabricEventName = when (eventName) {\n                \"onContentChange\" -> \"topContentChange\"\n                \"onSelectionChange\" -> \"topSelectionChange\"\n                \"onEditorFocus\" -> \"topEditorFocus\"\n                \"onEditorBlur\" -> \"topEditorBlur\"\n                \"onSizeChange\" -> \"topSizeChange\"\n                \"onActiveStylesChange\" -> \"topActiveStylesChange\"\n                \"onLinkTap\" -> \"topLinkTap\"\n                else -> eventName\n            }\n\n            // Try Fabric event dispatcher (New Architecture)\n            val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, viewId)\n            if (dispatcher != null) {\n                val surfaceId = UIManagerHelper.getSurfaceId(reactContext)\n                dispatcher.dispatchEvent(\n                    RichTextEditorEvent(surfaceId, viewId, fabricEventName, params)\n                )\n                return\n            }\n\n            // Fallback to Paper (Old Architecture)\n            reactContext.getJSModule(RCTEventEmitter::class.java)\n                ?.receiveEvent(viewId, eventName, params)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    /** Custom event class for Fabric event dispatching */\n    private class RichTextEditorEvent(\n        surfaceId: Int,\n        viewId: Int,\n        private val name: String,\n        private val eventData: WritableMap\n    ) : Event<RichTextEditorEvent>(surfaceId, viewId) {\n        override fun getEventName(): String = name\n        override fun getEventData(): WritableMap = eventData\n    }\n\n    private fun sendContentChange() {\n        sendContentChangeWithDelta(null)\n    }\n\n    private fun emitLinkTapEvent(url: String, text: String, location: Int, length: Int) {\n        val map = Arguments.createMap()\n        map.putString(\"url\", url)\n        map.putString(\"text\", text)\n        map.putInt(\"location\", location)\n        map.putInt(\"length\", length)\n        sendEvent(\"onLinkTap\", map)\n    }\n\n    /**\n     * Emit a send request event when Enter is pressed in \"sendMessage\" mode.\n     * The RN side listens for this and triggers the send action.\n     */\n    private fun emitSendRequestEvent() {\n        sendEvent(\"onSendRequest\", Arguments.createMap())\n    }\n\n    private fun sendContentChangeWithDelta(delta: Map<String, Any>? = pendingDelta) {\n        try {\n            val map = Arguments.createMap()\n            map.putString(\"text\", (text?.toString() ?: \"\").replace(\"\\u200B\", \"\"))\n            // Serialize blocks to JSON string (codegen doesn't support nested arrays)\n            map.putString(\"blocksJson\", getBlocksJsonString())\n\n            // Include delta information if available\n            if (delta != null) {\n                val deltaMap = Arguments.createMap()\n                delta[\"type\"]?.let { deltaMap.putString(\"type\", it as String) }\n                delta[\"position\"]?.let { deltaMap.putInt(\"position\", it as Int) }\n                delta[\"length\"]?.let { deltaMap.putInt(\"length\", it as Int) }\n                delta[\"text\"]?.let { deltaMap.putString(\"text\", it as String) }\n                delta[\"style\"]?.let { deltaMap.putString(\"style\", it as String) }\n                map.putMap(\"delta\", deltaMap)\n            }\n\n            sendEvent(\"onContentChange\", map)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    // Send format delta when applying styles\n    private fun sendFormatDelta(style: String, start: Int, end: Int) {\n        val delta = mapOf(\n            \"type\" to \"format\",\n            \"position\" to start,\n            \"length\" to (end - start),\n            \"style\" to style\n        )\n        sendContentChangeWithDelta(delta)\n    }\n\n    private fun saveToUndoStack() {\n        val currentText = text?.toString() ?: \"\"\n        if (currentText != lastSavedText) {\n            undoStack.add(SpannableStringBuilder(text))\n            if (undoStack.size > 50) {\n                undoStack.removeAt(0)\n            }\n            redoStack.clear()\n            lastSavedText = currentText\n        }\n    }\n\n    // ==================== Public API ====================\n\n    fun setPlaceholderText(value: String) {\n        placeholder = value\n        hint = value\n        updatePlaceholderVisibility()\n    }\n\n    /// Returns true when the editor has no user-visible content.\n    /// ZWS-only text (code block empty placeholder) is treated as empty.\n    private fun isEffectivelyEmpty(): Boolean {\n        val t = text?.toString() ?: return true\n        return t.isEmpty() || t.replace(\"\\u200B\", \"\").isEmpty()\n    }\n\n    /// Show or hide the hint based on effective emptiness.\n    /// Android's built-in hint hides whenever text is non-empty, but we want\n    /// the placeholder to remain visible when only ZWS is present (code block\n    /// empty state).\n    private fun updatePlaceholderVisibility() {\n        val empty = isEffectivelyEmpty()\n        // Restore or clear hint to control visibility\n        hint = if (empty && placeholder.isNotEmpty()) placeholder else null\n    }\n\n    fun setVariant(value: String) {\n        variant = value\n        applyVariantStyle()\n    }\n\n    fun setEditable(value: Boolean) {\n        isEnabled = value\n        if (numberOfLinesValue > 0) {\n            setNumberOfLinesValue(numberOfLinesValue)\n        }\n    }\n\n    fun setMaxHeightValue(value: Int) {\n        maxHeightValue = value\n        post { updateContentSize() }\n    }\n\n    fun setNumberOfLinesValue(value: Int) {\n        numberOfLinesValue = if (value == 0) 0 else value\n        if (numberOfLinesValue > 0 && !isEnabled) {\n            maxLines = numberOfLinesValue\n            ellipsize = android.text.TextUtils.TruncateAt.END\n            isVerticalScrollBarEnabled = false\n            scrollTo(0, 0)\n        } else {\n            maxLines = Integer.MAX_VALUE\n            ellipsize = null\n        }\n        requestLayout()\n    }\n\n    private fun applyEllipsisIfNeeded() {\n        if (numberOfLinesValue <= 0 || isEnabled) return\n        val textLayout = layout ?: return\n        if (textLayout.lineCount <= numberOfLinesValue) return\n\n        val content = text ?: return\n        val availableWidth = textLayout.width\n\n        val staticLayout = android.text.StaticLayout.Builder\n            .obtain(content, 0, content.length, paint, availableWidth)\n            .setMaxLines(numberOfLinesValue)\n            .setEllipsize(android.text.TextUtils.TruncateAt.END)\n            .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)\n            .setIncludePad(includeFontPadding)\n            .build()\n\n        val lastLine = numberOfLinesValue - 1\n        val ellipsisStart = staticLayout.getEllipsisStart(lastLine)\n        val ellipsisCount = staticLayout.getEllipsisCount(lastLine)\n\n        if (ellipsisCount > 0) {\n            val lineStart = staticLayout.getLineStart(lastLine)\n            val truncPoint = lineStart + ellipsisStart\n\n            isInternalChange = true\n            val editable = content as? android.text.Editable ?: return\n            editable.delete(truncPoint, editable.length)\n            editable.append(\"\\u2026\")\n            setSelection(0)\n            isInternalChange = false\n        }\n    }\n\n    fun setShowToolbar(value: Boolean) {\n        showToolbar = value\n        if (!value) hideToolbar()\n    }\n\n    fun setToolbarOptions(options: List<String>?) {\n        toolbarOptions = options\n    }\n\n    fun setContent(blocks: List<Map<String, Any>>) {\n        val spannable = SpannableStringBuilder()\n        var currentOffset = 0\n        var numberedListCounter = 1\n\n        blocks.forEachIndexed { index, block ->\n            val textContent = block[\"text\"] as? String ?: \"\"\n            val blockType = block[\"type\"] as? String ?: \"paragraph\"\n\n            // Add list prefix based on block type\n            val prefix = when (blockType) {\n                \"bullet\", \"bulletList\" -> \"• \"\n                \"numbered\", \"numberedList\" -> \"${numberedListCounter++}. \"\n                \"checklist\" -> \"☐ \"\n                \"quote\" -> \"▎ \"\n                \"quoteBullet\" -> \"▎ • \"\n                \"quoteNumbered\" -> \"▎ ${numberedListCounter++}. \"\n                else -> {\n                    // Reset numbered list counter when not in numbered list\n                    if (blockType != \"numbered\" && blockType != \"numberedList\" && blockType != \"quoteNumbered\") numberedListCounter = 1\n                    \"\"\n                }\n            }\n\n            // No suffix needed for quotes (using prefix-only approach)\n            val suffix = \"\"\n\n            val blockStart = currentOffset\n            spannable.append(prefix)\n            currentOffset += prefix.length\n\n            val textStart = currentOffset\n            spannable.append(textContent)\n            currentOffset += textContent.length\n\n            spannable.append(suffix)\n            currentOffset += suffix.length\n\n            // Apply heading style\n            if (blockType == \"heading\") {\n                spannable.setSpan(RelativeSizeSpan(1.5f), blockStart, currentOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                spannable.setSpan(StyleSpan(Typeface.BOLD), blockStart, currentOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n\n            // Apply styles from the block\n            @Suppress(\"UNCHECKED_CAST\")\n            val styles = block[\"styles\"] as? List<Map<String, Any>> ?: emptyList()\n            for (styleInfo in styles) {\n                val styleName = styleInfo[\"style\"] as? String ?: continue\n                val start = (styleInfo[\"start\"] as? Number)?.toInt() ?: 0\n                val end = (styleInfo[\"end\"] as? Number)?.toInt() ?: textContent.length\n\n                if (start >= end || end > textContent.length) continue\n\n                val absoluteStart = textStart + start\n                val absoluteEnd = textStart + end\n\n                when (styleName) {\n                    \"bold\" -> spannable.setSpan(StyleSpan(Typeface.BOLD), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    \"italic\" -> spannable.setSpan(StyleSpan(Typeface.ITALIC), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    \"underline\" -> spannable.setSpan(UnderlineSpan(), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    \"strikethrough\" -> spannable.setSpan(StrikethroughSpan(), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    \"code\" -> {\n                        val fontSize = inlineCodeFontSize ?: 12f\n                        spannable.setSpan(AbsoluteSizeSpan(fontSize.toInt(), true), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        spannable.setSpan(InlineCodeSpan(), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        val codeColor = inlineCodeTextColor ?: Color.parseColor(\"#6852D6\")\n                        spannable.setSpan(ForegroundColorSpan(codeColor), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    }\n                    \"highlight\" -> spannable.setSpan(BackgroundColorSpan(Color.parseColor(\"#80FFFF00\")), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    \"link\" -> {\n                        val url = styleInfo[\"url\"] as? String ?: \"\"\n                        if (url.isNotEmpty()) {\n                            spannable.setSpan(NoUnderlineURLSpan(url), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                            spannable.setSpan(ForegroundColorSpan(Color.parseColor(\"#2196F3\")), absoluteStart, absoluteEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                        }\n                    }\n                }\n            }\n\n            // Make ▎ quote prefix invisible — bar is custom-drawn in onDraw()\n            if ((blockType == \"quote\" || blockType == \"quoteBullet\" || blockType == \"quoteNumbered\") && prefix.isNotEmpty()) {\n                spannable.setSpan(ForegroundColorSpan(Color.TRANSPARENT), blockStart, blockStart + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n\n            // Apply code block attributes (monospace font + border span) for codeBlock lines\n            if (blockType == \"codeBlock\") {\n                spannable.setSpan(TypefaceSpan(\"monospace\"), blockStart, currentOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                spannable.setSpan(CodeBlockBorderSpan(density), blockStart, currentOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                // Apply dark-mode-aware text color for readability\n                val cbTextColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n                spannable.setSpan(ForegroundColorSpan(cbTextColor), blockStart, currentOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n\n            if (index < blocks.size - 1) {\n                spannable.append(\"\\n\")\n                // Carry CodeBlockBorderSpan on the newline between consecutive\n                // codeBlock lines so getBlocksArray() fallback detects empty lines\n                // inside code blocks correctly during edit round-trips.\n                if (blockType == \"codeBlock\") {\n                    spannable.setSpan(CodeBlockBorderSpan(density), currentOffset, currentOffset + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                }\n                currentOffset += 1\n            }\n        }\n\n        isInternalChange = true\n        setText(spannable)\n\n        // Detect if cursor lands inside a code block and update pendingStyles\n        // BEFORE setSelection, because setSelection triggers onSelectionChanged\n        // which calls emitActiveStyles().\n        val endPos = spannable.length\n        if (endPos > 0) {\n            val checkPos = (endPos - 1).coerceAtMost(spannable.length - 1)\n            if (checkPos >= 0) {\n                val codeBlockSpans = spannable.getSpans(checkPos, checkPos + 1, CodeBlockBorderSpan::class.java)\n                if (codeBlockSpans.isNotEmpty()) {\n                    pendingStyles.add(\"codeBlock\")\n                    pendingStylesInsertPos = endPos\n                }\n            }\n        }\n\n        if (numberOfLinesValue > 0 && !isEnabled) {\n            setSelection(0)\n        } else {\n            setSelection(spannable.length)\n        }\n        isInternalChange = false\n        applyListIndentation()\n        updateCodeBlockEdgePadding()\n        updatePlaceholderVisibility()\n\n        // Force redraw so code block borders/backgrounds render immediately\n        invalidate()\n        sendContentChange()\n\n        post {\n            if (numberOfLinesValue > 0 && !isEnabled) {\n                scrollTo(0, 0)\n                applyEllipsisIfNeeded()\n            }\n            updateContentSize()\n        }\n    }\n\n    fun getTextContent(): String = text.toString()\n\n    // Fabric command response methods\n    fun emitGetTextResponse() {\n        val map = Arguments.createMap()\n        map.putString(\"text\", text?.toString() ?: \"\")\n        sendEvent(\"onGetTextResponse\", map)\n    }\n\n    fun emitGetBlocksResponse() {\n        val map = Arguments.createMap()\n        map.putArray(\"blocks\", getBlocksArray())\n        sendEvent(\"onGetBlocksResponse\", map)\n    }\n\n    private fun extractStylesForRange(spannable: Spanned, lineStart: Int, lineEnd: Int): List<Map<String, Any>> {\n        val styles = mutableListOf<Map<String, Any>>()\n\n        spannable.getSpans(lineStart, lineEnd, StyleSpan::class.java).forEach { span ->\n            val start = (spannable.getSpanStart(span) - lineStart).coerceAtLeast(0)\n            val end = (spannable.getSpanEnd(span) - lineStart).coerceAtMost(lineEnd - lineStart)\n            when (span.style) {\n                Typeface.BOLD -> styles.add(mapOf(\"style\" to \"bold\", \"start\" to start, \"end\" to end))\n                Typeface.ITALIC -> styles.add(mapOf(\"style\" to \"italic\", \"start\" to start, \"end\" to end))\n                Typeface.BOLD_ITALIC -> {\n                    styles.add(mapOf(\"style\" to \"bold\", \"start\" to start, \"end\" to end))\n                    styles.add(mapOf(\"style\" to \"italic\", \"start\" to start, \"end\" to end))\n                }\n            }\n        }\n\n        spannable.getSpans(lineStart, lineEnd, UnderlineSpan::class.java).forEach { span ->\n            if (spannable.getSpans(spannable.getSpanStart(span), spannable.getSpanEnd(span), URLSpan::class.java).isEmpty()) {\n                val start = (spannable.getSpanStart(span) - lineStart).coerceAtLeast(0)\n                val end = (spannable.getSpanEnd(span) - lineStart).coerceAtMost(lineEnd - lineStart)\n                styles.add(mapOf(\"style\" to \"underline\", \"start\" to start, \"end\" to end))\n            }\n        }\n\n        spannable.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java).forEach { span ->\n            val start = (spannable.getSpanStart(span) - lineStart).coerceAtLeast(0)\n            val end = (spannable.getSpanEnd(span) - lineStart).coerceAtMost(lineEnd - lineStart)\n            styles.add(mapOf(\"style\" to \"strikethrough\", \"start\" to start, \"end\" to end))\n        }\n\n        spannable.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java).forEach { span ->\n            val start = (spannable.getSpanStart(span) - lineStart).coerceAtLeast(0)\n            val end = (spannable.getSpanEnd(span) - lineStart).coerceAtMost(lineEnd - lineStart)\n            styles.add(mapOf(\"style\" to \"code\", \"start\" to start, \"end\" to end))\n        }\n\n        spannable.getSpans(lineStart, lineEnd, BackgroundColorSpan::class.java).filter {\n            val color = it.backgroundColor\n            color == Color.parseColor(\"#80FFFF00\") || color == Color.YELLOW\n        }.forEach { span ->\n            val start = (spannable.getSpanStart(span) - lineStart).coerceAtLeast(0)\n            val end = (spannable.getSpanEnd(span) - lineStart).coerceAtMost(lineEnd - lineStart)\n            styles.add(mapOf(\"style\" to \"highlight\", \"start\" to start, \"end\" to end))\n        }\n\n        // Extract link spans (URLSpan) with url metadata\n        spannable.getSpans(lineStart, lineEnd, URLSpan::class.java).forEach { span ->\n            val start = (spannable.getSpanStart(span) - lineStart).coerceAtLeast(0)\n            val end = (spannable.getSpanEnd(span) - lineStart).coerceAtMost(lineEnd - lineStart)\n            val url = span.url ?: \"\"\n            if (url.isNotEmpty()) {\n                styles.add(mapOf(\"style\" to \"link\", \"start\" to start, \"end\" to end, \"url\" to url))\n            }\n        }\n\n        return styles\n    }\n\n    private fun detectBlockType(lineText: String): Pair<String, String> {\n        return when {\n            // Combined blockquote + list (must check before individual prefixes)\n            lineText.startsWith(\"▎ • \") -> \"quoteBullet\" to lineText.substring(4)\n            lineText.startsWith(\"▎ \") && lineText.substring(2).matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) ->\n                \"quoteNumbered\" to lineText.substring(2).replace(Regex(\"^\\\\d+\\\\.\\\\s\"), \"\")\n            lineText.startsWith(\"• \") -> \"bullet\" to lineText.substring(2)\n            lineText.matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) -> \"numbered\" to lineText.replace(Regex(\"^\\\\d+\\\\.\\\\s\"), \"\")\n            lineText.startsWith(\"☐ \") || lineText.startsWith(\"☑ \") -> \"checklist\" to lineText.substring(2)\n            lineText.startsWith(\"▎ \") -> \"quote\" to lineText.substring(2)\n            else -> \"paragraph\" to lineText\n        }\n    }\n\n    fun getBlocksArray(): WritableArray {\n        val blocks = Arguments.createArray()\n        val spannable = text as? Spanned ?: return blocks\n        val textContent = spannable.toString()\n        if (textContent.isEmpty()) return blocks\n\n        val lines = textContent.split(\"\\n\")\n        var currentIndex = 0\n        lines.forEach { line ->\n            var (blockType, displayText) = detectBlockType(line)\n            // Strip zero-width space placeholders from display text\n            displayText = displayText.replace(\"\\u200B\", \"\")\n            val prefixLen = line.length - displayText.length\n\n            // Detect code block: check the full line range (including ZWS prefix)\n            // for CodeBlockBorderSpan. Using currentIndex instead of lineStart\n            // ensures we detect spans that cover the ZWS placeholder character\n            // at the start of code block lines.\n            val lineStart = currentIndex + prefixLen\n            val lineEnd = currentIndex + line.length\n            val cbCheckStart = currentIndex.coerceAtMost(spannable.length)\n            val cbCheckEnd = lineEnd.coerceAtMost(spannable.length)\n            if (cbCheckEnd > cbCheckStart) {\n                val cbSpans = spannable.getSpans(cbCheckStart, cbCheckEnd, CodeBlockBorderSpan::class.java)\n                if (cbSpans.isNotEmpty()) {\n                    blockType = \"codeBlock\"\n                }\n            }\n            // Fallback: empty lines inside a code block — the newline char before\n            // this line carries the CodeBlockBorderSpan, but the empty line itself\n            // has zero length so getSpans(start, start) returns nothing.\n            // Check the preceding newline character for the span.\n            if (blockType != \"codeBlock\" && line.isEmpty() && currentIndex > 0) {\n                val prevCharIdx = currentIndex - 1\n                if (prevCharIdx < spannable.length) {\n                    val cbSpans = spannable.getSpans(prevCharIdx, prevCharIdx + 1, CodeBlockBorderSpan::class.java)\n                    if (cbSpans.isNotEmpty()) {\n                        blockType = \"codeBlock\"\n                    }\n                }\n            }\n\n            val block = Arguments.createMap()\n            block.putString(\"type\", blockType)\n            block.putString(\"text\", displayText)\n\n            val stylesArray = Arguments.createArray()\n            extractStylesForRange(spannable, lineStart, lineEnd).forEach { style ->\n                val styleMap = Arguments.createMap()\n                styleMap.putString(\"style\", style[\"style\"] as String)\n                styleMap.putInt(\"start\", style[\"start\"] as Int)\n                styleMap.putInt(\"end\", style[\"end\"] as Int)\n                val url = style[\"url\"] as? String\n                if (url != null) styleMap.putString(\"url\", url)\n                stylesArray.pushMap(styleMap)\n            }\n            block.putArray(\"styles\", stylesArray)\n            blocks.pushMap(block)\n\n            currentIndex += line.length + 1\n        }\n        return blocks\n    }\n\n    fun getBlocksJsonString(): String {\n        val spannable = text as? Spanned ?: return \"[]\"\n        val textContent = spannable.toString()\n        if (textContent.isEmpty()) return \"[]\"\n\n        val jsonArray = org.json.JSONArray()\n        val lines = textContent.split(\"\\n\")\n        var currentIndex = 0\n        lines.forEach { line ->\n            var (blockType, displayText) = detectBlockType(line)\n            // Strip zero-width space placeholders from display text\n            displayText = displayText.replace(\"\\u200B\", \"\")\n            val prefixLen = line.length - displayText.length\n\n            // Detect code block: check the full line range (including ZWS prefix)\n            // for CodeBlockBorderSpan. Using currentIndex instead of lineStart\n            // ensures we detect spans that cover the ZWS placeholder character\n            // at the start of code block lines.\n            val lineStart = currentIndex + prefixLen\n            val lineEnd = currentIndex + line.length\n            val cbCheckStart = currentIndex.coerceAtMost(spannable.length)\n            val cbCheckEnd = lineEnd.coerceAtMost(spannable.length)\n            if (cbCheckEnd > cbCheckStart) {\n                val cbSpans = spannable.getSpans(cbCheckStart, cbCheckEnd, CodeBlockBorderSpan::class.java)\n                if (cbSpans.isNotEmpty()) {\n                    blockType = \"codeBlock\"\n                }\n            }\n            // Fallback: empty lines inside a code block — the newline char before\n            // this line carries the CodeBlockBorderSpan, but the empty line itself\n            // has zero length so getSpans(start, start) returns nothing.\n            // Check the preceding newline character for the span.\n            if (blockType != \"codeBlock\" && line.isEmpty() && currentIndex > 0) {\n                val prevCharIdx = currentIndex - 1\n                if (prevCharIdx < spannable.length) {\n                    val cbSpans = spannable.getSpans(prevCharIdx, prevCharIdx + 1, CodeBlockBorderSpan::class.java)\n                    if (cbSpans.isNotEmpty()) {\n                        blockType = \"codeBlock\"\n                    }\n                }\n            }\n\n            val block = org.json.JSONObject()\n            block.put(\"type\", blockType)\n            block.put(\"text\", displayText)\n\n            val stylesJson = org.json.JSONArray()\n            extractStylesForRange(spannable, lineStart, lineEnd).forEach { style ->\n                val styleObj = org.json.JSONObject()\n                styleObj.put(\"style\", style[\"style\"])\n                styleObj.put(\"start\", style[\"start\"])\n                styleObj.put(\"end\", style[\"end\"])\n                val url = style[\"url\"] as? String\n                if (url != null) styleObj.put(\"url\", url)\n                stylesJson.put(styleObj)\n            }\n            block.put(\"styles\", stylesJson)\n            jsonArray.put(block)\n\n            currentIndex += line.length + 1\n        }\n        return jsonArray.toString()\n    }\n\n    // ==================== Text Styling (ToolbarActionListener) ====================\n\n    fun onBoldClick() {\n        // Ignore inline formatting when code block is active (Req 5.2)\n        if (isCursorInCodeBlock()) return\n        android.util.Log.d(\"RichTextEditor\", \"onBoldClick called\")\n        toggleStyle(Typeface.BOLD)\n    }\n\n    fun onItalicClick() {\n        // Ignore inline formatting when code block is active (Req 5.2)\n        if (isCursorInCodeBlock()) return\n        android.util.Log.d(\"RichTextEditor\", \"onItalicClick called\")\n        toggleStyle(Typeface.ITALIC)\n    }\n\n    fun onUnderlineClick() {\n        // Ignore inline formatting when code block is active (Req 5.2)\n        if (isCursorInCodeBlock()) return\n        android.util.Log.d(\"RichTextEditor\", \"onUnderlineClick called\")\n        toggleSpan(UnderlineSpan::class.java) { UnderlineSpan() }\n    }\n\n    fun onStrikethroughClick() {\n        // Ignore inline formatting when code block is active (Req 5.2)\n        if (isCursorInCodeBlock()) return\n        android.util.Log.d(\"RichTextEditor\", \"onStrikethroughClick called\")\n        toggleSpan(StrikethroughSpan::class.java) { StrikethroughSpan() }\n    }\n\n\n    /**\n     * Split InlineCodeSpan spans at newline characters so the visual container\n     * drawn by onDraw() does not extend across line breaks. Each line segment\n     * gets its own InlineCodeSpan (plus matching AbsoluteSizeSpan and ForegroundColorSpan).\n     */\n    private fun splitInlineCodeSpansAtNewlines(s: Editable) {\n        val spans = s.getSpans(0, s.length, InlineCodeSpan::class.java)\n        for (span in spans) {\n            val ss = s.getSpanStart(span)\n            val se = s.getSpanEnd(span)\n            if (ss < 0 || se < 0 || ss >= se) continue\n            val text = s.subSequence(ss, se).toString()\n            if (!text.contains(\"\\n\")) continue\n\n            // Collect associated spans (font size + foreground color) for re-application\n            val sizeSpans = s.getSpans(ss, se, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) == ss && s.getSpanEnd(it) == se }\n            val colorSpans = s.getSpans(ss, se, ForegroundColorSpan::class.java)\n                .filter { s.getSpanStart(it) == ss && s.getSpanEnd(it) == se }\n            val fontSize = sizeSpans.firstOrNull()?.size ?: (inlineCodeFontSize?.toInt() ?: 12)\n            val codeColor = colorSpans.firstOrNull()?.foregroundColor\n                ?: (inlineCodeTextColor ?: Color.parseColor(\"#6852D6\"))\n\n            // Remove original spans\n            isInternalChange = true\n            s.removeSpan(span)\n            sizeSpans.forEach { s.removeSpan(it) }\n            colorSpans.forEach { s.removeSpan(it) }\n\n            // Re-apply per-line segments (skip newline characters)\n            var i = 0\n            while (i < text.length) {\n                if (text[i] == '\\n') { i++; continue }\n                var end = text.indexOf('\\n', i)\n                if (end < 0) end = text.length\n                if (end > i) {\n                    val segStart = ss + i\n                    val segEnd = ss + end\n                    s.setSpan(InlineCodeSpan(), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    s.setSpan(AbsoluteSizeSpan(fontSize, true), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    s.setSpan(ForegroundColorSpan(codeColor), segStart, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                }\n                i = end\n            }\n            isInternalChange = false\n        }\n        invalidate()\n    }\n\n    fun onCodeClick() {\n        // Ignore inline code toggle when code block is active (Req 5.2)\n        if (isCursorInCodeBlock()) return\n\n        // Use saved selection if current selection is empty\n        var start = selectionStart\n        var end = selectionEnd\n\n        if (start >= end && savedSelectionStart < savedSelectionEnd) {\n            start = savedSelectionStart\n            end = savedSelectionEnd\n        }\n\n        // Clamp to current text length\n        val textLength = text?.length ?: 0\n        start = start.coerceIn(0, textLength)\n        end = end.coerceIn(start, textLength)\n\n        // No selection: toggle pending code style for type-ahead (Slack-style)\n        if (start >= end) {\n            val key = \"code\"\n            val spannableText = text as? Spanned\n            val cursorPos = selectionStart\n            val isInsideStyle = if (spannableText != null && cursorPos > 0) {\n                spannableText.getSpans(cursorPos - 1, cursorPos, InlineCodeSpan::class.java).isNotEmpty()\n            } else false\n\n            if (isInsideStyle) {\n                if (explicitlyOffStyles.contains(key)) {\n                    explicitlyOffStyles.remove(key)\n                } else {\n                    explicitlyOffStyles.add(key)\n                    pendingStyles.remove(key)\n                    pendingStyles.remove(\"codeBlock\")\n                }\n            } else {\n                if (pendingStyles.contains(key)) {\n                    pendingStyles.remove(key)\n                } else {\n                    pendingStyles.add(key)\n                    pendingStyles.remove(\"codeBlock\") // remove code block if active\n                    explicitlyOffStyles.remove(key)\n                }\n            }\n            pendingStylesInsertPos = selectionStart\n            emitActiveStyles()\n            return\n        }\n\n        val spannable = text as? Editable ?: return\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        val segments = nonMentionRanges(start, end, spannable)\n        if (segments.isEmpty()) return\n\n        val hasInlineCode = segments.any { seg ->\n            spannable.getSpans(seg.first, seg.last + 1, InlineCodeSpan::class.java).isNotEmpty()\n        }\n\n        isInternalChange = true\n        if (hasInlineCode) {\n            for (seg in segments) {\n                val segEnd = seg.last + 1 // IntRange.last is inclusive; span APIs need exclusive end\n                spannable.getSpans(seg.first, segEnd, InlineCodeSpan::class.java)\n                    .forEach { spannable.removeSpan(it) }\n                spannable.getSpans(seg.first, segEnd, AbsoluteSizeSpan::class.java)\n                    .filter { spannable.getSpanStart(it) >= seg.first && spannable.getSpanEnd(it) <= segEnd }\n                    .forEach { spannable.removeSpan(it) }\n                spannable.getSpans(seg.first, segEnd, BackgroundColorSpan::class.java)\n                    .filter { spannable.getSpanStart(it) >= seg.first && spannable.getSpanEnd(it) <= segEnd }\n                    .forEach { spannable.removeSpan(it) }\n                spannable.getSpans(seg.first, segEnd, ForegroundColorSpan::class.java)\n                    .filter { spannable.getSpanStart(it) >= seg.first && spannable.getSpanEnd(it) <= segEnd }\n                    .forEach { spannable.removeSpan(it) }\n            }\n        } else {\n            val fontSize = inlineCodeFontSize ?: 12f\n            val codeColor = inlineCodeTextColor ?: Color.parseColor(\"#6852D6\")\n            for (seg in segments) {\n                val segEnd = seg.last + 1 // IntRange.last is inclusive; span APIs need exclusive end\n                spannable.setSpan(AbsoluteSizeSpan(fontSize.toInt(), true), seg.first, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                spannable.setSpan(InlineCodeSpan(), seg.first, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                spannable.setSpan(ForegroundColorSpan(codeColor), seg.first, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n        }\n        setSelection(start, end)\n        isInternalChange = false\n        invalidate()\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    fun onHighlightClick() {\n        // Use saved selection if current selection is empty\n        var start = selectionStart\n        var end = selectionEnd\n\n        if (start >= end && savedSelectionStart < savedSelectionEnd) {\n            start = savedSelectionStart\n            end = savedSelectionEnd\n        }\n\n        // Clamp to current text length\n        val textLength = text?.length ?: 0\n        start = start.coerceIn(0, textLength)\n        end = end.coerceIn(start, textLength)\n\n        if (start >= end) return\n\n        val spannable = text as? Editable ?: return\n        val existingSpans = spannable.getSpans(start, end, BackgroundColorSpan::class.java)\n            .filter {\n                val color = it.backgroundColor\n                color == Color.parseColor(\"#80FFFF00\") || color == Color.YELLOW\n            }\n\n        isInternalChange = true\n        if (existingSpans.isNotEmpty()) {\n            existingSpans.forEach { spannable.removeSpan(it) }\n        } else {\n            spannable.setSpan(BackgroundColorSpan(Color.parseColor(\"#80FFFF00\")), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        }\n        setSelection(start, end)\n        isInternalChange = false\n        invalidate()\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    fun onHeadingClick() {\n        val (lineStart, lineEnd) = getLineRange()\n        if (lineStart >= lineEnd) return\n\n        val spannable = text as? Editable ?: return\n\n        // Check if already a heading\n        val existingSpans = spannable.getSpans(lineStart, lineEnd, RelativeSizeSpan::class.java)\n        val isHeading = existingSpans.any { it.sizeChange > 1.2f }\n\n        isInternalChange = true\n        existingSpans.forEach { spannable.removeSpan(it) }\n        spannable.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n            .filter { it.style == Typeface.BOLD }\n            .forEach { spannable.removeSpan(it) }\n\n        if (!isHeading) {\n            spannable.setSpan(RelativeSizeSpan(1.5f), lineStart, lineEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            spannable.setSpan(StyleSpan(Typeface.BOLD), lineStart, lineEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        }\n        isInternalChange = false\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    fun onBulletListClick() {\n        toggleListPrefix(\"• \")\n    }\n\n    fun onNumberedListClick() {\n        applyNumberedList()\n    }\n\n    fun onQuoteClick() {\n        toggleQuote()\n    }\n\n    fun onChecklistClick() {\n        toggleChecklistPrefix()\n    }\n\n    fun onLinkClick() {\n        promptInsertLink()\n    }\n\n    fun onUndoClick() {\n        undo()\n    }\n\n    fun onRedoClick() {\n        redo()\n    }\n\n    fun onClearFormattingClick() {\n        clearFormatting()\n    }\n\n    fun onIndentClick() {\n        indent()\n    }\n\n    fun onOutdentClick() {\n        outdent()\n    }\n\n    fun onAlignLeftClick() {\n        setAlignment(Layout.Alignment.ALIGN_NORMAL)\n    }\n\n    fun onAlignCenterClick() {\n        setAlignment(Layout.Alignment.ALIGN_CENTER)\n    }\n\n    fun onAlignRightClick() {\n        setAlignment(Layout.Alignment.ALIGN_OPPOSITE)\n    }\n\n    // ==================== Helper Methods ====================\n\n    /// Returns sub-ranges of `start..<end` that do NOT overlap any MentionSpan or URLSpan.\n    /// Used to skip mentions and links when applying inline formatting (Req 6.1).\n    private fun nonMentionRanges(start: Int, end: Int, spannable: Spanned): List<IntRange> {\n        val mentionSpans = spannable.getSpans(start, end, MentionSpan::class.java)\n        val urlSpans = spannable.getSpans(start, end, URLSpan::class.java)\n\n        val protectedRanges = mutableListOf<IntRange>()\n        mentionSpans.forEach {\n            protectedRanges.add(spannable.getSpanStart(it) until spannable.getSpanEnd(it))\n        }\n        urlSpans.forEach {\n            protectedRanges.add(spannable.getSpanStart(it) until spannable.getSpanEnd(it))\n        }\n        if (protectedRanges.isEmpty()) return listOf(start until end)\n        protectedRanges.sortBy { it.first }\n\n        val result = mutableListOf<IntRange>()\n        var cursor = start\n        for (pr in protectedRanges) {\n            if (cursor < pr.first) {\n                result.add(cursor until pr.first)\n            }\n            cursor = pr.last + 1 // IntRange.last is inclusive; advance past the protected range\n        }\n        if (cursor < end) {\n            result.add(cursor until end)\n        }\n        return result\n    }\n\n    private fun toggleStyle(style: Int) {\n        // Use saved selection if current selection is empty (can happen when clicking toolbar)\n        var start = selectionStart\n        var end = selectionEnd\n\n        if (start >= end && savedSelectionStart < savedSelectionEnd) {\n            start = savedSelectionStart\n            end = savedSelectionEnd\n            android.util.Log.d(\"RichTextEditor\", \"Using saved selection: start=$start, end=$end\")\n        }\n\n        // Clamp to current text length to prevent IndexOutOfBoundsException\n        val textLength = text?.length ?: 0\n        start = start.coerceIn(0, textLength)\n        end = end.coerceIn(start, textLength)\n\n        android.util.Log.d(\"RichTextEditor\", \"toggleStyle called: style=$style, start=$start, end=$end\")\n\n        // No selection: toggle pending style for type-ahead (Slack-style)\n        if (start >= end) {\n            val key = when (style) {\n                Typeface.BOLD -> \"bold\"\n                Typeface.ITALIC -> \"italic\"\n                else -> return\n            }\n\n            // Check if cursor is currently inside a span of this style\n            val spannable = text as? Spanned\n            val cursorPos = selectionStart\n            val isInsideStyle = if (spannable != null && cursorPos > 0) {\n                spannable.getSpans(cursorPos - 1, cursorPos, StyleSpan::class.java)\n                    .any { it.style == style }\n            } else false\n\n            if (isInsideStyle) {\n                // Inside styled text — toggle between explicitly-off and normal\n                if (explicitlyOffStyles.contains(key)) {\n                    explicitlyOffStyles.remove(key)\n                } else {\n                    explicitlyOffStyles.add(key)\n                    pendingStyles.remove(key)\n                }\n            } else {\n                // Not inside styled text — toggle pending on/off\n                if (pendingStyles.contains(key)) {\n                    pendingStyles.remove(key)\n                } else {\n                    pendingStyles.add(key)\n                    explicitlyOffStyles.remove(key)\n                }\n            }\n            pendingStylesInsertPos = selectionStart\n            emitActiveStyles()\n            return\n        }\n\n        val spannable = text as? Editable ?: return\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        val segments = nonMentionRanges(start, end, spannable)\n        if (segments.isEmpty()) return\n\n        val hasStyle = segments.any { seg ->\n            spannable.getSpans(seg.first, seg.last + 1, StyleSpan::class.java)\n                .any { it.style == style }\n        }\n\n        isInternalChange = true\n        if (hasStyle) {\n            android.util.Log.d(\"RichTextEditor\", \"Removing style $style\")\n            for (seg in segments) {\n                val segEnd = seg.last + 1 // IntRange.last is inclusive; span APIs need exclusive end\n                spannable.getSpans(seg.first, segEnd, StyleSpan::class.java)\n                    .filter { it.style == style }\n                    .forEach { spannable.removeSpan(it) }\n            }\n        } else {\n            android.util.Log.d(\"RichTextEditor\", \"Applying style $style\")\n            for (seg in segments) {\n                val segEnd = seg.last + 1 // IntRange.last is inclusive; span APIs need exclusive end\n                spannable.setSpan(StyleSpan(style), seg.first, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n        }\n        // Restore and maintain selection after applying style\n        setSelection(start, end)\n        isInternalChange = false\n        invalidate()\n        sendContentChange()\n        updateToolbarButtonStates()\n        emitActiveStyles()\n    }\n\n    private fun <T : Any> toggleSpan(spanClass: Class<T>, createSpan: () -> Any) {\n        // Use saved selection if current selection is empty\n        var start = selectionStart\n        var end = selectionEnd\n\n        if (start >= end && savedSelectionStart < savedSelectionEnd) {\n            start = savedSelectionStart\n            end = savedSelectionEnd\n        }\n\n        // Clamp to current text length to prevent IndexOutOfBoundsException\n        val textLength = text?.length ?: 0\n        start = start.coerceIn(0, textLength)\n        end = end.coerceIn(start, textLength)\n\n        // No selection: toggle pending style for type-ahead\n        if (start >= end) {\n            val key = when (spanClass) {\n                UnderlineSpan::class.java -> \"underline\"\n                StrikethroughSpan::class.java -> \"strikethrough\"\n                else -> return\n            }\n\n            // Check if cursor is currently inside a span of this type\n            val spannableText = text as? Spanned\n            val cursorPos = selectionStart\n            val isInsideStyle = if (spannableText != null && cursorPos > 0) {\n                spannableText.getSpans(cursorPos - 1, cursorPos, spanClass).isNotEmpty()\n            } else false\n\n            if (isInsideStyle) {\n                if (explicitlyOffStyles.contains(key)) {\n                    explicitlyOffStyles.remove(key)\n                } else {\n                    explicitlyOffStyles.add(key)\n                    pendingStyles.remove(key)\n                }\n            } else {\n                if (pendingStyles.contains(key)) {\n                    pendingStyles.remove(key)\n                } else {\n                    pendingStyles.add(key)\n                    explicitlyOffStyles.remove(key)\n                }\n            }\n            pendingStylesInsertPos = selectionStart\n            emitActiveStyles()\n            return\n        }\n\n        val spannable = text as? Editable ?: return\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        val segments = nonMentionRanges(start, end, spannable)\n        if (segments.isEmpty()) return\n\n        val hasSpan = segments.any { seg ->\n            spannable.getSpans(seg.first, seg.last + 1, spanClass).isNotEmpty()\n        }\n\n        isInternalChange = true\n        if (hasSpan) {\n            for (seg in segments) {\n                val segEnd = seg.last + 1 // IntRange.last is inclusive; span APIs need exclusive end\n                spannable.getSpans(seg.first, segEnd, spanClass)\n                    .forEach { spannable.removeSpan(it) }\n            }\n        } else {\n            for (seg in segments) {\n                val segEnd = seg.last + 1 // IntRange.last is inclusive; span APIs need exclusive end\n                spannable.setSpan(createSpan(), seg.first, segEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n        }\n        // Restore and maintain selection after applying style\n        setSelection(start, end)\n        isInternalChange = false\n        invalidate()\n        sendContentChange()\n        updateToolbarButtonStates()\n        emitActiveStyles()\n    }\n\n    private fun toggleListPrefix(prefix: String) {\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val s = text as? Editable ?: return\n\n        // Mutual exclusivity: if code block is active, remove it first (Req 14.1, 14.3)\n        val existingMonospace = s.getSpans(lineStart, lineEnd, TypefaceSpan::class.java)\n            .filter { it.family == \"monospace\" }\n        if (existingMonospace.isNotEmpty()) {\n            isInternalChange = true\n            existingMonospace.forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, CodeBlockBorderSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, BackgroundColorSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, UnderlineSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, ForegroundColorSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n            isInternalChange = false\n        } else if (pendingStyles.contains(\"codeBlock\")) {\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n        }\n\n        val selectedText = currentText.substring(lineStart, lineEnd)\n        val lines = selectedText.split(\"\\n\")\n\n        val numberedRegex = Regex(\"^\\\\d+\\\\.\\\\s\")\n\n        // Check if all non-empty lines already have this prefix (accounting for optional blockquote)\n        val hasNonEmptyLines = lines.any { it.trimEnd().isNotEmpty() }\n        val allHavePrefix = hasNonEmptyLines && lines.all { line ->\n            if (line.trimEnd().isEmpty()) return@all true\n            val content = if (line.startsWith(\"▎ \")) line.substring(2) else line\n            content.startsWith(prefix)\n        }\n\n        val newLines = lines.map { line ->\n            // Preserve blockquote prefix if present\n            val hasQuote = line.startsWith(\"▎ \")\n            val quoteStr = if (hasQuote) \"▎ \" else \"\"\n            val lineContent = if (hasQuote) line.substring(2) else line\n\n            if (allHavePrefix) {\n                // Removing prefix — skip empty lines, preserve blockquote\n                if (line.trimEnd().isEmpty()) return@map line\n                if (lineContent.startsWith(prefix)) {\n                    quoteStr + lineContent.substring(prefix.length)\n                } else {\n                    line\n                }\n            } else {\n                // Adding prefix — preserve blockquote, strip other prefixes (not blockquote)\n                val cleanLine = removeExistingPrefixKeepQuote(lineContent)\n                quoteStr + prefix + cleanLine\n            }\n        }\n\n        val newText = newLines.joinToString(\"\\n\")\n\n        // Snapshot mention spans before text modification (Req 5.1, 5.2, 5.7)\n        val mentionSnapshots = snapshotMentionSpans(lineStart, lineEnd)\n\n        isInternalChange = true\n        val editable = text ?: return\n        editable.replace(lineStart, lineEnd, newText)\n\n        // Restore mention spans at adjusted positions\n        if (mentionSnapshots.isNotEmpty()) {\n            val offsetDelta = newText.length - (lineEnd - lineStart)\n            restoreMentionSpans(editable, mentionSnapshots, offsetDelta)\n        }\n\n        setSelection(lineStart + newText.length)\n        renumberNumberedLists()\n        isInternalChange = false\n        applyListIndentation()\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    private fun applyNumberedList() {\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val s = text as? Editable ?: return\n\n        // Mutual exclusivity: if code block is active, remove it first (Req 14.2, 14.3)\n        val existingMonospace = s.getSpans(lineStart, lineEnd, TypefaceSpan::class.java)\n            .filter { it.family == \"monospace\" }\n        if (existingMonospace.isNotEmpty()) {\n            isInternalChange = true\n            existingMonospace.forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, CodeBlockBorderSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, BackgroundColorSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, UnderlineSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, ForegroundColorSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n            isInternalChange = false\n        } else if (pendingStyles.contains(\"codeBlock\")) {\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n        }\n\n        val selectedText = currentText.substring(lineStart, lineEnd)\n        val lines = selectedText.split(\"\\n\")\n\n        val numberedRegex = Regex(\"^\\\\d+\\\\.\\\\s\")\n\n        // Check if all non-empty lines already have numbered prefix (accounting for optional blockquote)\n        val hasNonEmptyLines = lines.any { it.trimEnd().isNotEmpty() }\n        val allHaveNumbered = hasNonEmptyLines && lines.all { line ->\n            if (line.trimEnd().isEmpty()) return@all true\n            val content = if (line.startsWith(\"▎ \")) line.substring(2) else line\n            numberedRegex.containsMatchIn(content)\n        }\n\n        val newLines = lines.mapIndexed { index, line ->\n            // Preserve blockquote prefix if present\n            val hasQuote = line.startsWith(\"▎ \")\n            val quoteStr = if (hasQuote) \"▎ \" else \"\"\n            val lineContent = if (hasQuote) line.substring(2) else line\n\n            if (allHaveNumbered) {\n                // Removing numbered prefix — skip empty lines, preserve blockquote\n                if (line.trimEnd().isEmpty()) return@mapIndexed line\n                val match = numberedRegex.find(lineContent)\n                if (match != null) quoteStr + lineContent.substring(match.value.length) else line\n            } else {\n                // Adding numbered prefix — preserve blockquote, strip other prefixes\n                val cleanLine = removeExistingPrefixKeepQuote(lineContent)\n                \"$quoteStr${index + 1}. $cleanLine\"\n            }\n        }\n\n        val newText = newLines.joinToString(\"\\n\")\n\n        // Snapshot mention spans before text modification (Req 5.1, 5.2, 5.7)\n        val mentionSnapshots = snapshotMentionSpans(lineStart, lineEnd)\n\n        isInternalChange = true\n        val editable = text ?: return\n        editable.replace(lineStart, lineEnd, newText)\n\n        // Restore mention spans at adjusted positions\n        if (mentionSnapshots.isNotEmpty()) {\n            val offsetDelta = newText.length - (lineEnd - lineStart)\n            restoreMentionSpans(editable, mentionSnapshots, offsetDelta)\n        }\n\n        setSelection(lineStart + newText.length)\n        renumberNumberedLists()\n        isInternalChange = false\n        applyListIndentation()\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    private fun detectBackspaceInListPrefix(s: CharSequence?, start: Int, count: Int, after: Int) {\n        pendingPrefixDeletion = null\n        if (isInternalChange || s == null || count != 1 || after != 0) return\n\n        val text = s.toString()\n        var lineStart = start\n        while (lineStart > 0 && text[lineStart - 1] != '\\n') lineStart--\n\n        val lineEnd = text.indexOf('\\n', lineStart).let { if (it == -1) text.length else it }\n        val line = text.substring(lineStart, lineEnd)\n\n        val numberedMatch = Regex(\"^(\\\\d+)\\\\.\\\\s\").find(line)\n        if (numberedMatch != null && start < lineStart + numberedMatch.value.length) {\n            pendingPrefixDeletion = Pair(lineStart, numberedMatch.value.length)\n            return\n        }\n        if ((line.startsWith(\"• \") || line.startsWith(\"☐ \") || line.startsWith(\"☑ \") || line.startsWith(\"▎ \")) && start < lineStart + 2) {\n            pendingPrefixDeletion = Pair(lineStart, 2)\n            return\n        }\n    }\n\n    private fun handleBackspaceInListPrefix(s: Editable?): Boolean {\n        val deletion = pendingPrefixDeletion ?: return false\n        pendingPrefixDeletion = null\n        if (s == null) return false\n\n        val (origLineStart, origPrefixLen) = deletion\n        // After the single-char delete, the remaining prefix starts at the same lineStart\n        // but is now origPrefixLen - 1 chars long\n        val remainingLen = origPrefixLen - 1\n        if (remainingLen <= 0) return false\n        if (origLineStart + remainingLen > s.length) return false\n\n        isInternalChange = true\n        s.delete(origLineStart, origLineStart + remainingLen)\n        setSelection(origLineStart)\n        renumberNumberedLists()\n        isInternalChange = false\n        return true\n    }\n\n    /**\n     * Detects three consecutive Enter presses inside a code block and exits code block mode.\n     * Removes the trailing three newlines, clears CodeBlockBorderSpan and TypefaceSpan(\"monospace\")\n     * from the last code block line, removes \"codeBlock\" from pendingStyles, and emits updated\n     * active styles. Follows the same isInternalChange guard pattern as autoContinueListOnEnter().\n     */\n    private fun checkTripleEnterExitCodeBlock(s: Editable?): Boolean {\n        if (s == null) return false\n        val cursorPos = selectionStart\n        if (cursorPos < 3 || cursorPos > s.length) return false\n\n        // Check that the last 3 characters at cursor are all newlines\n        if (s[cursorPos - 1] != '\\n' || s[cursorPos - 2] != '\\n' || s[cursorPos - 3] != '\\n') return false\n\n        // Check if we're inside a code block — either via pendingStyles or CodeBlockBorderSpan\n        val inCodeBlock = pendingStyles.contains(\"codeBlock\") ||\n            (cursorPos > 3 && s.getSpans(cursorPos - 4, cursorPos - 3, CodeBlockBorderSpan::class.java).isNotEmpty())\n        if (!inCodeBlock) return false\n\n        isInternalChange = true\n\n        // Remove the trailing 3 newlines\n        val newCursorPos = cursorPos - 3\n        s.delete(newCursorPos, cursorPos)\n\n        // Keep CodeBlockBorderSpan and TypefaceSpan on the last content line —\n        // it's still valid code block content. Instead, insert a newline after\n        // the code block and place the cursor on the fresh normal line.\n        s.insert(newCursorPos, \"\\n\")\n        val freshLinePos = newCursorPos + 1\n\n        // Trim code block spans so they end at newCursorPos (the \\n boundary),\n        // preventing the EXCLUSIVE_INCLUSIVE flag from absorbing the new line.\n        s.getSpans(newCursorPos, freshLinePos, CodeBlockBorderSpan::class.java).forEach { span ->\n            val spanStart = s.getSpanStart(span)\n            val spanEnd = s.getSpanEnd(span)\n            if (spanEnd > newCursorPos) {\n                s.removeSpan(span)\n                if (spanStart < newCursorPos) {\n                    s.setSpan(CodeBlockBorderSpan(density), spanStart, newCursorPos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                }\n            }\n        }\n        s.getSpans(newCursorPos, freshLinePos, TypefaceSpan::class.java)\n            .filter { it.family == \"monospace\" }\n            .forEach { span ->\n                val spanStart = s.getSpanStart(span)\n                val spanEnd = s.getSpanEnd(span)\n                if (spanEnd > newCursorPos) {\n                    s.removeSpan(span)\n                    if (spanStart < newCursorPos) {\n                        s.setSpan(TypefaceSpan(\"monospace\"), spanStart, newCursorPos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n                    }\n                }\n            }\n\n        // Exit code block mode\n        pendingStyles.remove(\"codeBlock\")\n        suppressCodeBlockShortcut = false\n        setSelection(freshLinePos.coerceAtMost(s.length))\n\n        isInternalChange = false\n\n        emitActiveStyles()\n        sendContentChangeWithDelta()\n        updateCodeBlockEdgePadding()\n        updateContentSize()\n        return true\n    }\n\n    private fun autoContinueListOnEnter(s: Editable?): Boolean {\n        if (s == null) return false\n        val cursorPos = selectionStart\n        if (cursorPos <= 0 || cursorPos > s.length) return false\n        if (s[cursorPos - 1] != '\\n') return false\n\n        if (cursorPos >= 2 && s[cursorPos - 2] == '\\n') return false\n\n        val text = s.toString()\n        var prevLineStart = cursorPos - 2\n        while (prevLineStart > 0 && text[prevLineStart - 1] != '\\n') prevLineStart--\n        if (prevLineStart < 0) prevLineStart = 0\n\n        val prevLine = text.substring(prevLineStart, cursorPos - 1)\n\n        // Combined blockquote + list prefixes (ENG-31998): must check before individual prefixes\n        val quoteNumberedMatch = Regex(\"^▎ (\\\\d+)\\\\.\\\\s\").find(prevLine)\n        if (quoteNumberedMatch != null) {\n            val content = prevLine.substring(quoteNumberedMatch.value.length)\n            if (content.isBlank()) {\n                isInternalChange = true\n                s.delete(prevLineStart, cursorPos)\n                setSelection(prevLineStart.coerceAtMost(s.length))\n                isInternalChange = false\n                return true\n            }\n            val nextNum = (quoteNumberedMatch.groupValues[1].toIntOrNull() ?: 0) + 1\n            val prefix = \"▎ $nextNum. \"\n            isInternalChange = true\n            s.insert(cursorPos, prefix)\n            // Make ▎ invisible — bar is custom-drawn in onDraw()\n            s.setSpan(ForegroundColorSpan(Color.TRANSPARENT), cursorPos, cursorPos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            setSelection(cursorPos + prefix.length)\n            renumberNumberedLists()\n            isInternalChange = false\n            return true\n        }\n\n        if (prevLine.startsWith(\"▎ • \")) {\n            val content = prevLine.substring(4)\n            if (content.isBlank()) {\n                isInternalChange = true\n                s.delete(prevLineStart, cursorPos)\n                setSelection(prevLineStart.coerceAtMost(s.length))\n                isInternalChange = false\n                return true\n            }\n            isInternalChange = true\n            s.insert(cursorPos, \"▎ • \")\n            // Make ▎ invisible — bar is custom-drawn in onDraw()\n            s.setSpan(ForegroundColorSpan(Color.TRANSPARENT), cursorPos, cursorPos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            setSelection(cursorPos + 4)\n            isInternalChange = false\n            return true\n        }\n\n        val numberedMatch = Regex(\"^(\\\\d+)\\\\.\\\\s\").find(prevLine)\n        if (numberedMatch != null) {\n            val content = prevLine.substring(numberedMatch.value.length)\n            if (content.isBlank()) {\n                isInternalChange = true\n                s.delete(prevLineStart, cursorPos)\n                setSelection(prevLineStart.coerceAtMost(s.length))\n                isInternalChange = false\n                return true\n            }\n            val nextNum = (numberedMatch.groupValues[1].toIntOrNull() ?: 0) + 1\n            val prefix = \"$nextNum. \"\n            isInternalChange = true\n            s.insert(cursorPos, prefix)\n            setSelection(cursorPos + prefix.length)\n            renumberNumberedLists()\n            isInternalChange = false\n            return true\n        }\n\n        if (prevLine.startsWith(\"• \")) {\n            val content = prevLine.substring(2)\n            if (content.isBlank()) {\n                isInternalChange = true\n                s.delete(prevLineStart, cursorPos)\n                setSelection(prevLineStart.coerceAtMost(s.length))\n                isInternalChange = false\n                return true\n            }\n            isInternalChange = true\n            s.insert(cursorPos, \"• \")\n            setSelection(cursorPos + 2)\n            isInternalChange = false\n            return true\n        }\n\n        if (prevLine.startsWith(\"☐ \") || prevLine.startsWith(\"☑ \")) {\n            val content = prevLine.substring(2)\n            if (content.isBlank()) {\n                isInternalChange = true\n                s.delete(prevLineStart, cursorPos)\n                setSelection(prevLineStart.coerceAtMost(s.length))\n                isInternalChange = false\n                return true\n            }\n            isInternalChange = true\n            s.insert(cursorPos, \"☐ \")\n            setSelection(cursorPos + 2)\n            isInternalChange = false\n            return true\n        }\n\n        if (prevLine.startsWith(\"▎ \")) {\n            val content = prevLine.substring(2)\n            if (content.isBlank()) {\n                isInternalChange = true\n                s.delete(prevLineStart, cursorPos)\n                setSelection(prevLineStart.coerceAtMost(s.length))\n                isInternalChange = false\n                return true\n            }\n            isInternalChange = true\n            s.insert(cursorPos, \"▎ \")\n            // Make ▎ invisible — bar is custom-drawn in onDraw()\n            s.setSpan(ForegroundColorSpan(Color.TRANSPARENT), cursorPos, cursorPos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            setSelection(cursorPos + 2)\n            isInternalChange = false\n            return true\n        }\n\n        return false\n    }\n\n    /// Scan all lines and renumber contiguous numbered-list blocks sequentially,\n    /// continuing across bullet-list interruptions (Slack behavior).\n    /// Bullet lines (- or • or ‧ prefix) are skipped without resetting the counter.\n    /// Any other non-matching line (blank, regular text, quote, etc.) ends the block\n    /// and resets the counter for the next block.\n    /// Requirements: 4.1, 4.2, 4.3\n    private fun renumberNumberedLists() {\n        val editable = text ?: return\n        val fullText = editable.toString()\n        if (fullText.isEmpty()) return\n\n        val lines = fullText.split(\"\\n\")\n        val numberedRegex = Regex(\"^(\\\\d+)\\\\.\\\\s\")\n        val quoteNumberedRegex = Regex(\"^▎ (\\\\d+)\\\\.\\\\s\")\n        val bulletRegex = Regex(\"^(- |• |‧ |▎ - |▎ • |▎ ‧ )\")\n\n        var counter = 0\n        var offset = 0\n        val replacements = mutableListOf<Triple<Int, Int, String>>()\n\n        for (line in lines) {\n            val quoteMatch = quoteNumberedRegex.find(line)\n            val match = numberedRegex.find(line)\n            if (quoteMatch != null) {\n                // Blockquote + numbered: \"▎ N. \" — renumber preserving quote prefix\n                counter++\n                val oldPrefix = quoteMatch.value\n                val newPrefix = \"▎ $counter. \"\n                if (oldPrefix != newPrefix) {\n                    replacements.add(Triple(offset, oldPrefix.length, newPrefix))\n                }\n            } else if (match != null) {\n                counter++\n                val oldPrefix = match.value\n                val newPrefix = \"$counter. \"\n                if (oldPrefix != newPrefix) {\n                    replacements.add(Triple(offset, oldPrefix.length, newPrefix))\n                }\n            } else if (bulletRegex.containsMatchIn(line)) {\n                // Bullet lines don't reset the counter — ordered numbering\n                // continues across bullet interruptions (Slack behavior).\n            } else {\n                counter = 0\n            }\n            offset += line.length + 1\n        }\n\n        if (replacements.isNotEmpty()) {\n            val cursorPos = selectionStart\n\n            // Compute cursor shift caused by prefix length changes before the cursor\n            var cursorDelta = 0\n            for ((start, length, newPrefix) in replacements) {\n                if (start + length <= cursorPos) {\n                    cursorDelta += newPrefix.length - length\n                }\n            }\n\n            // Apply replacements in reverse order to preserve earlier offsets\n            for ((start, length, newPrefix) in replacements.reversed()) {\n                editable.replace(start, start + length, newPrefix)\n            }\n\n            // Restore cursor position adjusted for any prefix length changes\n            val adjustedCursor = (cursorPos + cursorDelta).coerceIn(0, editable.length)\n            setSelection(adjustedCursor)\n        }\n    }\n\n    /**\n     * Snapshots all MentionSpan instances within [start, end) so they can be\n     * restored at adjusted positions after a block-formatter text modification.\n     */\n    private fun snapshotMentionSpans(start: Int, end: Int): List<Triple<Int, Int, MentionSpan>> {\n        val s = text as? Spanned ?: return emptyList()\n        return s.getSpans(start, end, MentionSpan::class.java).map { span ->\n            Triple(s.getSpanStart(span), s.getSpanEnd(span), span)\n        }\n    }\n\n    /**\n     * Re-applies snapshotted MentionSpans at positions shifted by [offsetDelta]\n     * (e.g. after a block-format prefix insertion/removal).\n     */\n    private fun restoreMentionSpans(\n        editable: Editable,\n        snapshots: List<Triple<Int, Int, MentionSpan>>,\n        offsetDelta: Int\n    ) {\n        for ((origStart, origEnd, span) in snapshots) {\n            val newStart = origStart + offsetDelta\n            val newEnd = origEnd + offsetDelta\n            if (newStart >= 0 && newEnd <= editable.length) {\n                editable.setSpan(span, newStart, newEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            }\n        }\n    }\n\n    private fun toggleQuote() {\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val lineText = currentText.substring(lineStart, lineEnd)\n        val s = text as? Editable ?: return\n\n        val quotePrefix = \"▎ \"\n\n        // Mutual exclusivity: if code block is active, remove it first (Req 13.2)\n        val existingMonospace = s.getSpans(lineStart, lineEnd, TypefaceSpan::class.java)\n            .filter { it.family == \"monospace\" }\n        if (existingMonospace.isNotEmpty()) {\n            isInternalChange = true\n            existingMonospace.forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, CodeBlockBorderSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, BackgroundColorSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            // Also clear inline formatting spans left from code block\n            s.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, UnderlineSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, ForegroundColorSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            // Reset pending code block styles\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n            isInternalChange = false\n        } else if (pendingStyles.contains(\"codeBlock\")) {\n            // Code block active via pending styles on empty line\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n        }\n\n        // Snapshot mention spans before text modification (Req 5.1, 5.3, 5.7)\n        val mentionSnapshots = snapshotMentionSpans(lineStart, lineEnd)\n\n        isInternalChange = true\n        val editable = text ?: return\n\n        if (lineText.startsWith(quotePrefix)) {\n            // Remove quote prefix — preserve any list prefix underneath\n            val unquoted = lineText.substring(quotePrefix.length)\n            editable.replace(lineStart, lineEnd, unquoted)\n            // Restore mention spans shifted back by prefix length\n            if (mentionSnapshots.isNotEmpty()) {\n                restoreMentionSpans(editable, mentionSnapshots, -quotePrefix.length)\n            }\n        } else {\n            // Add quote prefix — preserve existing list prefix\n            editable.replace(lineStart, lineEnd, \"$quotePrefix$lineText\")\n            // Make ▎ invisible — bar is custom-drawn in onDraw()\n            editable.setSpan(ForegroundColorSpan(Color.TRANSPARENT), lineStart, lineStart + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n            // Restore mention spans shifted forward by prefix length\n            if (mentionSnapshots.isNotEmpty()) {\n                restoreMentionSpans(editable, mentionSnapshots, quotePrefix.length)\n            }\n        }\n        isInternalChange = false\n        applyListIndentation()\n        sendContentChange()\n        updateToolbarButtonStates() // reflect mutually exclusive state (Req 13.3)\n    }\n\n    private fun toggleChecklistPrefix() {\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val lineText = currentText.substring(lineStart, lineEnd)\n        val s = text as? Editable ?: return\n\n        // Mutual exclusivity: if code block is active, remove it first (Req 14.1, 14.3)\n        val existingMonospace = s.getSpans(lineStart, lineEnd, TypefaceSpan::class.java)\n            .filter { it.family == \"monospace\" }\n        if (existingMonospace.isNotEmpty()) {\n            isInternalChange = true\n            existingMonospace.forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, CodeBlockBorderSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, BackgroundColorSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, UnderlineSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, ForegroundColorSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n            isInternalChange = false\n        } else if (pendingStyles.contains(\"codeBlock\")) {\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n        }\n\n        isInternalChange = true\n        val editable = text ?: return\n\n        when {\n            lineText.startsWith(\"☐ \") -> {\n                // Remove checklist\n                editable.delete(lineStart, lineStart + 2)\n            }\n            lineText.startsWith(\"☑ \") -> {\n                // Remove checklist\n                editable.delete(lineStart, lineStart + 2)\n            }\n            else -> {\n                // Add checklist\n                val cleanLine = removeExistingPrefix(lineText)\n                editable.replace(lineStart, lineEnd, \"☐ $cleanLine\")\n            }\n        }\n        isInternalChange = false\n        renumberNumberedLists()\n        applyListIndentation()\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    private fun removeExistingPrefix(line: String): String {\n        return when {\n            line.startsWith(\"• \") -> line.substring(2)\n            line.startsWith(\"☐ \") || line.startsWith(\"☑ \") -> line.substring(2)\n            line.matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) -> line.replace(Regex(\"^\\\\d+\\\\.\\\\s\"), \"\")\n            line.startsWith(\"\\\"\") && line.endsWith(\"\\\"\") && line.length >= 2 -> line.substring(1, line.length - 1)\n            line.startsWith(\"▎ \") -> line.substring(2)\n            else -> line\n        }\n    }\n\n    /** Strips list/checklist prefixes but preserves blockquote prefix. */\n    private fun removeExistingPrefixKeepQuote(line: String): String {\n        return when {\n            line.startsWith(\"• \") -> line.substring(2)\n            line.startsWith(\"☐ \") || line.startsWith(\"☑ \") -> line.substring(2)\n            line.matches(Regex(\"^\\\\d+\\\\.\\\\s.*\")) -> line.replace(Regex(\"^\\\\d+\\\\.\\\\s\"), \"\")\n            else -> line\n        }\n    }\n\n    /**\n     * Applies LeadingMarginSpan to lines with list/blockquote prefixes so that\n     * wrapped long text indents past the prefix (hanging indent). Also makes\n     * the ▎ blockquote character invisible since the bar is custom-drawn in onDraw().\n     * Mirrors iOS applyListIndentation() behavior (ENG-31433).\n     */\n    private fun applyListIndentation() {\n        val s = text as? Editable ?: return\n        val fullText = s.toString()\n        if (fullText.isEmpty()) return\n\n        val textPaint = paint\n\n        // Remove all existing LeadingMarginSpan.Standard spans first\n        s.getSpans(0, s.length, LeadingMarginSpan.Standard::class.java).forEach { s.removeSpan(it) }\n\n        val lines = fullText.split(\"\\n\")\n        var offset = 0\n\n        isInternalChange = true\n        for ((idx, line) in lines.withIndex()) {\n            val lineEnd = offset + line.length\n            val lineRange = offset until lineEnd.coerceAtMost(s.length)\n\n            if (lineRange.isEmpty() || lineRange.first >= s.length) {\n                offset = lineEnd + 1\n                continue\n            }\n\n            val safeEnd = lineRange.last.coerceAtMost(s.length - 1) + 1\n            val safeStart = lineRange.first\n\n            var prefixWidth = 0f\n            when {\n                line.startsWith(\"▎ • \") -> {\n                    // Combined blockquote + bullet\n                    prefixWidth = textPaint.measureText(\"▎ • \")\n                }\n                line.startsWith(\"▎ \") && quoteNumberedPrefixRegex.containsMatchIn(line) -> {\n                    // Combined blockquote + numbered: \"▎ N. \"\n                    val match = quoteNumberedPrefixRegex.find(line)!!\n                    prefixWidth = textPaint.measureText(match.value)\n                }\n                line.startsWith(\"▎ \") -> {\n                    // Plain blockquote\n                    prefixWidth = textPaint.measureText(\"▎ \")\n                }\n                line.startsWith(\"• \") -> {\n                    prefixWidth = textPaint.measureText(\"• \")\n                }\n                numberedPrefixRegex.containsMatchIn(line) -> {\n                    val match = numberedPrefixRegex.find(line)!!\n                    prefixWidth = textPaint.measureText(match.value)\n                }\n                line.startsWith(\"☐ \") || line.startsWith(\"☑ \") -> {\n                    prefixWidth = textPaint.measureText(\"☐ \")\n                }\n            }\n\n            if (prefixWidth > 0f && safeStart < safeEnd) {\n                s.setSpan(\n                    LeadingMarginSpan.Standard(0, prefixWidth.toInt()),\n                    safeStart, safeEnd,\n                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                )\n            }\n\n            // Code block horizontal padding — inset text from container edges (matches iOS)\n            val isCodeBlockLine = prefixWidth == 0f && safeStart < safeEnd\n                && s.getSpans(safeStart, safeEnd, CodeBlockBorderSpan::class.java).isNotEmpty()\n            if (isCodeBlockLine) {\n                val hPadPx = (CodeBlockBorderSpan.PADDING * density).toInt()\n                s.setSpan(\n                    LeadingMarginSpan.Standard(hPadPx, hPadPx),\n                    safeStart, safeEnd,\n                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                )\n\n                // Determine first/last line status for this code block line.\n                // A line is \"first\" if the previous line is NOT a code block line.\n                // A line is \"last\" if the next line is NOT a code block line.\n                val prevIsCodeBlock = if (idx > 0) {\n                    val prevOffset = offset - lines[idx - 1].length - 1\n                    val prevEnd = prevOffset + lines[idx - 1].length\n                    prevOffset >= 0 && prevEnd > prevOffset\n                        && s.getSpans(prevOffset, prevEnd.coerceAtMost(s.length), CodeBlockBorderSpan::class.java).isNotEmpty()\n                } else false\n\n                val nextIsCodeBlock = if (idx + 1 < lines.size) {\n                    val nextOffset = lineEnd + 1\n                    val nextLineEnd = nextOffset + lines[idx + 1].length\n                    nextOffset < s.length && nextLineEnd > nextOffset\n                        && s.getSpans(nextOffset, nextLineEnd.coerceAtMost(s.length), CodeBlockBorderSpan::class.java).isNotEmpty()\n                } else false\n\n                val isFirst = !prevIsCodeBlock\n                val isLast = !nextIsCodeBlock\n\n                // Set region-awareness flags on all CodeBlockBorderSpan instances for this line\n                val cbSpans = s.getSpans(safeStart, safeEnd, CodeBlockBorderSpan::class.java)\n                val dark = isDarkMode()\n                for (cb in cbSpans) {\n                    cb.isFirstLine = isFirst\n                    cb.isLastLine = isLast\n                    cb.density = density\n                    cb.isDark = dark\n                }\n            } else if (safeStart < safeEnd) {\n                // Non-code-block lines don't need special spacing —\n                // vertical padding is purely visual via vPadPx in onDraw().\n            }\n\n            // Make ▎ invisible — bar is custom-drawn in onDraw()\n            if (line.startsWith(\"▎\") && safeStart < s.length) {\n                s.setSpan(\n                    ForegroundColorSpan(Color.TRANSPARENT),\n                    safeStart, safeStart + 1,\n                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                )\n            }\n\n            offset = lineEnd + 1\n        }\n        isInternalChange = false\n    }\n\n    private fun promptInsertLink() {\n        val context = context\n        val builder = AlertDialog.Builder(context)\n        builder.setTitle(\"Insert Link\")\n\n        val layout = LinearLayout(context).apply {\n            orientation = LinearLayout.VERTICAL\n            setPadding(50, 40, 50, 10)\n        }\n\n        val textInput = EditText(context).apply {\n            hint = \"Link text\"\n            // Pre-fill with selected text\n            val selectedText = text?.subSequence(selectionStart, selectionEnd)?.toString() ?: \"\"\n            setText(selectedText)\n        }\n\n        val urlInput = EditText(context).apply {\n            hint = \"URL\"\n        }\n\n        layout.addView(textInput)\n        layout.addView(urlInput)\n        builder.setView(layout)\n\n        builder.setPositiveButton(\"Insert\") { _, _ ->\n            val linkText = textInput.text.toString()\n            val url = urlInput.text.toString()\n            if (linkText.isNotEmpty() && url.isNotEmpty()) {\n                insertLink(url, linkText)\n            }\n        }\n        builder.setNegativeButton(\"Cancel\", null)\n        builder.show()\n    }\n\n    fun insertLink(url: String, linkText: String) {\n        val start = selectionStart\n        val end = selectionEnd\n        val editable = text ?: return\n\n        // Normalize URL: add https:// if no scheme present\n        val normalizedUrl = if (!url.lowercase().startsWith(\"http://\") &&\n            !url.lowercase().startsWith(\"https://\") &&\n            !url.lowercase().startsWith(\"mailto:\") &&\n            !url.lowercase().startsWith(\"tel:\")) {\n            \"https://$url\"\n        } else url\n\n        isInternalChange = true\n        if (start != end) {\n            editable.delete(start, end)\n        }\n\n        val linkSpannable = SpannableStringBuilder(linkText)\n        linkSpannable.setSpan(NoUnderlineURLSpan(normalizedUrl), 0, linkText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        linkSpannable.setSpan(ForegroundColorSpan(Color.parseColor(\"#2196F3\")), 0, linkText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n\n        editable.insert(start, linkSpannable)\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    fun removeLink(location: Int, length: Int) {\n        val editable = text ?: return\n        val end = location + length\n        if (location < 0 || end > editable.length) return\n\n        isInternalChange = true\n        // Remove URLSpan, link color, and underline spans within the range while preserving display text\n        editable.getSpans(location, end, URLSpan::class.java).forEach { editable.removeSpan(it) }\n        editable.getSpans(location, end, ForegroundColorSpan::class.java).forEach { span ->\n            val ss = editable.getSpanStart(span)\n            val se = editable.getSpanEnd(span)\n            if (ss >= location && se <= end) {\n                editable.removeSpan(span)\n            }\n        }\n        editable.getSpans(location, end, UnderlineSpan::class.java).forEach { span ->\n            val ss = editable.getSpanStart(span)\n            val se = editable.getSpanEnd(span)\n            if (ss >= location && se <= end) {\n                editable.removeSpan(span)\n            }\n        }\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    fun updateLink(location: Int, length: Int, newUrl: String, newText: String) {\n        val editable = text ?: return\n        val end = location + length\n        if (location < 0 || end > editable.length) return\n\n        // Normalize URL: add https:// if no scheme present\n        val normalizedUrl = if (!newUrl.lowercase().startsWith(\"http://\") &&\n            !newUrl.lowercase().startsWith(\"https://\") &&\n            !newUrl.lowercase().startsWith(\"mailto:\") &&\n            !newUrl.lowercase().startsWith(\"tel:\")) {\n            \"https://$newUrl\"\n        } else newUrl\n\n        isInternalChange = true\n        // Remove existing link spans in the range\n        editable.getSpans(location, end, URLSpan::class.java).forEach { editable.removeSpan(it) }\n        editable.getSpans(location, end, ForegroundColorSpan::class.java).forEach { span ->\n            val ss = editable.getSpanStart(span)\n            val se = editable.getSpanEnd(span)\n            if (ss >= location && se <= end) {\n                editable.removeSpan(span)\n            }\n        }\n        editable.getSpans(location, end, UnderlineSpan::class.java).forEach { span ->\n            val ss = editable.getSpanStart(span)\n            val se = editable.getSpanEnd(span)\n            if (ss >= location && se <= end) {\n                editable.removeSpan(span)\n            }\n        }\n\n        // Replace text in range with new display text\n        editable.replace(location, end, newText)\n\n        // Apply new link spans on the replaced text\n        val newEnd = location + newText.length\n        editable.setSpan(NoUnderlineURLSpan(normalizedUrl), location, newEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        editable.setSpan(ForegroundColorSpan(Color.parseColor(\"#2196F3\")), location, newEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    fun undo() {\n        if (undoStack.size > 1) {\n            val current = undoStack.removeAt(undoStack.size - 1)\n            redoStack.add(current)\n            val previous = undoStack.last()\n\n            isInternalChange = true\n            setText(previous)\n            setSelection(previous.length)\n            lastSavedText = previous.toString()\n            isInternalChange = false\n            sendContentChange()\n        }\n    }\n\n    fun redo() {\n        if (redoStack.isNotEmpty()) {\n            val next = redoStack.removeAt(redoStack.size - 1)\n            undoStack.add(next)\n\n            isInternalChange = true\n            setText(next)\n            setSelection(next.length)\n            lastSavedText = next.toString()\n            isInternalChange = false\n            sendContentChange()\n        }\n    }\n\n    fun clearFormatting() {\n        // Use saved selection if current selection is empty\n        var start = selectionStart\n        var end = selectionEnd\n\n        if (start >= end && savedSelectionStart < savedSelectionEnd) {\n            start = savedSelectionStart\n            end = savedSelectionEnd\n        }\n\n        // Clamp to current text length\n        val textLength = text?.length ?: 0\n        start = start.coerceIn(0, textLength)\n        end = end.coerceIn(start, textLength)\n\n        if (start >= end) return\n\n        val spannable = text as? Editable ?: return\n\n        isInternalChange = true\n        // Remove all spans in selection\n        spannable.getSpans(start, end, Any::class.java).forEach { span ->\n            if (span is StyleSpan || span is UnderlineSpan || span is StrikethroughSpan ||\n                span is BackgroundColorSpan || span is ForegroundColorSpan || span is TypefaceSpan ||\n                span is RelativeSizeSpan || span is URLSpan || span is InlineCodeSpan ||\n                span is AbsoluteSizeSpan) {\n                spannable.removeSpan(span)\n            }\n        }\n        setSelection(start, end)\n        isInternalChange = false\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    fun indent() {\n        val (lineStart, _) = getLineRange()\n        val editable = text ?: return\n\n        isInternalChange = true\n        editable.insert(lineStart, \"    \")\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    fun outdent() {\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val lineText = currentText.substring(lineStart, lineEnd)\n        val editable = text ?: return\n\n        isInternalChange = true\n        when {\n            lineText.startsWith(\"    \") -> editable.delete(lineStart, lineStart + 4)\n            lineText.startsWith(\"\\t\") -> editable.delete(lineStart, lineStart + 1)\n            lineText.startsWith(\" \") -> {\n                var spaces = 0\n                for (c in lineText) {\n                    if (c == ' ' && spaces < 4) spaces++ else break\n                }\n                if (spaces > 0) editable.delete(lineStart, lineStart + spaces)\n            }\n        }\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    fun setAlignment(alignment: Layout.Alignment) {\n        val (lineStart, lineEnd) = getLineRange()\n        val spannable = text as? Editable ?: return\n\n        isInternalChange = true\n        // Remove existing alignment spans\n        spannable.getSpans(lineStart, lineEnd, AlignmentSpan.Standard::class.java)\n            .forEach { spannable.removeSpan(it) }\n\n        spannable.setSpan(AlignmentSpan.Standard(alignment), lineStart, lineEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\n        isInternalChange = false\n        sendContentChange()\n        updateToolbarButtonStates()\n    }\n\n    // Legacy method names for ViewManager commands\n    fun toggleBold() = onBoldClick()\n    fun toggleItalic() = onItalicClick()\n    fun toggleUnderline() = onUnderlineClick()\n    fun toggleStrikethrough() = onStrikethroughClick()\n    fun toggleCode() = onCodeClick()\n\n    fun toggleCodeBlock() {\n        val s = text as? Editable ?: return\n        var fullText = s.toString()\n\n        // Find current line boundaries\n        var lineStart = selectionStart.coerceIn(0, fullText.length)\n        while (lineStart > 0 && fullText[lineStart - 1] != '\\n') lineStart--\n        var lineEnd = (selectionStart.coerceAtLeast(selectionEnd)).coerceIn(0, fullText.length)\n        while (lineEnd < fullText.length && fullText[lineEnd] != '\\n') lineEnd++\n\n        android.util.Log.d(\"RichTextEditor\", \"toggleCodeBlock: lineStart=$lineStart, lineEnd=$lineEnd, pendingStyles=$pendingStyles, textLen=${s.length}, selStart=$selectionStart\")\n\n        // Mutual exclusivity: if blockquote is active, remove it first (Req 13.1)\n        val quotePrefix = \"▎ \"\n        if (lineStart < lineEnd) {\n            val lineText = fullText.substring(lineStart, lineEnd)\n            if (lineText.startsWith(quotePrefix)) {\n                isInternalChange = true\n                s.delete(lineStart, lineStart + quotePrefix.length)\n                isInternalChange = false\n\n                // Recalculate text and boundaries after quote removal\n                fullText = s.toString()\n                lineEnd -= quotePrefix.length\n            }\n        }\n\n        // Mutual exclusivity: remove list prefixes (bullet, numbered, checklist)\n        if (lineStart < lineEnd) {\n            val updatedLineText = fullText.substring(lineStart, lineEnd)\n            val numberedMatch = Regex(\"^\\\\d+\\\\.\\\\s\").find(updatedLineText)\n            when {\n                updatedLineText.startsWith(\"• \") -> {\n                    isInternalChange = true\n                    s.delete(lineStart, lineStart + 2)\n                    isInternalChange = false\n                    fullText = s.toString()\n                    lineEnd -= 2\n                }\n                updatedLineText.startsWith(\"☐ \") || updatedLineText.startsWith(\"☑ \") -> {\n                    isInternalChange = true\n                    s.delete(lineStart, lineStart + 2)\n                    isInternalChange = false\n                    fullText = s.toString()\n                    lineEnd -= 2\n                }\n                numberedMatch != null -> {\n                    val prefixLen = numberedMatch.value.length\n                    isInternalChange = true\n                    s.delete(lineStart, lineStart + prefixLen)\n                    isInternalChange = false\n                    fullText = s.toString()\n                    lineEnd -= prefixLen\n                }\n            }\n        }\n\n        // If line is empty, insert ZWS for immediate container appearance (Slack behavior)\n        if (lineStart >= lineEnd) {\n            val key = \"codeBlock\"\n            isInternalChange = true\n            if (pendingStyles.contains(key)) {\n                // Deactivating: remove ZWS and code block spans\n                pendingStyles.remove(key)\n                pendingStyles.remove(\"bold\")\n                pendingStyles.remove(\"italic\")\n                pendingStyles.remove(\"underline\")\n                pendingStyles.remove(\"strikethrough\")\n                pendingStyles.remove(\"code\")\n                explicitlyOffStyles.clear()\n                suppressCodeBlockShortcut = false\n                // Remove ZWS if present at cursor position\n                val pos = selectionStart.coerceIn(0, s.length)\n                if (pos > 0 && s.length > 0 && s[pos - 1] == '\\u200B') {\n                    // Explicitly remove code block spans BEFORE deleting the ZWS,\n                    // because SPAN_EXCLUSIVE_INCLUSIVE spans can collapse to zero-length\n                    // instead of being auto-removed when their only character is deleted.\n                    s.getSpans(pos - 1, pos, CodeBlockBorderSpan::class.java)\n                        .forEach { s.removeSpan(it) }\n                    s.getSpans(pos - 1, pos, TypefaceSpan::class.java)\n                        .filter { it.family == \"monospace\" }\n                        .forEach { s.removeSpan(it) }\n                    s.delete(pos - 1, pos)\n                }\n                // Safety: remove any remaining zero-length CodeBlockBorderSpan spans\n                // that may have survived the deletion\n                s.getSpans(0, s.length, CodeBlockBorderSpan::class.java).forEach { span ->\n                    if (s.getSpanStart(span) == s.getSpanEnd(span)) {\n                        s.removeSpan(span)\n                    }\n                }\n            } else {\n                pendingStyles.add(key)\n                pendingStyles.remove(\"code\")\n                explicitlyOffStyles.remove(\"code\")\n                explicitlyOffStyles.remove(key)\n                // Insert ZWS with code block spans for immediate container\n                val zws = \"\\u200B\"\n                val insertPos = selectionStart.coerceIn(0, s.length)\n                s.insert(insertPos, zws)\n                s.setSpan(CodeBlockBorderSpan(density), insertPos, insertPos + 1, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                s.setSpan(TypefaceSpan(\"monospace\"), insertPos, insertPos + 1, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n                setSelection(insertPos + 1)\n            }\n            isInternalChange = false\n            pendingStylesInsertPos = selectionStart\n            applyListIndentation()\n            invalidate()\n            sendContentChangeWithDelta()\n            saveToUndoStack()\n            updateCodeBlockEdgePadding()\n            updateContentSize()\n            updatePlaceholderVisibility()\n            emitActiveStyles()\n            return\n        }\n\n        // Check if the line already has monospace (code block style)\n        val existingMonospace = s.getSpans(lineStart, lineEnd, TypefaceSpan::class.java)\n            .filter { it.family == \"monospace\" }\n\n        isInternalChange = true\n        if (existingMonospace.isNotEmpty()) {\n            // Deactivating code block — remove code block styling\n            suppressCodeBlockShortcut = false\n            existingMonospace.forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, CodeBlockBorderSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, BackgroundColorSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n            // Clear all inline formatting spans from the affected range (Req 16.1)\n            s.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, UnderlineSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, ForegroundColorSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n\n            // Restore stored mention metadata if plain text still matches (Req 19.2, 19.3)\n            val currentPlainText = s.toString()\n            val keysToRemove = mutableListOf<String>()\n            for ((key, stored) in storedMentionMetadata) {\n                val mentionRange = stored.originalRange\n                // Verify range is still valid within the line\n                if (mentionRange.first < lineStart || mentionRange.last > lineEnd ||\n                    mentionRange.last > currentPlainText.length) {\n                    keysToRemove.add(key)\n                    continue\n                }\n                val textAtRange = currentPlainText.substring(mentionRange.first, mentionRange.last)\n                if (textAtRange == stored.displayName) {\n                    // Text unchanged — restore mention span (Req 19.2)\n                    s.setSpan(\n                        MentionSpan(stored.uid, stored.displayName),\n                        mentionRange.first, mentionRange.last,\n                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n                    )\n                }\n                // Whether restored or discarded, remove from stored metadata\n                keysToRemove.add(key)\n            }\n            for (key in keysToRemove) {\n                storedMentionMetadata.remove(key)\n            }\n\n            // Reset pending styles so toolbar reflects clean state (Req 16.2)\n            // CRITICAL: remove \"codeBlock\" from pendingStyles so emitActiveStyles\n            // reports codeBlock=false and the JS toolbar icon deactivates.\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.clear()\n            pendingStylesInsertPos = -1\n        } else {\n            // Activating code block — extract and store mention metadata before stripping (Req 19.1)\n            s.getSpans(lineStart, lineEnd, MentionSpan::class.java).forEach { span ->\n                val spanStart = s.getSpanStart(span)\n                val spanEnd = s.getSpanEnd(span)\n                val range = spanStart until spanEnd\n                storedMentionMetadata[rangeKey(range)] = StoredMention(\n                    displayName = span.displayName,\n                    uid = span.uid,\n                    originalRange = range\n                )\n            }\n            // Remove mention spans — convert to plain text display names (Req 7.2)\n            s.getSpans(lineStart, lineEnd, MentionSpan::class.java)\n                .forEach { s.removeSpan(it) }\n\n            // Remove all inline styles (Req 7.1, 7.3)\n            s.getSpans(lineStart, lineEnd, StyleSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, UnderlineSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, StrikethroughSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, InlineCodeSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, ForegroundColorSpan::class.java)\n                .forEach { s.removeSpan(it) }\n            s.getSpans(lineStart, lineEnd, AbsoluteSizeSpan::class.java)\n                .filter { s.getSpanStart(it) >= lineStart && s.getSpanEnd(it) <= lineEnd }\n                .forEach { s.removeSpan(it) }\n\n            // Apply code block styling: monospace + bordered container\n            s.setSpan(TypefaceSpan(\"monospace\"), lineStart, lineEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n            s.setSpan(CodeBlockBorderSpan(density), lineStart, lineEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n            // Apply dark-mode-aware text color for readability\n            val cbTextColor = if (isDarkMode()) Color.parseColor(\"#E0E0E0\") else Color.parseColor(\"#333333\")\n            s.setSpan(ForegroundColorSpan(cbTextColor), lineStart, lineEnd, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)\n        }\n        setSelection(lineStart, lineEnd)\n        isInternalChange = false\n        invalidate()\n        applyListIndentation()\n        updateCodeBlockEdgePadding()\n        sendContentChangeWithDelta()\n        saveToUndoStack()\n        updateContentSize()\n        // Update toolbar to reflect clean inline state after deselection (Req 16.2)\n        updateToolbarButtonStates()\n        emitActiveStyles()\n    }\n    fun toggleHighlight(color: String?) = onHighlightClick()\n    fun setHeading() = onHeadingClick()\n    fun toggleBulletList() = onBulletListClick()\n    fun toggleNumberedList() = applyNumberedList()\n    fun setQuote() = onQuoteClick()\n    fun setChecklist() = onChecklistClick()\n    fun setParagraph() {\n        // Remove all block-level formatting from current line\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val lineText = currentText.substring(lineStart, lineEnd)\n        val cleanLine = removeExistingPrefix(lineText)\n\n        isInternalChange = true\n        text?.replace(lineStart, lineEnd, cleanLine)\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    fun toggleChecklistItem() {\n        val (lineStart, lineEnd) = getLineRange()\n        val currentText = text?.toString() ?: return\n        val lineText = currentText.substring(lineStart, lineEnd)\n        val editable = text ?: return\n\n        isInternalChange = true\n        when {\n            lineText.startsWith(\"☐ \") -> {\n                editable.replace(lineStart, lineStart + 1, \"☑\")\n            }\n            lineText.startsWith(\"☑ \") -> {\n                editable.replace(lineStart, lineStart + 1, \"☐\")\n            }\n        }\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    // ==================== Ref Methods ====================\n\n    fun focusEditor() {\n        requestFocus()\n        val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as android.view.inputmethod.InputMethodManager\n        imm.showSoftInput(this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT)\n    }\n\n    fun blurEditor() {\n        clearFocus()\n        val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as android.view.inputmethod.InputMethodManager\n        imm.hideSoftInputFromWindow(windowToken, 0)\n    }\n\n    fun clearContent() {\n        isInternalChange = true\n        setText(\"\")\n        pendingStyles.clear()\n        explicitlyOffStyles.clear()\n        isInternalChange = false\n        updateCodeBlockEdgePadding()\n        updatePlaceholderVisibility()\n        sendContentChange()\n        emitActiveStyles()\n        updateToolbarButtonStates()\n    }\n\n    // ==================== New Props ====================\n\n    fun setSelectionRange(start: Int, end: Int) {\n        // Map clean coordinates (no \\u200B) to raw Editable positions\n        val rawText = text?.toString() ?: \"\"\n        val cleanToRaw = mutableListOf<Int>()\n        for ((rawIdx, ch) in rawText.withIndex()) {\n            if (ch != '\\u200B') {\n                cleanToRaw.add(rawIdx)\n            }\n        }\n        cleanToRaw.add(rawText.length)\n\n        val rawStart = if (start in 0 until cleanToRaw.size) cleanToRaw[start] else rawText.length\n        val rawEnd = if (end in 0 until cleanToRaw.size) cleanToRaw[end] else rawText.length\n        val clampedStart = rawStart.coerceIn(0, rawText.length)\n        val clampedEnd = rawEnd.coerceIn(clampedStart, rawText.length)\n        setSelection(clampedStart, clampedEnd)\n    }\n\n    fun setTextStyle(fontSize: Float?, color: String?, fontFamily: String?) {\n        fontSize?.let { textSize = it }\n        color?.let {\n            try {\n                setTextColor(Color.parseColor(it))\n            } catch (e: Exception) {\n                // Invalid color, ignore\n            }\n        }\n        fontFamily?.let {\n            typeface = Typeface.create(it, Typeface.NORMAL)\n        }\n    }\n\n    fun setPlaceholderTextColor(color: String) {\n        try {\n            setHintTextColor(Color.parseColor(color))\n        } catch (e: Exception) {\n            // Invalid color, ignore\n        }\n    }\n\n    fun setTextContent(newText: String, force: Boolean = false) {\n        val currentText = text?.toString() ?: \"\"\n        if (currentText == newText) return\n        // Skip stale prop updates while user is actively typing.\n        // Force=true bypasses this (used by imperative setText command for mentions/clear).\n        if (!force) {\n            val timeSinceLastEdit = System.currentTimeMillis() - lastUserEditTime\n            if (timeSinceLastEdit < 200) return\n        }\n        isInternalChange = true\n\n        // For force updates (mentions, etc.), do a targeted replacement that preserves\n        // spans on unchanged regions (e.g. code block, inline code).\n        // Uses prefix + suffix matching with overlap guard to find the minimal changed region.\n        val editable = text\n        if (editable != null && force) {\n            // The incoming newText is in \"clean\" coordinates (zero-width spaces stripped),\n            // matching what JS sees via onContentChange. Diff against the cleaned version\n            // of the current text, then map the replacement range back to raw positions\n            // so spans on unchanged regions (code block, inline code) are preserved.\n            val cleanChars = mutableListOf<Char>()\n            val cleanToRaw = mutableListOf<Int>()  // cleanToRaw[i] = raw index of clean char i\n            for ((rawIdx, ch) in currentText.withIndex()) {\n                if (ch != '\\u200B') {\n                    cleanToRaw.add(rawIdx)\n                    cleanChars.add(ch)\n                }\n            }\n            // Sentinel: cleanToRaw[cleanChars.size] = currentText.length (for end-of-string)\n            cleanToRaw.add(currentText.length)\n\n            val cleanLen = cleanChars.size\n            val newLen = newText.length\n            val minLen = minOf(cleanLen, newLen)\n\n            // Find common prefix (in clean coordinates)\n            var prefixLen = 0\n            while (prefixLen < minLen && cleanChars[prefixLen] == newText[prefixLen]) prefixLen++\n\n            // Find common suffix, but never overlap with the prefix\n            var suffixLen = 0\n            val maxSuffix = minLen - prefixLen\n            while (suffixLen < maxSuffix &&\n                cleanChars[cleanLen - 1 - suffixLen] == newText[newLen - 1 - suffixLen]) {\n                suffixLen++\n            }\n\n            // Map clean-coordinate range back to raw-coordinate range\n            val rawReplaceStart = cleanToRaw[prefixLen]\n            val rawReplaceEnd = if (cleanLen - suffixLen < cleanToRaw.size)\n                cleanToRaw[cleanLen - suffixLen] else currentText.length\n            val replacement = newText.substring(prefixLen, newLen - suffixLen)\n\n            editable.replace(rawReplaceStart, rawReplaceEnd, replacement)\n            val newPosition = (rawReplaceStart + replacement.length).coerceIn(0, editable.length)\n            setSelection(newPosition)\n        } else {\n            val currentSelection = selectionStart\n            setText(newText)\n            val newPosition = currentSelection.coerceIn(0, newText.length)\n            setSelection(newPosition)\n        }\n\n        isInternalChange = false\n\n        // Emit updated content (with correct block types) so JS state stays in sync.\n        // The TextWatcher skips this because isInternalChange was true during the edit.\n        sendContentChange()\n    }\n\n    /// Applies visual mention styling (bold purple text + background pill) to the given ranges.\n    /// Called from JS after mention insertion so the native editor matches the message bubble look.\n    fun setMentionRanges(ranges: List<Pair<Int, Int>>) {\n        val spannable = text as? android.text.Spannable ?: return\n        val textLength = spannable.length\n\n        // Build clean-to-raw index mapping.\n        // JS sends ranges in \"clean\" coordinates (zero-width spaces stripped),\n        // but the Editable contains \\u200B placeholders.\n        val rawText = spannable.toString()\n        val cleanToRaw = mutableListOf<Int>()\n        for ((rawIdx, ch) in rawText.withIndex()) {\n            if (ch != '\\u200B') {\n                cleanToRaw.add(rawIdx)\n            }\n        }\n        cleanToRaw.add(rawText.length) // sentinel for end-of-string\n\n        // Clear existing mention styling — remove spans only on ranges that had MentionSpan\n        // to avoid clearing user-applied bold or code block backgrounds.\n        val oldMentionSpans = spannable.getSpans(0, textLength, MentionSpan::class.java)\n        for (ms in oldMentionSpans) {\n            val msStart = spannable.getSpanStart(ms)\n            val msEnd = spannable.getSpanEnd(ms)\n            // Remove bold StyleSpans that overlap this mention range\n            spannable.getSpans(msStart, msEnd, android.text.style.StyleSpan::class.java)\n                .filter { it.style == android.graphics.Typeface.BOLD }\n                .forEach { spannable.removeSpan(it) }\n            // Remove ForegroundColorSpan and BackgroundColorSpan on this mention range\n            spannable.getSpans(msStart, msEnd, android.text.style.ForegroundColorSpan::class.java)\n                .forEach { spannable.removeSpan(it) }\n            spannable.getSpans(msStart, msEnd, android.text.style.BackgroundColorSpan::class.java)\n                .forEach { spannable.removeSpan(it) }\n            spannable.removeSpan(ms)\n        }\n\n        for ((cleanStart, cleanEnd) in ranges) {\n            if (cleanStart < 0 || cleanEnd <= cleanStart) continue\n            if (cleanStart >= cleanToRaw.size || cleanEnd >= cleanToRaw.size) continue\n\n            val start = cleanToRaw[cleanStart]\n            val end = cleanToRaw[cleanEnd]\n            if (start >= end || end > textLength) continue\n\n            // Skip if inside a code block — mentions in code blocks are plain monospace (Req 19.1)\n            if (spannable.getSpans(start, end, CodeBlockBorderSpan::class.java).isNotEmpty()) continue\n\n            val displayName = spannable.subSequence(start, end).toString()\n\n            // Add MentionSpan marker (used by nonMentionRanges to skip formatting)\n            spannable.setSpan(\n                MentionSpan(displayName, displayName),\n                start, end,\n                android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n            )\n\n            // Bold\n            spannable.setSpan(\n                android.text.style.StyleSpan(android.graphics.Typeface.BOLD),\n                start, end,\n                android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n            )\n\n            // Purple text color (#6852D6)\n            spannable.setSpan(\n                android.text.style.ForegroundColorSpan(android.graphics.Color.parseColor(\"#6852D6\")),\n                start, end,\n                android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n            )\n\n            // Light purple background (rgba(104, 82, 214, 0.15))\n            spannable.setSpan(\n                android.text.style.BackgroundColorSpan(android.graphics.Color.argb(38, 104, 82, 214)),\n                start, end,\n                android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\n            )\n        }\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        hideToolbar()\n        toolbarPopup = null\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/RichTextEditorViewManager.kt",
    "content": "package com.reactnativecometchatuikit\n\nimport android.graphics.Color\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport com.facebook.react.bridge.ReadableArray\nimport com.facebook.react.bridge.ReadableMap\nimport com.facebook.react.module.annotations.ReactModule\nimport com.facebook.react.uimanager.SimpleViewManager\nimport com.facebook.react.uimanager.ThemedReactContext\nimport com.facebook.react.uimanager.annotations.ReactProp\n\n@ReactModule(name = RichTextEditorViewManager.NAME)\nclass RichTextEditorViewManager : SimpleViewManager<LinearLayout>() {\n\n    companion object {\n        const val NAME = \"RichTextEditorView\"\n        const val COMMAND_FOCUS = \"focus\"\n        const val COMMAND_BLUR = \"blur\"\n        const val COMMAND_CLEAR = \"clear\"\n        const val COMMAND_TOGGLE_BOLD = \"toggleBold\"\n        const val COMMAND_TOGGLE_ITALIC = \"toggleItalic\"\n        const val COMMAND_TOGGLE_UNDERLINE = \"toggleUnderline\"\n        const val COMMAND_TOGGLE_STRIKETHROUGH = \"toggleStrikethrough\"\n        const val COMMAND_TOGGLE_CODE = \"toggleCode\"\n        const val COMMAND_TOGGLE_CODE_BLOCK = \"toggleCodeBlock\"\n        const val COMMAND_TOGGLE_HIGHLIGHT = \"toggleHighlight\"\n        const val COMMAND_SET_HEADING = \"setHeading\"\n        const val COMMAND_SET_BULLET_LIST = \"setBulletList\"\n        const val COMMAND_SET_NUMBERED_LIST = \"setNumberedList\"\n        const val COMMAND_SET_QUOTE = \"setQuote\"\n        const val COMMAND_SET_CHECKLIST = \"setChecklist\"\n        const val COMMAND_SET_PARAGRAPH = \"setParagraph\"\n        const val COMMAND_INSERT_LINK = \"insertLink\"\n        const val COMMAND_UNDO = \"undo\"\n        const val COMMAND_REDO = \"redo\"\n        const val COMMAND_CLEAR_FORMATTING = \"clearFormatting\"\n        const val COMMAND_INDENT = \"indent\"\n        const val COMMAND_OUTDENT = \"outdent\"\n        const val COMMAND_ALIGN_LEFT = \"alignLeft\"\n        const val COMMAND_ALIGN_CENTER = \"alignCenter\"\n        const val COMMAND_ALIGN_RIGHT = \"alignRight\"\n        const val COMMAND_TOGGLE_CHECKLIST_ITEM = \"toggleChecklistItem\"\n        const val COMMAND_SET_TEXT = \"setText\"\n        const val COMMAND_SET_SELECTION = \"setSelection\"\n        const val COMMAND_SET_CONTENT = \"setContent\"\n        const val COMMAND_SET_MENTION_RANGES = \"setMentionRanges\"\n        const val COMMAND_REMOVE_LINK = \"removeLink\"\n        const val COMMAND_UPDATE_LINK = \"updateLink\"\n\n        private const val TAG_EDITOR = \"editor\"\n    }\n\n    override fun getName(): String = NAME\n\n    override fun createViewInstance(reactContext: ThemedReactContext): LinearLayout {\n        val container = LinearLayout(reactContext).apply {\n            orientation = LinearLayout.VERTICAL\n            layoutParams = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT\n            )\n        }\n\n        val editor = RichTextEditorView(reactContext)\n        editor.tag = TAG_EDITOR\n        editor.containerView = container\n        editor.layoutParams = LinearLayout.LayoutParams(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.WRAP_CONTENT,\n            1f\n        )\n        container.addView(editor)\n\n        return container\n    }\n\n    /** Track showToolbar prop value for proper inline toolbar visibility */\n    private val showToolbarState = mutableMapOf<Int, Boolean>()\n\n    /** Helper to find the editor child inside the container */\n    private fun getEditor(container: LinearLayout): RichTextEditorView? {\n        for (i in 0 until container.childCount) {\n            val child = container.getChildAt(i)\n            if (child.tag == TAG_EDITOR && child is RichTextEditorView) {\n                return child\n            }\n        }\n        return null\n    }\n\n    override fun getCommandsMap(): Map<String, Int> {\n        return mapOf(\n            COMMAND_FOCUS to 1,\n            COMMAND_BLUR to 2,\n            COMMAND_CLEAR to 3,\n            COMMAND_TOGGLE_BOLD to 4,\n            COMMAND_TOGGLE_ITALIC to 5,\n            COMMAND_TOGGLE_UNDERLINE to 6,\n            COMMAND_TOGGLE_STRIKETHROUGH to 7,\n            COMMAND_TOGGLE_CODE to 8,\n            COMMAND_TOGGLE_CODE_BLOCK to 28,\n            COMMAND_TOGGLE_HIGHLIGHT to 9,\n            COMMAND_SET_HEADING to 10,\n            COMMAND_SET_BULLET_LIST to 11,\n            COMMAND_SET_NUMBERED_LIST to 12,\n            COMMAND_SET_QUOTE to 13,\n            COMMAND_SET_CHECKLIST to 14,\n            COMMAND_SET_PARAGRAPH to 15,\n            COMMAND_INSERT_LINK to 16,\n            COMMAND_UNDO to 17,\n            COMMAND_REDO to 18,\n            COMMAND_CLEAR_FORMATTING to 19,\n            COMMAND_INDENT to 20,\n            COMMAND_OUTDENT to 21,\n            COMMAND_ALIGN_LEFT to 22,\n            COMMAND_ALIGN_CENTER to 23,\n            COMMAND_ALIGN_RIGHT to 24,\n            COMMAND_TOGGLE_CHECKLIST_ITEM to 25,\n            COMMAND_SET_TEXT to 26,\n            COMMAND_SET_SELECTION to 27,\n            COMMAND_SET_CONTENT to 29,\n            COMMAND_SET_MENTION_RANGES to 30,\n            COMMAND_REMOVE_LINK to 31,\n            COMMAND_UPDATE_LINK to 32\n        )\n    }\n\n    override fun receiveCommand(container: LinearLayout, commandId: String, args: ReadableArray?) {\n        val view = getEditor(container) ?: return\n        when (commandId) {\n            COMMAND_FOCUS -> view.focusEditor()\n            COMMAND_BLUR -> view.blurEditor()\n            COMMAND_CLEAR -> view.clearContent()\n            else -> {\n                if (!view.hasFocus()) {\n                    view.requestFocus()\n                }\n                when (commandId) {\n                    COMMAND_TOGGLE_BOLD -> view.onBoldClick()\n                    COMMAND_TOGGLE_ITALIC -> view.onItalicClick()\n                    COMMAND_TOGGLE_UNDERLINE -> view.onUnderlineClick()\n                    COMMAND_TOGGLE_STRIKETHROUGH -> view.onStrikethroughClick()\n                    COMMAND_TOGGLE_CODE -> view.onCodeClick()\n                    COMMAND_TOGGLE_CODE_BLOCK -> view.toggleCodeBlock()\n                    COMMAND_TOGGLE_HIGHLIGHT -> view.onHighlightClick()\n                    COMMAND_SET_HEADING -> view.onHeadingClick()\n                    COMMAND_SET_BULLET_LIST -> view.onBulletListClick()\n                    COMMAND_SET_NUMBERED_LIST -> view.onNumberedListClick()\n                    COMMAND_SET_QUOTE -> view.onQuoteClick()\n                    COMMAND_SET_CHECKLIST -> view.onChecklistClick()\n                    COMMAND_SET_PARAGRAPH -> { }\n                    COMMAND_INSERT_LINK -> {\n                        val url = args?.getString(0) ?: \"\"\n                        val linkText = args?.getString(1) ?: \"\"\n                        if (url.isNotEmpty() && linkText.isNotEmpty()) {\n                            view.insertLink(url, linkText)\n                        }\n                    }\n                    COMMAND_UNDO -> view.onUndoClick()\n                    COMMAND_REDO -> view.onRedoClick()\n                    COMMAND_CLEAR_FORMATTING -> view.onClearFormattingClick()\n                    COMMAND_INDENT -> view.onIndentClick()\n                    COMMAND_OUTDENT -> view.onOutdentClick()\n                    COMMAND_ALIGN_LEFT -> view.onAlignLeftClick()\n                    COMMAND_ALIGN_CENTER -> view.onAlignCenterClick()\n                    COMMAND_ALIGN_RIGHT -> view.onAlignRightClick()\n                    COMMAND_TOGGLE_CHECKLIST_ITEM -> view.onChecklistClick()\n                    COMMAND_SET_TEXT -> {\n                        val text = args?.getString(0) ?: \"\"\n                        view.setTextContent(text, force = true)\n                    }\n                    COMMAND_SET_SELECTION -> {\n                        val start = args?.getInt(0) ?: 0\n                        val end = args?.getInt(1) ?: start\n                        view.setSelectionRange(start, end)\n                    }\n                    COMMAND_SET_CONTENT -> {\n                        val blocksArray = args?.getArray(0)\n                        if (blocksArray != null) {\n                            val blocksList = mutableListOf<Map<String, Any>>()\n                            for (i in 0 until blocksArray.size()) {\n                                val block = blocksArray.getMap(i) ?: continue\n                                val blockMap = mutableMapOf<String, Any>()\n                                blockMap[\"text\"] = block.getString(\"text\") ?: \"\"\n                                blockMap[\"type\"] = block.getString(\"type\") ?: \"paragraph\"\n                                val stylesList = mutableListOf<Map<String, Any>>()\n                                val styles = block.getArray(\"styles\")\n                                if (styles != null) {\n                                    for (j in 0 until styles.size()) {\n                                        val style = styles.getMap(j) ?: continue\n                                        val styleMap = mutableMapOf<String, Any>()\n                                        val styleName = style.getString(\"style\") ?: \"\"\n                                        styleMap[\"style\"] = styleName\n                                        styleMap[\"start\"] = style.getInt(\"start\")\n                                        styleMap[\"end\"] = style.getInt(\"end\")\n                                        // Preserve url for link styles\n                                        if (styleName == \"link\" && style.hasKey(\"url\")) {\n                                            styleMap[\"url\"] = style.getString(\"url\") ?: \"\"\n                                        }\n                                        stylesList.add(styleMap)\n                                    }\n                                }\n                                blockMap[\"styles\"] = stylesList\n                                blocksList.add(blockMap)\n                            }\n                            view.setContent(blocksList)\n                        }\n                    }\n                    COMMAND_SET_MENTION_RANGES -> {\n                        val rangesArray = args?.getArray(0)\n                        if (rangesArray != null) {\n                            val ranges = mutableListOf<Pair<Int, Int>>()\n                            for (i in 0 until rangesArray.size()) {\n                                val rangeMap = rangesArray.getMap(i) ?: continue\n                                val start = rangeMap.getInt(\"start\")\n                                val end = rangeMap.getInt(\"end\")\n                                ranges.add(Pair(start, end))\n                            }\n                            view.setMentionRanges(ranges)\n                        }\n                    }\n                    COMMAND_REMOVE_LINK -> {\n                        val location = args?.getInt(0) ?: return\n                        val length = args?.getInt(1) ?: return\n                        view.removeLink(location, length)\n                    }\n                    COMMAND_UPDATE_LINK -> {\n                        val location = args?.getInt(0) ?: return\n                        val length = args?.getInt(1) ?: return\n                        val newUrl = args?.getString(2) ?: return\n                        val newText = args?.getString(3) ?: return\n                        view.updateLink(location, length, newUrl, newText)\n                    }\n                }\n            }\n        }\n    }\n\n    @ReactProp(name = \"toolbarMode\")\n    fun setToolbarMode(container: LinearLayout, mode: String?) {\n        val editor = getEditor(container) ?: return\n        val isInline = mode == \"inline\"\n\n        if (isInline) {\n            editor.setShowToolbar(false)\n        } else {\n            editor.setShowToolbar(true)\n        }\n    }\n\n    @ReactProp(name = \"placeholder\")\n    fun setPlaceholder(container: LinearLayout, placeholder: String?) {\n        try {\n            getEditor(container)?.setPlaceholderText(placeholder ?: \"\")\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"editable\")\n    fun setEditable(container: LinearLayout, editable: Boolean) {\n        try {\n            getEditor(container)?.setEditable(editable)\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"maxHeight\")\n    fun setMaxHeight(container: LinearLayout, maxHeight: Double) {\n        try {\n            getEditor(container)?.setMaxHeightValue(maxHeight.toInt())\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"numberOfLines\")\n    fun setNumberOfLines(container: LinearLayout, numberOfLines: Int) {\n        try {\n            getEditor(container)?.setNumberOfLinesValue(numberOfLines)\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"showToolbar\")\n    fun setShowToolbar(container: LinearLayout, showToolbar: Boolean) {\n        try {\n            showToolbarState[container.id] = showToolbar\n            val editor = getEditor(container) ?: return\n            editor.setShowToolbar(showToolbar)\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"toolbarOptions\")\n    fun setToolbarOptions(container: LinearLayout, toolbarOptions: ReadableArray?) {\n        try {\n            val options = if (toolbarOptions != null) {\n                val list = mutableListOf<String>()\n                for (i in 0 until toolbarOptions.size()) {\n                    toolbarOptions.getString(i)?.let { list.add(it) }\n                }\n                list\n            } else null\n            getEditor(container)?.setToolbarOptions(options)\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"variant\")\n    fun setVariant(container: LinearLayout, variant: String?) {\n        try {\n            getEditor(container)?.setVariant(variant ?: \"outlined\")\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"initialContentJson\")\n    fun setInitialContentJson(container: LinearLayout, initialContentJson: String?) {\n        if (initialContentJson.isNullOrEmpty()) return\n        try {\n            val editor = getEditor(container) ?: return\n            val json = org.json.JSONArray(initialContentJson)\n            val blocksList = mutableListOf<Map<String, Any>>()\n            for (i in 0 until json.length()) {\n                val block = json.getJSONObject(i)\n                val blockMap = mutableMapOf<String, Any>()\n                blockMap[\"text\"] = block.optString(\"text\", \"\")\n                blockMap[\"type\"] = block.optString(\"type\", \"paragraph\")\n                val stylesList = mutableListOf<Map<String, Any>>()\n                val styles = block.optJSONArray(\"styles\")\n                if (styles != null) {\n                    for (j in 0 until styles.length()) {\n                        val style = styles.getJSONObject(j)\n                        val styleMap = mutableMapOf<String, Any>()\n                        val styleName = style.optString(\"style\", \"\")\n                        styleMap[\"style\"] = styleName\n                        styleMap[\"start\"] = style.optInt(\"start\", 0)\n                        styleMap[\"end\"] = style.optInt(\"end\", 0)\n                        // Preserve url for link styles\n                        if (styleName == \"link\" && style.has(\"url\")) {\n                            styleMap[\"url\"] = style.optString(\"url\", \"\")\n                        }\n                        stylesList.add(styleMap)\n                    }\n                }\n                blockMap[\"styles\"] = stylesList\n                blocksList.add(blockMap)\n            }\n            editor.post { editor.setContent(blocksList) }\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"selection\")\n    fun setSelection(container: LinearLayout, selection: ReadableMap?) {\n        try {\n            selection?.let {\n                val start = it.getInt(\"start\")\n                val end = it.getInt(\"end\")\n                getEditor(container)?.setSelectionRange(start, end)\n            }\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"textStyle\")\n    fun setTextStyle(container: LinearLayout, textStyle: ReadableMap?) {\n        try {\n            textStyle?.let {\n                val fontSize = if (it.hasKey(\"fontSize\")) it.getDouble(\"fontSize\").toFloat() else null\n                val color = if (it.hasKey(\"color\")) it.getString(\"color\") else null\n                val fontFamily = if (it.hasKey(\"fontFamily\")) it.getString(\"fontFamily\") else null\n                getEditor(container)?.setTextStyle(fontSize, color, fontFamily)\n            }\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"placeholderTextColor\")\n    fun setPlaceholderTextColor(container: LinearLayout, color: String?) {\n        try {\n            color?.let { getEditor(container)?.setPlaceholderTextColor(it) }\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"text\")\n    fun setText(container: LinearLayout, text: String?) {\n        try {\n            if (text != null) {\n                getEditor(container)?.setTextContent(text)\n            }\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"codeBackgroundColor\")\n    fun setCodeBackgroundColor(container: LinearLayout, color: String?) {\n        try {\n            val editor = getEditor(container) ?: return\n            editor.inlineCodeBackgroundColor = color?.let {\n                try { Color.parseColor(it) } catch (e: Exception) { null }\n            }\n            editor.invalidate()\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"codeBorderColor\")\n    fun setCodeBorderColor(container: LinearLayout, color: String?) {\n        try {\n            val editor = getEditor(container) ?: return\n            editor.inlineCodeBorderColor = color?.let {\n                try { Color.parseColor(it) } catch (e: Exception) { null }\n            }\n            editor.invalidate()\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"codeTextColor\")\n    fun setCodeTextColor(container: LinearLayout, color: String?) {\n        try {\n            val editor = getEditor(container) ?: return\n            editor.inlineCodeTextColor = color?.let {\n                try { Color.parseColor(it) } catch (e: Exception) { null }\n            }\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"codeFontSize\")\n    fun setCodeFontSize(container: LinearLayout, size: Double) {\n        try {\n            val editor = getEditor(container) ?: return\n            editor.inlineCodeFontSize = if (size > 0) size.toFloat() else null\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"enterKeyBehavior\")\n    fun setEnterKeyBehavior(container: LinearLayout, behavior: String?) {\n        try {\n            getEditor(container)?.enterKeyBehavior = behavior ?: \"newLine\"\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    @ReactProp(name = \"showTextSelectionMenuItems\", defaultBoolean = true)\n    fun setShowTextSelectionMenuItems(container: LinearLayout, show: Boolean) {\n        try {\n            getEditor(container)?.showTextSelectionMenuItems = show\n        } catch (e: Exception) { e.printStackTrace() }\n    }\n\n    override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {\n        return mutableMapOf(\n            \"topContentChange\" to mapOf(\"registrationName\" to \"onContentChange\"),\n            \"topSelectionChange\" to mapOf(\"registrationName\" to \"onSelectionChange\"),\n            \"topEditorFocus\" to mapOf(\"registrationName\" to \"onEditorFocus\"),\n            \"topEditorBlur\" to mapOf(\"registrationName\" to \"onEditorBlur\"),\n            \"topSizeChange\" to mapOf(\"registrationName\" to \"onSizeChange\"),\n            \"topActiveStylesChange\" to mapOf(\"registrationName\" to \"onActiveStylesChange\"),\n            \"topLinkTap\" to mapOf(\"registrationName\" to \"onLinkTap\"),\n            \"topSendRequest\" to mapOf(\"registrationName\" to \"onSendRequest\")\n        )\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/SoundPlayer.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.media.AudioManager;\nimport android.media.MediaPlayer;\nimport android.net.Uri;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.bridge.Arguments;\nimport com.facebook.react.bridge.Promise;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.bridge.Callback;\nimport com.facebook.react.bridge.WritableMap;\nimport com.facebook.react.modules.core.DeviceEventManagerModule;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.IOException;\n\npublic class SoundPlayer extends ReactContextBaseJavaModule {\n    MediaPlayer mediaPlayer = null;\n    JSONObject obj = new JSONObject();\n    public static final String MODULE_NAME = \"SoundPlayer\";\n    public static final String EVENT_NAME = \"soundPlayStatus\";\n    private static String currentUrl = \"\";\n    private static String prevUrl = \"\";\n    DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = null;\n\n    SoundPlayer(ReactApplicationContext context) {\n        super(context);\n    }\n\n    private void emitEvent(String status, String forAudio) {\n        WritableMap params = Arguments.createMap();\n        params.putString(\"status\", status);\n        params.putString(\"url\", forAudio);\n        eventEmitter.emit(EVENT_NAME, params);\n    }\n\n    @ReactMethod\n    public void prepareMediaPlayer(String url, Callback resolve) {\n        if (mediaPlayer == null)\n            eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n\n        currentUrl = url;\n        mediaPlayer = MediaPlayer.create(getReactApplicationContext(), Uri.parse(url));\n\n        if(mediaPlayer == null)\n           return;\n\n        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n        try {\n            obj = new JSONObject();\n            obj.put(\"duration\",mediaPlayer.getDuration()/1000);\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        resolve.invoke(obj.toString());\n    }\n\n    @ReactMethod\n    public void play(String url, Callback resolve) {\n        if (!prevUrl.equals(\"\")){\n            this.stop();\n            emitEvent(\"complete\", prevUrl);\n        }\n        \n        if (mediaPlayer == null)\n            eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);\n\n        currentUrl = url;\n        mediaPlayer = MediaPlayer.create(getReactApplicationContext(), Uri.parse(url));\n\n        if(mediaPlayer == null)\n           return;\n\n        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {\n            @Override\n            public void onCompletion(MediaPlayer mediaPlayer) {\n                emitEvent(\"complete\", url);\n                if (prevUrl.equals(url)){\n                    prevUrl = \"\";\n                }\n            }\n        });\n\n        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n        try {\n            mediaPlayer.start();\n            obj = new JSONObject();\n            obj.put(\"success\", true);\n            int duration = mediaPlayer.getDuration();\n            if (duration > -1) {\n                duration = duration / 1000;\n            }\n            prevUrl = url;\n            obj.put(\"duration\", duration);\n            obj.put(\"url\", currentUrl);\n            resolve.invoke(obj.toString());\n        } catch (JSONException ex) {\n            resolve.invoke(\"Error\", ex.getMessage());\n        }\n    }\n\n    @ReactMethod\n    public void resume() {\n        if (mediaPlayer != null) {\n            mediaPlayer.start();\n        }\n    }\n\n    @ReactMethod\n    public void playAt(int atTime, Callback resolve) {\n        if (mediaPlayer != null) {\n            mediaPlayer.seekTo(atTime * 1000);\n            obj = new JSONObject();\n            resolve.invoke(obj.toString());\n        } else {\n            resolve.invoke(\"Error\", \"MediaPlayer not prepared.\");\n        }\n    }\n\n    @ReactMethod\n    public void pause(Callback resolve) {\n        if (mediaPlayer != null) {\n            mediaPlayer.pause();\n            try {\n                obj = new JSONObject();\n                obj.put(\"success\", true);\n                resolve.invoke(obj.toString());\n            } catch (JSONException ex) {\n                resolve.invoke(obj.toString());\n            }\n        }\n    }\n\n    @ReactMethod\n    public void stop() {\n        if(mediaPlayer == null)\n           return;\n        mediaPlayer.stop();\n        mediaPlayer.reset();\n    }\n\n    @ReactMethod\n    public void releaseMediaPlayer() {\n        if(mediaPlayer == null)\n           return;\n        mediaPlayer.release();\n    }\n\n    @ReactMethod\n    public void getPosition(Callback resolve) {\n        try {\n            obj = new JSONObject();\n            obj.put(\"position\",mediaPlayer.getCurrentPosition()/1000);\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        resolve.invoke(obj.toString());\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return MODULE_NAME;\n    }\n}"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/TimeZoneCodeManager.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.bridge.Callback;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\n\nimport java.util.TimeZone;\n\npublic class TimeZoneCodeManager extends ReactContextBaseJavaModule {\n    public static final String TAG = \"TimeZoneCodeManager\";\n\n    public TimeZoneCodeManager(ReactApplicationContext context) {\n        super(context);\n    }\n\n    @ReactMethod\n    public void getCurrentTimeZone(Callback callback) {\n        try {\n            // Get the current time zone ID\n            String timeZoneID = TimeZone.getDefault().getID();\n            // Invoke the callback with the time zone ID\n            callback.invoke(timeZoneID);\n        } catch (Exception e) {\n            // Log the exception\n            Log.e(TAG, \"Error getting the time zone\", e);\n            // If there's an error, you may choose to invoke the callback with null or an error message\n            callback.invoke((Object) null);\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return TAG;\n    }\n}"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/reactnativecometchatuikit/WebViewManager.java",
    "content": "package com.reactnativecometchatuikit;\n\nimport android.content.Intent;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\n\npublic class WebViewManager extends ReactContextBaseJavaModule {\n\n    public static final String TAG = \"WebViewManager\";\n    public static final String MODULE_NAME = \"WebViewManager\";\n\n    public WebViewManager(ReactApplicationContext reactContext) {\n        super(reactContext);\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return MODULE_NAME;\n    }\n\n    @ReactMethod\n    public void openUrl(String url) {\n        try {\n            Intent i = new Intent(getReactApplicationContext(), CometChatWebView.class);\n            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            i.putExtra(\"load_url\", url);\n            getReactApplicationContext().startActivity(i);\n        } catch (Exception ex) {\n            Log.e(TAG, ex.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/scalablevideoview/BuildConfig.java",
    "content": "package com.scalablevideoview;\n\npublic final class BuildConfig {\n    public static final boolean DEBUG = false;\n    public static final String LIBRARY_PACKAGE_NAME = \"com.yqritc.scalablevideoview\";\n    public static final String BUILD_TYPE = \"release\";\n\n    public BuildConfig() {\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/scalablevideoview/PivotPoint.java",
    "content": "package com.scalablevideoview;\n\npublic enum PivotPoint {\n    LEFT_TOP,\n    LEFT_CENTER,\n    LEFT_BOTTOM,\n    CENTER_TOP,\n    CENTER,\n    CENTER_BOTTOM,\n    RIGHT_TOP,\n    RIGHT_CENTER,\n    RIGHT_BOTTOM\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/scalablevideoview/ScalableType.java",
    "content": "package com.scalablevideoview;\n\npublic enum ScalableType {\n    NONE,\n\n    FIT_XY,\n    FIT_START,\n    FIT_CENTER,\n    FIT_END,\n\n    LEFT_TOP,\n    LEFT_CENTER,\n    LEFT_BOTTOM,\n    CENTER_TOP,\n    CENTER,\n    CENTER_BOTTOM,\n    RIGHT_TOP,\n    RIGHT_CENTER,\n    RIGHT_BOTTOM,\n\n    LEFT_TOP_CROP,\n    LEFT_CENTER_CROP,\n    LEFT_BOTTOM_CROP,\n    CENTER_TOP_CROP,\n    CENTER_CROP,\n    CENTER_BOTTOM_CROP,\n    RIGHT_TOP_CROP,\n    RIGHT_CENTER_CROP,\n    RIGHT_BOTTOM_CROP,\n\n    START_INSIDE,\n    CENTER_INSIDE,\n    END_INSIDE\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/scalablevideoview/ScalableVideoView.java",
    "content": "package com.scalablevideoview;\n\nimport android.content.Context;\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.AssetManager;\nimport android.content.res.TypedArray;\nimport android.graphics.Matrix;\nimport android.graphics.SurfaceTexture;\nimport android.media.MediaPlayer;\nimport android.net.Uri;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RawRes;\nimport android.util.AttributeSet;\nimport android.view.Surface;\nimport android.view.TextureView;\n\nimport com.reactnativecometchatuikit.R;\n\nimport java.io.FileDescriptor;\nimport java.io.IOException;\nimport java.util.Map;\n\npublic class ScalableVideoView extends TextureView implements TextureView.SurfaceTextureListener,\n        MediaPlayer.OnVideoSizeChangedListener {\n\n    protected MediaPlayer mMediaPlayer;\n    protected ScalableType mScalableType = ScalableType.NONE;\n\n    public ScalableVideoView(Context context) {\n        this(context, null);\n    }\n\n    public ScalableVideoView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public ScalableVideoView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        if (attrs == null) {\n            return;\n        }\n\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.scaleStyle, 0, 0);\n        if (a == null) {\n            return;\n        }\n\n        int scaleType = a.getInt(R.styleable.scaleStyle_scalableType, ScalableType.NONE.ordinal());\n        a.recycle();\n        mScalableType = ScalableType.values()[scaleType];\n    }\n\n    @Override\n    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {\n        Surface surface = new Surface(surfaceTexture);\n        if (mMediaPlayer != null) {\n            mMediaPlayer.setSurface(surface);\n        }\n    }\n\n    @Override\n    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {\n    }\n\n    @Override\n    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {\n        return false;\n    }\n\n    @Override\n    public void onSurfaceTextureUpdated(SurfaceTexture surface) {\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        if (mMediaPlayer == null) {\n            return;\n        }\n\n        if (isPlaying()) {\n            stop();\n        }\n        release();\n    }\n\n    @Override\n    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {\n        scaleVideoSize(width, height);\n    }\n\n    private void scaleVideoSize(int videoWidth, int videoHeight) {\n        if (videoWidth == 0 || videoHeight == 0) {\n            return;\n        }\n\n        Size viewSize = new Size(getWidth(), getHeight());\n        Size videoSize = new Size(videoWidth, videoHeight);\n        ScaleManager scaleManager = new ScaleManager(viewSize, videoSize);\n        Matrix matrix = scaleManager.getScaleMatrix(mScalableType);\n        if (matrix != null) {\n            setTransform(matrix);\n        }\n    }\n\n    private void initializeMediaPlayer() {\n        if (mMediaPlayer == null) {\n            mMediaPlayer = new MediaPlayer();\n            mMediaPlayer.setOnVideoSizeChangedListener(this);\n            setSurfaceTextureListener(this);\n        } else {\n            reset();\n        }\n    }\n\n    public void setRawData(@RawRes int id) throws IOException {\n        AssetFileDescriptor afd = getResources().openRawResourceFd(id);\n        setDataSource(afd);\n    }\n\n    public void setAssetData(@NonNull String assetName) throws IOException {\n        AssetManager manager = getContext().getAssets();\n        AssetFileDescriptor afd = manager.openFd(assetName);\n        setDataSource(afd);\n    }\n\n    private void setDataSource(@NonNull AssetFileDescriptor afd) throws IOException {\n        setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());\n        afd.close();\n    }\n\n    public void setDataSource(@NonNull String path) throws IOException {\n        initializeMediaPlayer();\n        mMediaPlayer.setDataSource(path);\n    }\n\n    public void setDataSource(@NonNull Context context, @NonNull Uri uri,\n            @Nullable Map<String, String> headers) throws IOException {\n        initializeMediaPlayer();\n        mMediaPlayer.setDataSource(context, uri, headers);\n    }\n\n    public void setDataSource(@NonNull Context context, @NonNull Uri uri) throws IOException {\n        initializeMediaPlayer();\n        mMediaPlayer.setDataSource(context, uri);\n    }\n\n    public void setDataSource(@NonNull FileDescriptor fd, long offset, long length)\n            throws IOException {\n        initializeMediaPlayer();\n        mMediaPlayer.setDataSource(fd, offset, length);\n    }\n\n    public void setDataSource(@NonNull FileDescriptor fd) throws IOException {\n        initializeMediaPlayer();\n        mMediaPlayer.setDataSource(fd);\n    }\n\n    public void setScalableType(ScalableType scalableType) {\n        mScalableType = scalableType;\n        scaleVideoSize(getVideoWidth(), getVideoHeight());\n    }\n\n    public void prepare(@Nullable MediaPlayer.OnPreparedListener listener)\n            throws IOException, IllegalStateException {\n        mMediaPlayer.setOnPreparedListener(listener);\n        mMediaPlayer.prepare();\n    }\n\n    public void prepareAsync(@Nullable MediaPlayer.OnPreparedListener listener)\n            throws IllegalStateException {\n        mMediaPlayer.setOnPreparedListener(listener);\n        mMediaPlayer.prepareAsync();\n    }\n\n    public void prepare() throws IOException, IllegalStateException {\n        prepare(null);\n    }\n\n    public void prepareAsync() throws IllegalStateException {\n        prepareAsync(null);\n    }\n\n    public void setOnErrorListener(@Nullable MediaPlayer.OnErrorListener listener) {\n        mMediaPlayer.setOnErrorListener(listener);\n    }\n\n    public void setOnCompletionListener(@Nullable MediaPlayer.OnCompletionListener listener) {\n        mMediaPlayer.setOnCompletionListener(listener);\n    }\n\n    public void setOnInfoListener(@Nullable MediaPlayer.OnInfoListener listener) {\n        mMediaPlayer.setOnInfoListener(listener);\n    }\n\n    public int getCurrentPosition() {\n        return mMediaPlayer.getCurrentPosition();\n    }\n\n    public int getDuration() {\n        return mMediaPlayer.getDuration();\n    }\n\n    public int getVideoHeight() {\n        return mMediaPlayer.getVideoHeight();\n    }\n\n    public int getVideoWidth() {\n        return mMediaPlayer.getVideoWidth();\n    }\n\n    public boolean isLooping() {\n        return mMediaPlayer.isLooping();\n    }\n\n    public boolean isPlaying() {\n        return mMediaPlayer.isPlaying();\n    }\n\n    public void pause() {\n        mMediaPlayer.pause();\n    }\n\n    public void seekTo(int msec) {\n        mMediaPlayer.seekTo(msec);\n    }\n\n    public void setLooping(boolean looping) {\n        mMediaPlayer.setLooping(looping);\n    }\n\n    public void setVolume(float leftVolume, float rightVolume) {\n        mMediaPlayer.setVolume(leftVolume, rightVolume);\n    }\n\n    public void start() {\n        mMediaPlayer.start();\n    }\n\n    public void stop() {\n        mMediaPlayer.stop();\n    }\n\n    public void reset() {\n        mMediaPlayer.reset();\n    }\n\n    public void release() {\n        reset();\n        mMediaPlayer.release();\n        mMediaPlayer = null;\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/scalablevideoview/ScaleManager.java",
    "content": "package com.scalablevideoview;\n\nimport android.graphics.Matrix;\n\npublic class ScaleManager {\n\n    private Size mViewSize;\n    private Size mVideoSize;\n\n    public ScaleManager(Size viewSize, Size videoSize) {\n        mViewSize = viewSize;\n        mVideoSize = videoSize;\n    }\n\n    public Matrix getScaleMatrix(ScalableType scalableType) {\n        switch (scalableType) {\n            case NONE:\n                return getNoScale();\n\n            case FIT_XY:\n                return fitXY();\n            case FIT_CENTER:\n                return fitCenter();\n            case FIT_START:\n                return fitStart();\n            case FIT_END:\n                return fitEnd();\n\n            case LEFT_TOP:\n                return getOriginalScale(PivotPoint.LEFT_TOP);\n            case LEFT_CENTER:\n                return getOriginalScale(PivotPoint.LEFT_CENTER);\n            case LEFT_BOTTOM:\n                return getOriginalScale(PivotPoint.LEFT_BOTTOM);\n            case CENTER_TOP:\n                return getOriginalScale(PivotPoint.CENTER_TOP);\n            case CENTER:\n                return getOriginalScale(PivotPoint.CENTER);\n            case CENTER_BOTTOM:\n                return getOriginalScale(PivotPoint.CENTER_BOTTOM);\n            case RIGHT_TOP:\n                return getOriginalScale(PivotPoint.RIGHT_TOP);\n            case RIGHT_CENTER:\n                return getOriginalScale(PivotPoint.RIGHT_CENTER);\n            case RIGHT_BOTTOM:\n                return getOriginalScale(PivotPoint.RIGHT_BOTTOM);\n\n            case LEFT_TOP_CROP:\n                return getCropScale(PivotPoint.LEFT_TOP);\n            case LEFT_CENTER_CROP:\n                return getCropScale(PivotPoint.LEFT_CENTER);\n            case LEFT_BOTTOM_CROP:\n                return getCropScale(PivotPoint.LEFT_BOTTOM);\n            case CENTER_TOP_CROP:\n                return getCropScale(PivotPoint.CENTER_TOP);\n            case CENTER_CROP:\n                return getCropScale(PivotPoint.CENTER);\n            case CENTER_BOTTOM_CROP:\n                return getCropScale(PivotPoint.CENTER_BOTTOM);\n            case RIGHT_TOP_CROP:\n                return getCropScale(PivotPoint.RIGHT_TOP);\n            case RIGHT_CENTER_CROP:\n                return getCropScale(PivotPoint.RIGHT_CENTER);\n            case RIGHT_BOTTOM_CROP:\n                return getCropScale(PivotPoint.RIGHT_BOTTOM);\n\n            case START_INSIDE:\n                return startInside();\n            case CENTER_INSIDE:\n                return centerInside();\n            case END_INSIDE:\n                return endInside();\n\n            default:\n                return null;\n        }\n    }\n\n    private Matrix getMatrix(float sx, float sy, float px, float py) {\n        Matrix matrix = new Matrix();\n        matrix.setScale(sx, sy, px, py);\n        return matrix;\n    }\n\n    private Matrix getMatrix(float sx, float sy, PivotPoint pivotPoint) {\n        switch (pivotPoint) {\n            case LEFT_TOP:\n                return getMatrix(sx, sy, 0, 0);\n            case LEFT_CENTER:\n                return getMatrix(sx, sy, 0, mViewSize.getHeight() / 2f);\n            case LEFT_BOTTOM:\n                return getMatrix(sx, sy, 0, mViewSize.getHeight());\n            case CENTER_TOP:\n                return getMatrix(sx, sy, mViewSize.getWidth() / 2f, 0);\n            case CENTER:\n                return getMatrix(sx, sy, mViewSize.getWidth() / 2f, mViewSize.getHeight() / 2f);\n            case CENTER_BOTTOM:\n                return getMatrix(sx, sy, mViewSize.getWidth() / 2f, mViewSize.getHeight());\n            case RIGHT_TOP:\n                return getMatrix(sx, sy, mViewSize.getWidth(), 0);\n            case RIGHT_CENTER:\n                return getMatrix(sx, sy, mViewSize.getWidth(), mViewSize.getHeight() / 2f);\n            case RIGHT_BOTTOM:\n                return getMatrix(sx, sy, mViewSize.getWidth(), mViewSize.getHeight());\n            default:\n                throw new IllegalArgumentException(\"Illegal PivotPoint\");\n        }\n    }\n\n    private Matrix getNoScale() {\n        float sx = mVideoSize.getWidth() / (float) mViewSize.getWidth();\n        float sy = mVideoSize.getHeight() / (float) mViewSize.getHeight();\n        return getMatrix(sx, sy, PivotPoint.LEFT_TOP);\n    }\n\n    private Matrix getFitScale(PivotPoint pivotPoint) {\n        float sx = (float) mViewSize.getWidth() / mVideoSize.getWidth();\n        float sy = (float) mViewSize.getHeight() / mVideoSize.getHeight();\n        float minScale = Math.min(sx, sy);\n        sx = minScale / sx;\n        sy = minScale / sy;\n        return getMatrix(sx, sy, pivotPoint);\n    }\n\n    private Matrix fitXY() {\n        return getMatrix(1, 1, PivotPoint.LEFT_TOP);\n    }\n\n    private Matrix fitStart() {\n        return getFitScale(PivotPoint.LEFT_TOP);\n    }\n\n    private Matrix fitCenter() {\n        return getFitScale(PivotPoint.CENTER);\n    }\n\n    private Matrix fitEnd() {\n        return getFitScale(PivotPoint.RIGHT_BOTTOM);\n    }\n\n    private Matrix getOriginalScale(PivotPoint pivotPoint) {\n        float sx = mVideoSize.getWidth() / (float) mViewSize.getWidth();\n        float sy = mVideoSize.getHeight() / (float) mViewSize.getHeight();\n        return getMatrix(sx, sy, pivotPoint);\n    }\n\n    private Matrix getCropScale(PivotPoint pivotPoint) {\n        float sx = (float) mViewSize.getWidth() / mVideoSize.getWidth();\n        float sy = (float) mViewSize.getHeight() / mVideoSize.getHeight();\n        float maxScale = Math.max(sx, sy);\n        sx = maxScale / sx;\n        sy = maxScale / sy;\n        return getMatrix(sx, sy, pivotPoint);\n    }\n\n    private Matrix startInside() {\n        if (mVideoSize.getHeight() <= mViewSize.getWidth()\n                && mVideoSize.getHeight() <= mViewSize.getHeight()) {\n            // video is smaller than view size\n            return getOriginalScale(PivotPoint.LEFT_TOP);\n        } else {\n            // either of width or height of the video is larger than view size\n            return fitStart();\n        }\n    }\n\n    private Matrix centerInside() {\n        if (mVideoSize.getHeight() <= mViewSize.getWidth()\n                && mVideoSize.getHeight() <= mViewSize.getHeight()) {\n            // video is smaller than view size\n            return getOriginalScale(PivotPoint.CENTER);\n        } else {\n            // either of width or height of the video is larger than view size\n            return fitCenter();\n        }\n    }\n\n    private Matrix endInside() {\n        if (mVideoSize.getHeight() <= mViewSize.getWidth()\n                && mVideoSize.getHeight() <= mViewSize.getHeight()) {\n            // video is smaller than view size\n            return getOriginalScale(PivotPoint.RIGHT_BOTTOM);\n        } else {\n            // either of width or height of the video is larger than view size\n            return fitEnd();\n        }\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/scalablevideoview/Size.java",
    "content": "package com.scalablevideoview;\n\npublic class Size {\n\n    private int mWidth;\n    private int mHeight;\n\n    public Size(int width, int height) {\n        mWidth = width;\n        mHeight = height;\n    }\n\n    public int getWidth() {\n        return mWidth;\n    }\n\n    public int getHeight() {\n        return mHeight;\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/zipfile/APEZProvider.java",
    "content": "package com.zipfile;\n\n/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n//To implement APEZProvider in your application, you'll want to change\n//the AUTHORITY to match what you define in the manifest.\n\nimport com.zipfile.ZipResourceFile.ZipEntryRO;\n\nimport android.content.ContentProvider;\nimport android.content.ContentProviderOperation;\nimport android.content.ContentProviderResult;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.OperationApplicationException;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.content.pm.ProviderInfo;\nimport android.content.res.AssetFileDescriptor;\nimport android.database.Cursor;\nimport android.database.MatrixCursor;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.provider.BaseColumns;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.ArrayList;\n\n/**\n * This content provider is an optional part of the library.\n * \n * <p>Most apps don't need to use this class. This defines a\n * ContentProvider that marshalls the data from the ZIP files through a\n * content provider Uri in order to provide file access for certain Android APIs \n * that expect Uri access to media files.\n *\n */\npublic abstract class APEZProvider extends ContentProvider {\t\n\n\tprivate ZipResourceFile mAPKExtensionFile;\n\tprivate boolean mInit;\n\t\n\tpublic static final String FILEID = BaseColumns._ID;\n\tpublic static final String FILENAME = \"ZPFN\";\n\tpublic static final String ZIPFILE = \"ZFIL\";\n\tpublic static final String MODIFICATION = \"ZMOD\";\n\tpublic static final String CRC32 = \"ZCRC\";\n\tpublic static final String COMPRESSEDLEN = \"ZCOL\";\n\tpublic static final String UNCOMPRESSEDLEN = \"ZUNL\";\n\tpublic static final String COMPRESSIONTYPE = \"ZTYP\";\n\t\n\tpublic static final String[] ALL_FIELDS = {\n\t\tFILEID,\n\t\tFILENAME,\n\t\tZIPFILE,\n\t\tMODIFICATION,\n\t\tCRC32,\n\t\tCOMPRESSEDLEN,\n\t\tUNCOMPRESSEDLEN,\n\t\tCOMPRESSIONTYPE\n\t};\n\t\n\tpublic static final int FILEID_IDX = 0;\n\tpublic static final int FILENAME_IDX = 1;\n\tpublic static final int ZIPFILE_IDX = 2;\n\tpublic static final int MOD_IDX = 3;\n\tpublic static final int CRC_IDX = 4;\n\tpublic static final int COMPLEN_IDX = 5;\n\tpublic static final int UNCOMPLEN_IDX = 6;\n\tpublic static final int COMPTYPE_IDX = 7;\n\t\n\tpublic static final int[] ALL_FIELDS_INT = {\n\t\tFILEID_IDX,\n\t\tFILENAME_IDX,\n\t\tZIPFILE_IDX,\n\t\tMOD_IDX,\n\t\tCRC_IDX,\n\t\tCOMPLEN_IDX,\n\t\tUNCOMPLEN_IDX,\n\t\tCOMPTYPE_IDX\n\t};\n\t\n\t/**\n\t * This needs to match the authority in your manifest\n\t */\n\tpublic abstract String getAuthority();\n\t\n\t@Override\n\tpublic int delete(Uri arg0, String arg1, String[] arg2) {\n\t\t// TODO Auto-generated method stub\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic String getType(Uri uri) {\n\t\treturn \"vnd.android.cursor.item/asset\";\n\t}\n\n\t@Override\n\tpublic Uri insert(Uri uri, ContentValues values) {\n\t\t// TODO Auto-generated method stub\n\t\treturn null;\n\t}\n\t\n\tstatic private final String NO_FILE = \"N\";\n\t\n\tprivate boolean initIfNecessary() {\n\t    if ( !mInit ) {\n            Context ctx = getContext();\n            PackageManager pm = ctx.getPackageManager();\n            ProviderInfo pi = pm.resolveContentProvider(getAuthority(), PackageManager.GET_META_DATA);\n            PackageInfo packInfo;\n            try {\n                packInfo = pm.getPackageInfo(ctx.getPackageName(), 0);\n            } catch (NameNotFoundException e1) {\n                e1.printStackTrace();\n                return false;\n            }\n            int patchFileVersion;\n            int mainFileVersion;\n            int appVersionCode = packInfo.versionCode;\n            String[] resourceFiles = null;\n            if ( null != pi.metaData ) {\n                mainFileVersion = pi.metaData.getInt(\"mainVersion\", appVersionCode);\n                patchFileVersion = pi.metaData.getInt(\"patchVersion\", appVersionCode);\n                String mainFileName = pi.metaData.getString(\"mainFilename\", NO_FILE);\n                if ( NO_FILE != mainFileName ) {\n                    String patchFileName = pi.metaData.getString(\"patchFilename\", NO_FILE);\n                    if ( NO_FILE != patchFileName ) {\n                        resourceFiles = new String[] { mainFileName, patchFileName };\n                    } else {\n                        resourceFiles = new String[] { mainFileName };\n                    }\n                }\n            } else {\n                mainFileVersion = patchFileVersion = appVersionCode;\n            }\n            try {\n                if ( null == resourceFiles ) {\n                    mAPKExtensionFile = APKExpansionSupport.getAPKExpansionZipFile(ctx, mainFileVersion, patchFileVersion);\n                } else {\n                    mAPKExtensionFile = APKExpansionSupport.getResourceZipFile(resourceFiles);\n                }\n                mInit = true;\n                return true;\n            } catch (IOException e) {\n                e.printStackTrace();                \n            }\n\t    }\n        return false;\t    \n\t}\n\n\t@Override\n\tpublic boolean onCreate() {\n\t    return true;\n\t}\n\n\t@Override\n\tpublic AssetFileDescriptor openAssetFile(Uri uri, String mode)\n\t\t\tthrows FileNotFoundException {\n        initIfNecessary();\n\t\tString path = uri.getEncodedPath();\n\t\tif ( path.startsWith(\"/\") ) {\n\t\t\tpath = path.substring(1);\n\t\t}\n\t\treturn mAPKExtensionFile.getAssetFileDescriptor(path);\t\t\n\t}\n\n\t@Override\n\tpublic ContentProviderResult[] applyBatch(\n\t\t\tArrayList<ContentProviderOperation> operations)\n\t\t\tthrows OperationApplicationException {\n        initIfNecessary();\n\t\treturn super.applyBatch(operations);\n\t}\n\n\t@Override\n\tpublic ParcelFileDescriptor openFile(Uri uri, String mode)\n\t\t\tthrows FileNotFoundException {\n        initIfNecessary();\n\t\tAssetFileDescriptor af = openAssetFile(uri, mode);\n\t\tif ( null != af ) {\n\t\t\treturn af.getParcelFileDescriptor();\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic Cursor query(Uri uri, String[] projection, String selection,\n\t\t\tString[] selectionArgs, String sortOrder) {\n\t    initIfNecessary();\n\t\t// lists all of the items in the file that match\n\t\tZipEntryRO[] zipEntries;\n\t\tif ( null == mAPKExtensionFile ) {\n\t\t\tzipEntries = new ZipEntryRO[0];\n\t\t} else {\n\t\t\tzipEntries = mAPKExtensionFile.getAllEntries();\n\t\t}\n\t\tint[] intProjection;\n\t\tif ( null == projection )  {\n\t\t\tintProjection = ALL_FIELDS_INT;\n\t\t\tprojection = ALL_FIELDS;\n\t\t} else {\n\t\t\tint len = projection.length;\n\t\t\tintProjection = new int[len];\n\t\t\tfor ( int i = 0; i < len; i++ ) {\n\t\t\t\tif ( projection[i].equals(FILEID) ) {\n\t\t\t\t\tintProjection[i] = FILEID_IDX;\n\t\t\t\t} else if ( projection[i].equals(FILENAME) ) {\n\t\t\t\t\tintProjection[i] = FILENAME_IDX;\n\t\t\t\t} else if ( projection[i].equals(ZIPFILE) ) {\n\t\t\t\t\tintProjection[i] = ZIPFILE_IDX;\n\t\t\t\t} else if ( projection[i].equals(MODIFICATION) ) {\n\t\t\t\t\tintProjection[i] = MOD_IDX;\n\t\t\t\t} else if ( projection[i].equals(CRC32) ) {\n\t\t\t\t\tintProjection[i] = CRC_IDX;\n\t\t\t\t} else if ( projection[i].equals(COMPRESSEDLEN) ) {\n\t\t\t\t\tintProjection[i] = COMPLEN_IDX;\n\t\t\t\t} else if ( projection[i].equals(UNCOMPRESSEDLEN) ) {\n\t\t\t\t\tintProjection[i] = UNCOMPLEN_IDX;\n\t\t\t\t} else if ( projection[i].equals(COMPRESSIONTYPE) ) {\n\t\t\t\t\tintProjection[i] = COMPTYPE_IDX;\n\t\t\t\t} else {\n\t\t\t\t\tthrow new RuntimeException();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tMatrixCursor mc = new MatrixCursor(projection, zipEntries.length);\n\t\tint len = intProjection.length;\n\t\tfor ( ZipEntryRO zer : zipEntries ) {\n\t\t\tMatrixCursor.RowBuilder rb = mc.newRow();\n\t\t\tfor ( int i = 0; i < len; i++ ) {\t\t\t\t\n\t\t\t\tswitch (intProjection[i]) {\n\t\t\t\t\tcase FILEID_IDX:\n\t\t\t\t\t\trb.add(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase FILENAME_IDX:\n\t\t\t\t\t\trb.add(zer.mFileName);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ZIPFILE_IDX:\n\t\t\t\t\t\trb.add(zer.getZipFileName());\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase MOD_IDX:\n\t\t\t\t\t\trb.add(zer.mWhenModified);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase CRC_IDX:\n\t\t\t\t\t\trb.add(zer.mCRC32);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase COMPLEN_IDX:\n\t\t\t\t\t\trb.add(zer.mCompressedLength);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase UNCOMPLEN_IDX:\n\t\t\t\t\t\trb.add(zer.mUncompressedLength);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase COMPTYPE_IDX:\n\t\t\t\t\t\trb.add(zer.mMethod);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn mc;\n\t}\n\n\t@Override\n\tpublic int update(Uri uri, ContentValues values, String selection,\n\t\t\tString[] selectionArgs) {\n\t\t// TODO Auto-generated method stub\n\t\treturn 0;\n\t}\n\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/zipfile/APKExpansionSupport.java",
    "content": "package com.zipfile;\n/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.io.IOException;\nimport java.util.Vector;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport android.content.Context;\nimport android.os.Environment;\nimport android.util.Log;\n\npublic class APKExpansionSupport {\n\t// The shared path to all app expansion files\n\tprivate final static String EXP_PATH = \"/Android/obb/\";\n\n\tstatic String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {\n\t\tString packageName = ctx.getPackageName();\n\t\tVector<String> ret = new Vector<String>();\n\t\tif (Environment.getExternalStorageState().equals(\n\t\t\t\tEnvironment.MEDIA_MOUNTED)) {\n\t\t\t// Build the full path to the app's expansion files\n\t\t\tFile root = Environment.getExternalStorageDirectory();\n\t\t\tFile expPath = new File(root.toString() + EXP_PATH + packageName);\n\n\t\t\t// Check that expansion file path exists\n\t\t\tif (expPath.exists()) {\n\t\t\t\tif ( mainVersion > 0 ) {\n\t\t\t\t\tString strMainPath = expPath + File.separator + \"main.\" + mainVersion + \".\" + packageName + \".obb\";\n//\t\t\t\t\tLog.d(\"APKEXPANSION\", strMainPath);\n\t\t\t\t\tFile main = new File(strMainPath);\n\t\t\t\t\tif ( main.isFile() ) {\n\t\t\t\t\t\tret.add(strMainPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( patchVersion > 0 ) {\n\t\t\t\t\tString strPatchPath = expPath + File.separator + \"patch.\" + patchVersion + \".\" + packageName + \".obb\";\n\t\t\t\t\tFile main = new File(strPatchPath);\n\t\t\t\t\tif ( main.isFile() ) {\n\t\t\t\t\t\tret.add(strPatchPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tString[] retArray = new String[ret.size()];\n\t\tret.toArray(retArray);\n\t\treturn retArray;\n\t}\n\n\tstatic public ZipResourceFile getResourceZipFile(String[] expansionFiles) throws IOException {\n        ZipResourceFile apkExpansionFile = null;\n        for (String expansionFilePath : expansionFiles) {\n            if ( null == apkExpansionFile ) {\n                apkExpansionFile = new ZipResourceFile(expansionFilePath);\n            } else {\n                apkExpansionFile.addPatchFile(expansionFilePath);\n            }\n        }\n        return apkExpansionFile;\n\t}\n\t\n\tstatic public ZipResourceFile getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) throws IOException{\n\t\tString[] expansionFiles = getAPKExpansionFiles(ctx, mainVersion, patchVersion);\n\t\treturn getResourceZipFile(expansionFiles);\n\t}\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/java/com/zipfile/ZipResourceFile.java",
    "content": "\npackage com.zipfile;\n\n/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport android.content.res.AssetFileDescriptor;\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\n\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.RandomAccessFile;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Vector;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\npublic class ZipResourceFile {\n\n    //\n    // Read-only access to Zip archives, with minimal heap allocation.\n    //\n    static final String LOG_TAG = \"zipro\";\n    static final boolean LOGV = false;\n\n    // 4-byte number\n    static private int swapEndian(int i)\n    {\n        return ((i & 0xff) << 24) + ((i & 0xff00) << 8) + ((i & 0xff0000) >>> 8)\n                + ((i >>> 24) & 0xff);\n    }\n\n    // 2-byte number\n    static private int swapEndian(short i)\n    {\n        return ((i & 0x00FF) << 8 | (i & 0xFF00) >>> 8);\n    }\n\n    /*\n     * Zip file constants.\n     */\n    static final int kEOCDSignature = 0x06054b50;\n    static final int kEOCDLen = 22;\n    static final int kEOCDNumEntries = 8; // offset to #of entries in file\n    static final int kEOCDSize = 12; // size of the central directory\n    static final int kEOCDFileOffset = 16; // offset to central directory\n\n    static final int kMaxCommentLen = 65535; // longest possible in ushort\n    static final int kMaxEOCDSearch = (kMaxCommentLen + kEOCDLen);\n\n    static final int kLFHSignature = 0x04034b50;\n    static final int kLFHLen = 30; // excluding variable-len fields\n    static final int kLFHNameLen = 26; // offset to filename length\n    static final int kLFHExtraLen = 28; // offset to extra length\n\n    static final int kCDESignature = 0x02014b50;\n    static final int kCDELen = 46; // excluding variable-len fields\n    static final int kCDEMethod = 10; // offset to compression method\n    static final int kCDEModWhen = 12; // offset to modification timestamp\n    static final int kCDECRC = 16; // offset to entry CRC\n    static final int kCDECompLen = 20; // offset to compressed length\n    static final int kCDEUncompLen = 24; // offset to uncompressed length\n    static final int kCDENameLen = 28; // offset to filename length\n    static final int kCDEExtraLen = 30; // offset to extra length\n    static final int kCDECommentLen = 32; // offset to comment length\n    static final int kCDELocalOffset = 42; // offset to local hdr\n\n    static final int kCompressStored = 0; // no compression\n    static final int kCompressDeflated = 8; // standard deflate\n\n    /*\n     * The values we return for ZipEntryRO use 0 as an invalid value, so we want\n     * to adjust the hash table index by a fixed amount. Using a large value\n     * helps insure that people don't mix & match arguments, e.g. to\n     * findEntryByIndex().\n     */\n    static final int kZipEntryAdj = 10000;\n\n    static public final class ZipEntryRO {\n        public ZipEntryRO(final String zipFileName, final File file, final String fileName) {\n            mFileName = fileName;\n            mZipFileName = zipFileName;\n            mFile = file;\n        }\n\n        public final File mFile;\n        public final String mFileName;\n        public final String mZipFileName;\n        public long mLocalHdrOffset; // offset of local file header\n\n        /* useful stuff from the directory entry */\n        public int mMethod;\n        public long mWhenModified;\n        public long mCRC32;\n        public long mCompressedLength;\n        public long mUncompressedLength;\n\n        public long mOffset = -1;\n\n        public void setOffsetFromFile(RandomAccessFile f, ByteBuffer buf) throws IOException {\n            long localHdrOffset = mLocalHdrOffset;\n            try {\n                f.seek(localHdrOffset);\n                f.readFully(buf.array());\n                if (buf.getInt(0) != kLFHSignature) {\n                    Log.w(LOG_TAG, \"didn't find signature at start of lfh\");\n                    throw new IOException();\n                }\n                int nameLen = buf.getShort(kLFHNameLen) & 0xFFFF;\n                int extraLen = buf.getShort(kLFHExtraLen) & 0xFFFF;\n                mOffset = localHdrOffset + kLFHLen + nameLen + extraLen;\n            } catch (FileNotFoundException e) {\n                e.printStackTrace();\n            } catch (IOException ioe) {\n                ioe.printStackTrace();\n            }\n        }\n\n        /**\n         * Calculates the offset of the start of the Zip file entry within the\n         * Zip file.\n         * \n         * @return the offset, in bytes from the start of the file of the entry\n         */\n        public long getOffset() {\n            return mOffset;\n        }\n\n        /**\n         * isUncompressed\n         * \n         * @return true if the file is stored in uncompressed form\n         */\n        public boolean isUncompressed() {\n            return mMethod == kCompressStored;\n        }\n\n        public AssetFileDescriptor getAssetFileDescriptor() {\n            if (mMethod == kCompressStored) {\n                ParcelFileDescriptor pfd;\n                try {\n                    pfd = ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY);\n                    return new AssetFileDescriptor(pfd, getOffset(), mUncompressedLength);\n                } catch (FileNotFoundException e) {\n                    // TODO Auto-generated catch block\n                    e.printStackTrace();\n                }\n            }\n            return null;\n        }\n\n        public String getZipFileName() {\n            return mZipFileName;\n        }\n\n        public File getZipFile() {\n            return mFile;\n        }\n\n    }\n\n    private HashMap<String, ZipEntryRO> mHashMap = new HashMap<String, ZipEntryRO>();\n\n    /* for reading compressed files */\n    public HashMap<File, ZipFile> mZipFiles = new HashMap<File, ZipFile>();\n\n    public ZipResourceFile(String zipFileName) throws IOException {\n        addPatchFile(zipFileName);\n    }\n\n    ZipEntryRO[] getEntriesAt(String path) {\n        Vector<ZipEntryRO> zev = new Vector<ZipEntryRO>();\n        Collection<ZipEntryRO> values = mHashMap.values();\n        if (null == path)\n            path = \"\";\n        int length = path.length();\n        for (ZipEntryRO ze : values) {\n            if (ze.mFileName.startsWith(path)) {\n                if (-1 == ze.mFileName.indexOf('/', length)) {\n                    zev.add(ze);\n                }\n            }\n        }\n        ZipEntryRO[] entries = new ZipEntryRO[zev.size()];\n        return zev.toArray(entries);\n    }\n\n    public ZipEntryRO[] getAllEntries() {\n        Collection<ZipEntryRO> values = mHashMap.values();\n        return values.toArray(new ZipEntryRO[values.size()]);\n    }\n\n    /**\n     * getAssetFileDescriptor allows for ZipResourceFile to directly feed\n     * Android API's that want an fd, offset, and length such as the\n     * MediaPlayer. It also allows for the class to be used in a content\n     * provider that can feed video players. The file must be stored\n     * (non-compressed) in the Zip file for this to work.\n     * \n     * @param assetPath\n     * @return the asset file descriptor for the file, or null if the file isn't\n     *         present or is stored compressed\n     */\n    public AssetFileDescriptor getAssetFileDescriptor(String assetPath) {\n        ZipEntryRO entry = mHashMap.get(assetPath);\n        if (null != entry) {\n            return entry.getAssetFileDescriptor();\n        }\n        return null;\n    }\n\n    /**\n     * getInputStream returns an AssetFileDescriptor.AutoCloseInputStream\n     * associated with the asset that is contained in the Zip file, or a\n     * standard ZipInputStream if necessary to uncompress the file\n     * \n     * @param assetPath\n     * @return an input stream for the named asset path, or null if not found\n     * @throws IOException\n     */\n    public InputStream getInputStream(String assetPath) throws IOException {\n        ZipEntryRO entry = mHashMap.get(assetPath);\n        if (null != entry) {\n            if (entry.isUncompressed()) {\n                return entry.getAssetFileDescriptor().createInputStream();\n            } else {\n                ZipFile zf = mZipFiles.get(entry.getZipFile());\n                /** read compressed files **/\n                if (null == zf) {\n                    zf = new ZipFile(entry.getZipFile(), ZipFile.OPEN_READ);\n                    mZipFiles.put(entry.getZipFile(), zf);\n                }\n                ZipEntry zi = zf.getEntry(assetPath);\n                if (null != zi)\n                    return zf.getInputStream(zi);\n            }\n        }\n        return null;\n    }\n\n    ByteBuffer mLEByteBuffer = ByteBuffer.allocate(4);\n\n    static private int read4LE(RandomAccessFile f) throws EOFException, IOException {\n        return swapEndian(f.readInt());\n    }\n    \n    /*\n     * Opens the specified file read-only. We memory-map the entire thing and\n     * close the file before returning.\n     */\n    void addPatchFile(String zipFileName) throws IOException\n    {\n        File file = new File(zipFileName);\n        RandomAccessFile f = new RandomAccessFile(file, \"r\");\n        long fileLength = f.length();\n\n        if (fileLength < kEOCDLen) {\n            throw new java.io.IOException();\n        }\n\n        long readAmount = kMaxEOCDSearch;\n        if (readAmount > fileLength)\n            readAmount = fileLength;\n\n        /*\n         * Make sure this is a Zip archive.\n         */\n        f.seek(0);\n\n        int header = read4LE(f);\n        if (header == kEOCDSignature) {\n            Log.i(LOG_TAG, \"Found Zip archive, but it looks empty\");\n            throw new IOException();\n        } else if (header != kLFHSignature) {\n            Log.v(LOG_TAG, \"Not a Zip archive\");\n            throw new IOException();\n        }\n\n        /*\n         * Perform the traditional EOCD snipe hunt. We're searching for the End\n         * of Central Directory magic number, which appears at the start of the\n         * EOCD block. It's followed by 18 bytes of EOCD stuff and up to 64KB of\n         * archive comment. We need to read the last part of the file into a\n         * buffer, dig through it to find the magic number, parse some values\n         * out, and use those to determine the extent of the CD. We start by\n         * pulling in the last part of the file.\n         */\n        long searchStart = fileLength - readAmount;\n\n        f.seek(searchStart);\n        ByteBuffer bbuf = ByteBuffer.allocate((int) readAmount);\n        byte[] buffer = bbuf.array();\n        f.readFully(buffer);\n        bbuf.order(ByteOrder.LITTLE_ENDIAN);\n\n        /*\n         * Scan backward for the EOCD magic. In an archive without a trailing\n         * comment, we'll find it on the first try. (We may want to consider\n         * doing an initial minimal read; if we don't find it, retry with a\n         * second read as above.)\n         */\n\n        // EOCD == 0x50, 0x4b, 0x05, 0x06\n        int eocdIdx;\n        for (eocdIdx = buffer.length - kEOCDLen; eocdIdx >= 0; eocdIdx--) {\n            if (buffer[eocdIdx] == 0x50 && bbuf.getInt(eocdIdx) == kEOCDSignature)\n            {\n                if (LOGV) {\n                    Log.v(LOG_TAG, \"+++ Found EOCD at index: \" + eocdIdx);\n                }\n                break;\n            }\n        }\n\n        if (eocdIdx < 0) {\n            Log.d(LOG_TAG, \"Zip: EOCD not found, \" + zipFileName + \" is not zip\");\n        }\n\n        /*\n         * Grab the CD offset and size, and the number of entries in the\n         * archive. After that, we can release our EOCD hunt buffer.\n         */\n\n        int numEntries = bbuf.getShort(eocdIdx + kEOCDNumEntries);\n        long dirSize = bbuf.getInt(eocdIdx + kEOCDSize) & 0xffffffffL;\n        long dirOffset = bbuf.getInt(eocdIdx + kEOCDFileOffset) & 0xffffffffL;\n\n        // Verify that they look reasonable.\n        if (dirOffset + dirSize > fileLength) {\n            Log.w(LOG_TAG, \"bad offsets (dir \" + dirOffset + \", size \" + dirSize + \", eocd \"\n                    + eocdIdx + \")\");\n            throw new IOException();\n        }\n        if (numEntries == 0) {\n            Log.w(LOG_TAG, \"empty archive?\");\n            throw new IOException();\n        }\n\n        if (LOGV) {\n            Log.v(LOG_TAG, \"+++ numEntries=\" + numEntries + \" dirSize=\" + dirSize + \" dirOffset=\"\n                    + dirOffset);\n        }\n\n        MappedByteBuffer directoryMap = f.getChannel()\n                .map(FileChannel.MapMode.READ_ONLY, dirOffset, dirSize);\n        directoryMap.order(ByteOrder.LITTLE_ENDIAN);\n\n        byte[] tempBuf = new byte[0xffff];\n\n        /*\n         * Walk through the central directory, adding entries to the hash table.\n         */\n\n        int currentOffset = 0;\n\n        /*\n         * Allocate the local directory information\n         */\n        ByteBuffer buf = ByteBuffer.allocate(kLFHLen);\n        buf.order(ByteOrder.LITTLE_ENDIAN);\n\n        for (int i = 0; i < numEntries; i++) {\n            if (directoryMap.getInt(currentOffset) != kCDESignature) {\n                Log.w(LOG_TAG, \"Missed a central dir sig (at \" + currentOffset + \")\");\n                throw new IOException();\n            }\n\n            /* useful stuff from the directory entry */\n            int fileNameLen = directoryMap.getShort(currentOffset + kCDENameLen) & 0xffff;\n            int extraLen = directoryMap.getShort(currentOffset + kCDEExtraLen) & 0xffff;\n            int commentLen = directoryMap.getShort(currentOffset + kCDECommentLen) & 0xffff;\n\n            /* get the CDE filename */\n\n            directoryMap.position(currentOffset + kCDELen);\n            directoryMap.get(tempBuf, 0, fileNameLen);\n            directoryMap.position(0);\n\n            /* UTF-8 on Android */\n            String str = new String(tempBuf, 0, fileNameLen);\n            if (LOGV) {\n                Log.v(LOG_TAG, \"Filename: \" + str);\n            }\n\n            ZipEntryRO ze = new ZipEntryRO(zipFileName, file, str);\n            ze.mMethod = directoryMap.getShort(currentOffset + kCDEMethod) & 0xffff;\n            ze.mWhenModified = directoryMap.getInt(currentOffset + kCDEModWhen) & 0xffffffffL;\n            ze.mCRC32 = directoryMap.getLong(currentOffset + kCDECRC) & 0xffffffffL;\n            ze.mCompressedLength = directoryMap.getLong(currentOffset + kCDECompLen) & 0xffffffffL;\n            ze.mUncompressedLength = directoryMap.getLong(currentOffset + kCDEUncompLen) & 0xffffffffL;\n            ze.mLocalHdrOffset = directoryMap.getInt(currentOffset + kCDELocalOffset) & 0xffffffffL;\n\n            // set the offsets\n            buf.clear();\n            ze.setOffsetFromFile(f, buf);\n\n            // put file into hash\n            mHashMap.put(str, ze);\n\n            // go to next directory entry\n            currentOffset += kCDELen + fileNameLen + extraLen + commentLen;\n        }\n        if (LOGV) {\n            Log.v(LOG_TAG, \"+++ zip good scan \" + numEntries + \" entries\");\n        }\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"scaleStyle\">\n        <attr name=\"scalableType\">\n            <enum name=\"none\" value=\"0\"/>\n\n            <enum name=\"fitXY\" value=\"1\"/>\n            <enum name=\"fitStart\" value=\"2\"/>\n            <enum name=\"fitCenter\" value=\"3\"/>\n            <enum name=\"fitEnd\" value=\"4\"/>\n\n            <enum name=\"leftTop\" value=\"5\"/>\n            <enum name=\"leftCenter\" value=\"6\"/>\n            <enum name=\"leftBottom\" value=\"7\"/>\n            <enum name=\"centerTop\" value=\"8\"/>\n            <enum name=\"center\" value=\"9\"/>\n            <enum name=\"centerBottom\" value=\"10\"/>\n            <enum name=\"rightTop\" value=\"11\"/>\n            <enum name=\"rightCenter\" value=\"12\"/>\n            <enum name=\"rightBottom\" value=\"13\"/>\n\n            <enum name=\"leftTopCrop\" value=\"14\"/>\n            <enum name=\"leftCenterCrop\" value=\"15\"/>\n            <enum name=\"leftBottomCrop\" value=\"16\"/>\n            <enum name=\"centerTopCrop\" value=\"17\"/>\n            <enum name=\"centerCrop\" value=\"18\"/>\n            <enum name=\"centerBottomCrop\" value=\"19\"/>\n            <enum name=\"rightTopCrop\" value=\"20\"/>\n            <enum name=\"rightCenterCrop\" value=\"21\"/>\n            <enum name=\"rightBottomCrop\" value=\"22\"/>\n\n            <enum name=\"startInside\" value=\"23\"/>\n            <enum name=\"centerInside\" value=\"24\"/>\n            <enum name=\"endInside\" value=\"25\"/>\n        </attr>\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "packages/ChatUiKit/android/src/main/res/xml/filepaths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <external-path\n        name=\"external_files\"\n        path=\".\" />\n</paths>"
  },
  {
    "path": "packages/ChatUiKit/babel.config.js",
    "content": "module.exports = {\n  presets: ['module:@react-native/babel-preset'],\n};\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometChatSoundModule.h",
    "content": "#if __has_include(<React/RCTBridgeModule.h>)\n#import <React/RCTBridgeModule.h>\n#else\n#import \"RCTBridgeModule.h\"\n#endif\n\n#import <AVFoundation/AVFoundation.h>\n\n#if __has_include(<React/RCTEventEmitter.h>)\n#import <React/RCTEventEmitter.h>\n#else\n#import \"RCTEventEmitter.h\"\n#endif\n\n@interface CometChatSoundModule : RCTEventEmitter <RCTBridgeModule, AVAudioPlayerDelegate>\n@property (nonatomic, weak) NSNumber *_key;\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometChatSoundModule.m",
    "content": "#import \"CometChatSoundModule.h\"\n\n#if __has_include(\"RCTUtils.h\")\n#import \"RCTUtils.h\"\n#else\n#import <React/RCTUtils.h>\n#endif\n\n@implementation CometChatSoundModule {\n    NSMutableDictionary *_playerPool;\n    NSMutableDictionary *_callbackPool;\n}\n\n@synthesize _key = _key;\n\n- (void)audioSessionChangeObserver:(NSNotification *)notification {\n    NSDictionary *userInfo = notification.userInfo;\n    AVAudioSessionRouteChangeReason audioSessionRouteChangeReason =\n        [userInfo[@\"AVAudioSessionRouteChangeReasonKey\"] longValue];\n    AVAudioSessionInterruptionType audioSessionInterruptionType =\n        [userInfo[@\"AVAudioSessionInterruptionTypeKey\"] longValue];\n    AVAudioPlayer *player = [self playerForKey:self._key];\n    if (audioSessionInterruptionType == AVAudioSessionInterruptionTypeEnded) {\n        if (player && player.isPlaying) {\n            [player play];\n            [self setOnPlay:YES forPlayerKey:self._key];\n        }\n    }\n    if (audioSessionRouteChangeReason ==\n        AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {\n        if (player) {\n            [player pause];\n            [self setOnPlay:NO forPlayerKey:self._key];\n        }\n    }\n    if (audioSessionInterruptionType == AVAudioSessionInterruptionTypeBegan) {\n        if (player) {\n            [player pause];\n            [self setOnPlay:NO forPlayerKey:self._key];\n        }\n    }\n}\n\n- (NSMutableDictionary *)playerPool {\n    if (!_playerPool) {\n        _playerPool = [NSMutableDictionary new];\n    }\n    return _playerPool;\n}\n\n- (NSMutableDictionary *)callbackPool {\n    if (!_callbackPool) {\n        _callbackPool = [NSMutableDictionary new];\n    }\n    return _callbackPool;\n}\n\n- (AVAudioPlayer *)playerForKey:(nonnull NSNumber *)key {\n    return [[self playerPool] objectForKey:key];\n}\n\n- (NSNumber *)keyForPlayer:(nonnull AVAudioPlayer *)player {\n    return [[[self playerPool] allKeysForObject:player] firstObject];\n}\n\n- (RCTResponseSenderBlock)callbackForKey:(nonnull NSNumber *)key {\n    return [[self callbackPool] objectForKey:key];\n}\n\n- (NSString *)getDirectory:(int)directory {\n    return [NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask,\n                                                YES) firstObject];\n}\n\n- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player\n                       successfully:(BOOL)flag {\n    @synchronized(self) {\n        NSNumber *key = [self keyForPlayer:player];\n        if (key == nil)\n            return;\n\n        [self setOnPlay:NO forPlayerKey:key];\n        RCTResponseSenderBlock callback = [self callbackForKey:key];\n        if (callback) {\n            callback(\n                [NSArray arrayWithObjects:[NSNumber numberWithBool:flag], nil]);\n            [[self callbackPool] removeObjectForKey:key];\n        }\n    }\n}\n\nRCT_EXPORT_MODULE();\n\n- (NSArray<NSString *> *)supportedEvents {\n    return [NSArray arrayWithObjects:@\"onPlayChange\", nil];\n}\n\n- (NSDictionary *)constantsToExport {\n    return [NSDictionary\n        dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], @\"IsAndroid\",\n                                     [[NSBundle mainBundle] bundlePath],\n                                     @\"MainBundlePath\",\n                                     [self getDirectory:NSDocumentDirectory],\n                                     @\"NSDocumentDirectory\",\n                                     [self getDirectory:NSLibraryDirectory],\n                                     @\"NSLibraryDirectory\",\n                                     [self getDirectory:NSCachesDirectory],\n                                     @\"NSCachesDirectory\", nil];\n}\n\nRCT_EXPORT_METHOD(enable : (BOOL)enabled) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    [session setCategory:AVAudioSessionCategoryAmbient error:nil];\n    [session setActive:enabled error:nil];\n}\n\nRCT_EXPORT_METHOD(setActive : (BOOL)active) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    [session setActive:active error:nil];\n}\n\nRCT_EXPORT_METHOD(setMode : (NSString *)modeName) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    NSString *mode = nil;\n\n    if ([modeName isEqual:@\"Default\"]) {\n        mode = AVAudioSessionModeDefault;\n    } else if ([modeName isEqual:@\"VoiceChat\"]) {\n        mode = AVAudioSessionModeVoiceChat;\n    } else if ([modeName isEqual:@\"VideoChat\"]) {\n        mode = AVAudioSessionModeVideoChat;\n    } else if ([modeName isEqual:@\"GameChat\"]) {\n        mode = AVAudioSessionModeGameChat;\n    } else if ([modeName isEqual:@\"VideoRecording\"]) {\n        mode = AVAudioSessionModeVideoRecording;\n    } else if ([modeName isEqual:@\"Measurement\"]) {\n        mode = AVAudioSessionModeMeasurement;\n    } else if ([modeName isEqual:@\"MoviePlayback\"]) {\n        mode = AVAudioSessionModeMoviePlayback;\n    } else if ([modeName isEqual:@\"SpokenAudio\"]) {\n        mode = AVAudioSessionModeSpokenAudio;\n    }\n\n    if (mode) {\n        [session setMode:mode error:nil];\n    }\n}\n\nRCT_EXPORT_METHOD(setCategory\n                  : (NSString *)categoryName mixWithOthers\n                  : (BOOL)mixWithOthers) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    NSString *category = nil;\n\n    if ([categoryName isEqual:@\"Ambient\"]) {\n        category = AVAudioSessionCategoryAmbient;\n    } else if ([categoryName isEqual:@\"SoloAmbient\"]) {\n        category = AVAudioSessionCategorySoloAmbient;\n    } else if ([categoryName isEqual:@\"Playback\"]) {\n        category = AVAudioSessionCategoryPlayback;\n    } else if ([categoryName isEqual:@\"Record\"]) {\n        category = AVAudioSessionCategoryRecord;\n    } else if ([categoryName isEqual:@\"PlayAndRecord\"]) {\n        category = AVAudioSessionCategoryPlayAndRecord;\n    }\n#if TARGET_OS_IOS\n    else if ([categoryName isEqual:@\"AudioProcessing\"]) {\n        category = AVAudioSessionCategoryAudioProcessing;\n    }\n#endif\n    else if ([categoryName isEqual:@\"MultiRoute\"]) {\n        category = AVAudioSessionCategoryMultiRoute;\n    }\n\n    if (category) {\n        if (mixWithOthers) {\n            [session setCategory:category\n                     withOptions:AVAudioSessionCategoryOptionMixWithOthers |\n                                 AVAudioSessionCategoryOptionAllowBluetooth\n                           error:nil];\n        } else {\n            [session setCategory:category error:nil];\n        }\n    }\n}\n\nRCT_EXPORT_METHOD(enableInSilenceMode : (BOOL)enabled) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    [session setCategory:AVAudioSessionCategoryPlayback error:nil];\n    [session setActive:enabled error:nil];\n}\n\nRCT_EXPORT_METHOD(prepare\n                  : (NSString *)fileName withKey\n                  : (nonnull NSNumber *)key withOptions\n                  : (NSDictionary *)options withCallback\n                  : (RCTResponseSenderBlock)callback) {\n    NSError *error;\n    NSURL *fileNameUrl;\n    AVAudioPlayer *player;\n    NSString* fileNameEscaped = [fileName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\n\n    if ([fileNameEscaped hasPrefix:@\"http\"]) {\n        fileNameUrl = [NSURL URLWithString:fileNameEscaped];\n        NSData *data = [NSData dataWithContentsOfURL:fileNameUrl];\n        player = [[AVAudioPlayer alloc] initWithData:data error:&error];\n    } else if ([fileNameEscaped hasPrefix:@\"ipod-library://\"]) {\n        fileNameUrl = [NSURL URLWithString:fileNameEscaped];\n        player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileNameUrl\n                                                        error:&error];\n    } else {\n        fileNameUrl = [NSURL URLWithString:fileNameEscaped];\n        player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileNameUrl\n                                                        error:&error];\n    }\n\n    if (player) {\n        @synchronized(self) {\n            player.delegate = self;\n            player.enableRate = YES;\n            [player prepareToPlay];\n            [[self playerPool] setObject:player forKey:key];\n            callback([NSArray\n                arrayWithObjects:[NSNull null],\n                                 [NSDictionary\n                                     dictionaryWithObjectsAndKeys:\n                                         [NSNumber\n                                             numberWithDouble:player.duration],\n                                         @\"duration\",\n                                         [NSNumber numberWithUnsignedInteger:\n                                                       player.numberOfChannels],\n                                         @\"numberOfChannels\", nil],\n                                 nil]);\n        }\n    } else {\n        callback([NSArray arrayWithObjects:RCTJSErrorFromNSError(error), nil]);\n    }\n}\n\nRCT_EXPORT_METHOD(play\n                  : (nonnull NSNumber *)key withCallback\n                  : (RCTResponseSenderBlock)callback) {\n    [[AVAudioSession sharedInstance] setActive:YES error:nil];\n    [[NSNotificationCenter defaultCenter]\n        addObserver:self\n           selector:@selector(audioSessionChangeObserver:)\n               name:AVAudioSessionRouteChangeNotification\n             object:nil];\n    self._key = key;\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        [[self callbackPool] setObject:[callback copy] forKey:key];\n        [player play];\n        [self setOnPlay:YES forPlayerKey:key];\n    }\n}\n\nRCT_EXPORT_METHOD(pause\n                  : (nonnull NSNumber *)key withCallback\n                  : (RCTResponseSenderBlock)callback) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        [player pause];\n        callback([NSArray array]);\n    }\n}\n\nRCT_EXPORT_METHOD(stop\n                  : (nonnull NSNumber *)key withCallback\n                  : (RCTResponseSenderBlock)callback) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        [player stop];\n        player.currentTime = 0;\n        callback([NSArray array]);\n    }\n}\n\nRCT_EXPORT_METHOD(release : (nonnull NSNumber *)key) {\n    @synchronized(self) {\n        AVAudioPlayer *player = [self playerForKey:key];\n        if (player) {\n            [player stop];\n            [[self callbackPool] removeObjectForKey:key];\n            [[self playerPool] removeObjectForKey:key];\n            NSNotificationCenter *notificationCenter =\n                [NSNotificationCenter defaultCenter];\n            [notificationCenter removeObserver:self];\n        }\n    }\n}\n\nRCT_EXPORT_METHOD(setVolume\n                  : (nonnull NSNumber *)key withValue\n                  : (nonnull NSNumber *)value) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        player.volume = [value floatValue];\n    }\n}\n\nRCT_EXPORT_METHOD(getSystemVolume : (RCTResponseSenderBlock)callback) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    callback(@[ @(session.outputVolume) ]);\n}\n\nRCT_EXPORT_METHOD(setPan\n                  : (nonnull NSNumber *)key withValue\n                  : (nonnull NSNumber *)value) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        player.pan = [value floatValue];\n    }\n}\n\nRCT_EXPORT_METHOD(setNumberOfLoops\n                  : (nonnull NSNumber *)key withValue\n                  : (nonnull NSNumber *)value) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        player.numberOfLoops = [value intValue];\n    }\n}\n\nRCT_EXPORT_METHOD(setSpeed\n                  : (nonnull NSNumber *)key withValue\n                  : (nonnull NSNumber *)value) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        player.rate = [value floatValue];\n    }\n}\n\nRCT_EXPORT_METHOD(setCurrentTime\n                  : (nonnull NSNumber *)key withValue\n                  : (nonnull NSNumber *)value) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        player.currentTime = [value doubleValue];\n    }\n}\n//added to check if the audio is played\nRCT_EXPORT_METHOD(checkOtherAudioPlaying:\n                 (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock) __unused reject) {\nbool otherAudioPlaying = [[AVAudioSession sharedInstance] secondaryAudioShouldBeSilencedHint];\nbool otherAudioPlaying1 = [[AVAudioSession sharedInstance] isOtherAudioPlaying];\nNSLog(@\"%d\", otherAudioPlaying);\nNSLog(@\"%d\", otherAudioPlaying1);\n  resolve( @(otherAudioPlaying));\n}\n\nRCT_EXPORT_METHOD(getCurrentTime\n                  : (nonnull NSNumber *)key withCallback\n                  : (RCTResponseSenderBlock)callback) {\n    AVAudioPlayer *player = [self playerForKey:key];\n    if (player) {\n        callback([NSArray\n            arrayWithObjects:[NSNumber numberWithDouble:player.currentTime],\n                             [NSNumber numberWithBool:player.isPlaying], nil]);\n    } else {\n        callback([NSArray arrayWithObjects:[NSNumber numberWithInteger:-1],\n                                           [NSNumber numberWithBool:NO], nil]);\n    }\n}\n\nRCT_EXPORT_METHOD(setSpeakerPhone : (BOOL)on) {\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    if (on) {\n        [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker\n                                   error:nil];\n    } else {\n        [session overrideOutputAudioPort:AVAudioSessionPortOverrideNone\n                                   error:nil];\n    }\n    [session setActive:true error:nil];\n}\n\n+ (BOOL)requiresMainQueueSetup {\n    return YES;\n}\n- (void)setOnPlay:(BOOL)isPlaying forPlayerKey:(nonnull NSNumber *)playerKey {\n    [self\n        sendEventWithName:@\"onPlayChange\"\n                     body:[NSDictionary\n                              dictionaryWithObjectsAndKeys:\n                                  [NSNumber\n                                      numberWithBool:isPlaying ? YES : NO],\n                                  @\"isPlaying\", playerKey, @\"playerKey\", nil]];\n}\n@end\n\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometchatUiKit.h",
    "content": "#import <React/RCTBridgeModule.h>\n\n@interface CometchatUiKit : NSObject <RCTBridgeModule>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometchatUiKit.mm",
    "content": "// #import \"CometchatUiKit.h\"\n\n// #ifdef RCT_NEW_ARCH_ENABLED\n// #import \"RNCometchatUiKitSpec.h\"\n// #endif\n\n// @implementation CometchatUiKit\n// RCT_EXPORT_MODULE()\n\n// // Example method\n// // See // https://reactnative.dev/docs/native-modules-ios\n// RCT_REMAP_METHOD(multiply,\n//                  multiplyWithA:(double)a withB:(double)b\n//                  withResolver:(RCTPromiseResolveBlock)resolve\n//                  withRejecter:(RCTPromiseRejectBlock)reject)\n// {\n//   NSNumber *result = @(a * b);\n\n//   resolve(result);\n// }\n\n// // Don't compile this code when we build for the old architecture.\n// #ifdef RCT_NEW_ARCH_ENABLED\n// - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:\n//     (const facebook::react::ObjCTurboModule::InitParams &)params\n// {\n//     return std::make_shared<facebook::react::NativeCometchatUiKitSpecJSI>(params);\n// }\n// #endif\n\n// @end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometchatUiKit.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tC70F1B152923D3480019C96E /* VideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C70F1B142923D3480019C96E /* VideoManager.m */; };\n\t\tC7458E34290A29E2001072BB /* CometchatUiKit.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* CometchatUiKit.m */; };\n\t\tC7458E36290A29F5001072BB /* SoundPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C7458E35290A29F5001072BB /* SoundPlayer.m */; };\n\t\tC7458E3A290A2A0A001072BB /* RNSound.m in Sources */ = {isa = PBXBuildFile; fileRef = C7458E38290A2A0A001072BB /* RNSound.m */; };\n\t\tC7A4099B29112EE00093E4EF /* FileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C7A4099A29112EE00093E4EF /* FileManager.m */; };\n\t\tC7CF34D0291BAB1000E1B855 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7CF34CF291BAB1000E1B855 /* QuickLook.framework */; };\n\t\tC7EC92C72A39A2DA00C99C30 /* WebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C7EC92C32A39A2D900C99C30 /* WebViewManager.m */; };\n\t\tC7EC92CB2A39E8F400C99C30 /* CCWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = C7EC92CA2A39E8F400C99C30 /* CCWebView.m */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t58B511D91A9E6C8500147676 /* CopyFiles */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"include/$(PRODUCT_NAME)\";\n\t\t\tdstSubfolderSpec = 16;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t134814201AA4EA6300B7C361 /* libCometchatUiKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCometchatUiKit.a; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB3E7B5881CC2AC0600A0062D /* CometchatUiKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CometchatUiKit.h; sourceTree = \"<group>\"; };\n\t\tB3E7B5891CC2AC0600A0062D /* CometchatUiKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CometchatUiKit.m; sourceTree = \"<group>\"; };\n\t\tC70F1B142923D3480019C96E /* VideoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VideoManager.m; sourceTree = \"<group>\"; };\n\t\tC70F1B162923D36E0019C96E /* VideoManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VideoManager.h; sourceTree = \"<group>\"; };\n\t\tC7458E35290A29F5001072BB /* SoundPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundPlayer.m; sourceTree = \"<group>\"; };\n\t\tC7458E37290A2A0A001072BB /* RNSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSound.h; sourceTree = \"<group>\"; };\n\t\tC7458E38290A2A0A001072BB /* RNSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSound.m; sourceTree = \"<group>\"; };\n\t\tC7458E39290A2A0A001072BB /* SoundPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundPlayer.h; sourceTree = \"<group>\"; };\n\t\tC7A4099929112EC40093E4EF /* FileManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileManager.h; sourceTree = \"<group>\"; };\n\t\tC7A4099A29112EE00093E4EF /* FileManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileManager.m; sourceTree = \"<group>\"; };\n\t\tC7CF34CF291BAB1000E1B855 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; };\n\t\tC7EC92C22A39A2D900C99C30 /* WebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewManager.h; sourceTree = \"<group>\"; };\n\t\tC7EC92C32A39A2D900C99C30 /* WebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewManager.m; sourceTree = \"<group>\"; };\n\t\tC7EC92C92A39E8DD00C99C30 /* CCWebView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CCWebView.h; sourceTree = \"<group>\"; };\n\t\tC7EC92CA2A39E8F400C99C30 /* CCWebView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CCWebView.m; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t58B511D81A9E6C8500147676 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tC7CF34D0291BAB1000E1B855 /* QuickLook.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t134814211AA4EA7D00B7C361 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t134814201AA4EA6300B7C361 /* libCometchatUiKit.a */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t58B511D21A9E6C8500147676 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tC7EC92CA2A39E8F400C99C30 /* CCWebView.m */,\n\t\t\t\tC7EC92C92A39E8DD00C99C30 /* CCWebView.h */,\n\t\t\t\tC7EC92C22A39A2D900C99C30 /* WebViewManager.h */,\n\t\t\t\tC7EC92C32A39A2D900C99C30 /* WebViewManager.m */,\n\t\t\t\tC70F1B162923D36E0019C96E /* VideoManager.h */,\n\t\t\t\tC70F1B142923D3480019C96E /* VideoManager.m */,\n\t\t\t\tC7A4099A29112EE00093E4EF /* FileManager.m */,\n\t\t\t\tC7A4099929112EC40093E4EF /* FileManager.h */,\n\t\t\t\tC7458E37290A2A0A001072BB /* RNSound.h */,\n\t\t\t\tC7458E38290A2A0A001072BB /* RNSound.m */,\n\t\t\t\tC7458E39290A2A0A001072BB /* SoundPlayer.h */,\n\t\t\t\tB3E7B5881CC2AC0600A0062D /* CometchatUiKit.h */,\n\t\t\t\tB3E7B5891CC2AC0600A0062D /* CometchatUiKit.m */,\n\t\t\t\tC7458E35290A29F5001072BB /* SoundPlayer.m */,\n\t\t\t\t134814211AA4EA7D00B7C361 /* Products */,\n\t\t\t\tC7CF34CE291BAB1000E1B855 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tC7CF34CE291BAB1000E1B855 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tC7CF34CF291BAB1000E1B855 /* QuickLook.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t58B511DA1A9E6C8500147676 /* CometchatUiKit */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget \"CometchatUiKit\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t58B511D71A9E6C8500147676 /* Sources */,\n\t\t\t\t58B511D81A9E6C8500147676 /* Frameworks */,\n\t\t\t\t58B511D91A9E6C8500147676 /* CopyFiles */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = CometchatUiKit;\n\t\t\tproductName = RCTDataManager;\n\t\t\tproductReference = 134814201AA4EA6300B7C361 /* libCometchatUiKit.a */;\n\t\t\tproductType = \"com.apple.product-type.library.static\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t58B511D31A9E6C8500147676 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 0920;\n\t\t\t\tORGANIZATIONNAME = Facebook;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t58B511DA1A9E6C8500147676 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.1.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject \"CometchatUiKit\" */;\n\t\t\tcompatibilityVersion = \"Xcode 3.2\";\n\t\t\tdevelopmentRegion = English;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\tEnglish,\n\t\t\t\ten,\n\t\t\t);\n\t\t\tmainGroup = 58B511D21A9E6C8500147676;\n\t\t\tproductRefGroup = 58B511D21A9E6C8500147676;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t58B511DA1A9E6C8500147676 /* CometchatUiKit */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t58B511D71A9E6C8500147676 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tC7458E36290A29F5001072BB /* SoundPlayer.m in Sources */,\n\t\t\t\tC7EC92CB2A39E8F400C99C30 /* CCWebView.m in Sources */,\n\t\t\t\tC7458E34290A29E2001072BB /* CometchatUiKit.m in Sources */,\n\t\t\t\tC7458E3A290A2A0A001072BB /* RNSound.m in Sources */,\n\t\t\t\tC7EC92C72A39A2DA00C99C30 /* WebViewManager.m in Sources */,\n\t\t\t\tC7A4099B29112EE00093E4EF /* FileManager.m in Sources */,\n\t\t\t\tC70F1B152923D3480019C96E /* VideoManager.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t58B511ED1A9E6C8500147676 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=*]\" = arm64;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t58B511EE1A9E6C8500147676 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=*]\" = arm64;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 8.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t58B511F01A9E6C8500147676 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,\n\t\t\t\t\t\"$(SRCROOT)/../../../React/**\",\n\t\t\t\t\t\"$(SRCROOT)/../../react-native/React/**\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_NAME = CometchatUiKit;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t58B511F11A9E6C8500147676 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,\n\t\t\t\t\t\"$(SRCROOT)/../../../React/**\",\n\t\t\t\t\t\"$(SRCROOT)/../../react-native/React/**\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = \"$(inherited)\";\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_NAME = CometchatUiKit;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t58B511D61A9E6C8500147676 /* Build configuration list for PBXProject \"CometchatUiKit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t58B511ED1A9E6C8500147676 /* Debug */,\n\t\t\t\t58B511EE1A9E6C8500147676 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget \"CometchatUiKit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t58B511F01A9E6C8500147676 /* Debug */,\n\t\t\t\t58B511F11A9E6C8500147676 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 58B511D31A9E6C8500147676 /* Project object */;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometchatUiKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CometchatUiKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "packages/ChatUiKit/ios/CommonUtil.h",
    "content": "#import <React/RCTBridgeModule.h>\n\n@interface CometChatCommonUtil : NSObject <RCTBridgeModule>\n@end"
  },
  {
    "path": "packages/ChatUiKit/ios/CommonUtil.m",
    "content": "#import \"CommonUtil.h\"\n\n@implementation CometChatCommonUtil\n\nRCT_EXPORT_MODULE(CommonUtil);\n\nRCT_EXPORT_METHOD(getSafeAreaInsets:(RCTPromiseResolveBlock)resolve\n                  rejecter:(RCTPromiseRejectBlock)reject)\n{\n  dispatch_async(dispatch_get_main_queue(), ^{\n    if (@available(iOS 11.0, *)) {\n      UIWindow *window = [[UIApplication sharedApplication] keyWindow];\n      UIEdgeInsets safeInsets = window.safeAreaInsets;\n      resolve(@{\n        @\"top\" : @(safeInsets.top),\n        @\"bottom\" : @(safeInsets.bottom),\n      });\n    } else {\n      resolve(@{\n        @\"top\" : @0,\n        @\"bottom\" : @0,\n      });\n    }\n  });\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/FileManager.h",
    "content": "#import <React/RCTBridgeModule.h>\n#import <React/RCTEventEmitter.h>\n#import <QuickLook/QuickLook.h>\n\n@interface CometChatFileManager : RCTEventEmitter <RCTBridgeModule, QLPreviewControllerDataSource, QLPreviewControllerDelegate>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/FileManager.m",
    "content": "#import <Foundation/Foundation.h>\n#import <React/RCTBridgeModule.h>\n#import \"FileManager.h\"\n#import <QuickLook/QuickLook.h>\n#import <React/RCTUtils.h>\n#import <React/RCTConvert.h>\n#import <MobileCoreServices/MobileCoreServices.h>\n#import <AVFoundation/AVFoundation.h>\n\nstatic NSString *const E_DOCUMENT_PICKER_CANCELED = @\"DOCUMENT_PICKER_CANCELED\";\nstatic NSString *const E_INVALID_DATA_RETURNED = @\"INVALID_DATA_RETURNED\";\n\nstatic NSString *const OPTION_TYPE = @\"type\";\nstatic NSString *const OPTION_MULTIPLE = @\"allowMultiSelection\";\n\nstatic NSString *const FIELD_URI = @\"uri\";\nstatic NSString *const FIELD_FILE_COPY_URI = @\"fileCopyUri\";\nstatic NSString *const FIELD_COPY_ERR = @\"copyError\";\nstatic NSString *const FIELD_NAME = @\"name\";\nstatic NSString *const FIELD_TYPE = @\"type\";\nstatic NSString *const FIELD_SIZE = @\"size\";\n\n\n@interface CometChatFileManager () <UIDocumentPickerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, AVAudioRecorderDelegate, AVAudioPlayerDelegate, UIDocumentInteractionControllerDelegate>\n\n@property (nonatomic, strong) AVAudioSession *recordingSession;\n@property (nonatomic, strong) AVAudioRecorder *audioRecorder;\n@property (nonatomic, strong) NSURL *audioFilename;\n@property (nonatomic, assign) CMTime lastPlaybackTime;\n@property (nonatomic, strong) NSDateFormatter *dateFormatter;\n@property (nonatomic, strong) NSTimer *timer;\n@property (nonatomic, strong) AVAudioPlayer *player;\n@property (nonatomic, strong) UIDocumentInteractionController *documentController;\n\n// Segment-based recording properties\n@property (nonatomic, strong) NSMutableArray<NSURL *> *segmentPaths;\n@property (nonatomic, assign) NSInteger currentSegmentIndex;\n@property (nonatomic, strong) AVQueuePlayer *queuePlayer;\n@property (nonatomic, strong) id playbackObserver;\n\n@end\n\n@implementation CometChatFileManager {\n    NSString *_url;\n    RCTResponseSenderBlock callback;\n    bool hasListeners;\n}\n\n- (void)startObserving {\n    hasListeners = YES;\n}\n\n- (void)stopObserving {\n    hasListeners = NO;\n}\n\n- (NSArray<NSString *> *)supportedEvents {\n    return @[@\"opening\", @\"downloading\", @\"status\", @\"downloadComplete\", @\"audioAmplitude\"];\n}\n\nRCT_EXPORT_MODULE(FileManager)\n\nRCT_EXPORT_METHOD(checkAndDownload:(NSString *) urlToDownload name:(NSString *) name callback:(RCTResponseSenderBlock) callback) {\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        NSURL  *url = [NSURL URLWithString:urlToDownload];\n        NSData *urlData = [NSData dataWithContentsOfURL:url];\n        if (urlData)\n        {\n            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n            NSString *documentsDirectory = [paths objectAtIndex:0];\n            \n            NSString *filePath = [NSString stringWithFormat:@\"file://%@/%@\", documentsDirectory,name];\n            NSURL *destinationFileURL = [NSURL URLWithString:filePath];\n            \n            NSURL *url = [NSURL URLWithString:urlToDownload];\n            [[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {\n                if (error == nil) {\n                    NSLog(@\"down moving %@ to %@\", location, destinationFileURL);\n                    [[NSFileManager defaultManager] moveItemAtURL:location toURL:destinationFileURL error:nil];\n                    NSLog(@\"downloaded to %@\", filePath);\n                    self->_url = filePath;\n                    NSString *response = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"filePath\\\":%d}\", filePath];\n                    callback(@[response]);\n                    return;\n                }\n                callback(@[@\"\"]);\n            }] resume];\n        }\n    });\n}\n\n- (void) openMedia:(NSString *) path {\n    dispatch_async(dispatch_get_main_queue(), ^{\n        QLPreviewController* previewCtrl = [[QLPreviewController alloc] init];\n        [previewCtrl setDataSource: self];\n        [[previewCtrl navigationController] setTitle:@\"\"];\n        [previewCtrl setModalPresentationStyle:UIModalPresentationPopover];\n        UIViewController *presentedViewController = RCTPresentedViewController();\n        [presentedViewController presentViewController:previewCtrl animated:YES completion:nil];\n    });\n}\n\nRCT_EXPORT_METHOD(doesFileExist:(NSString *)fileName callback:(RCTResponseSenderBlock)callback) {\n    // Get the document directory path\n    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n    NSString *documentsDirectory = [paths objectAtIndex:0];\n    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];\n    \n    // Check if the file exists\n    BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath];\n    \n    // Call the callback with the result\n    if (exists) {\n        callback(@[@\"{\\\"exists\\\": true}\"]);\n    } else {\n        callback(@[@\"{\\\"exists\\\": false}\"]);\n    }\n}\n\n\nRCT_EXPORT_METHOD(openFile:(NSString *) url name:(NSString *) fileName myCallback:(RCTResponseSenderBlock)callback) {\n    //new way\n    @try {\n        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n        NSString *documentsDirectory = [paths objectAtIndex:0];\n        NSString *filepath = [NSString stringWithFormat:@\"%@/%@\", documentsDirectory, fileName];\n        if ([[NSFileManager defaultManager] fileExistsAtPath: filepath isDirectory:false]) {\n            self->_url = [NSString stringWithFormat:@\"file://%@\",filepath];\n            if (hasListeners)\n                [self sendEventWithName:@\"status\" body:@{@\"url\": url, @\"state\": @\"opening\"}];\n            [self openMedia:self->_url];\n            callback(@[@\"{\\\"success\\\": true}\"]);\n        } else {\n            if (hasListeners)\n                [self sendEventWithName:@\"status\" body:@{@\"url\": url, @\"state\": @\"downloading\"}];\n            [self checkAndDownload:url name:fileName callback:^(NSArray *response) {\n                self->_url = [NSString stringWithFormat:@\"%@\",response[0]];\n                [self openMedia:self->_url];\n                if (self->hasListeners)\n                    [self sendEventWithName:@\"status\" body:@{@\"url\": url, @\"state\": @\"opening\"}];\n                callback(@[@\"{\\\"success\\\": true}\"]);\n            }];\n        }\n    } @catch (NSException *exception) {\n        NSLog(@\"down got exception %@\", [exception description]);\n        callback(@[@\"{\\\"error\\\": true}\"]);\n    }\n}\n\nRCT_EXPORT_METHOD(openFileWithOption:(NSString *)fileName callback:(RCTResponseSenderBlock)callback) {\n    @try {\n        // Get the Downloads directory path\n        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n        NSString *documentsDirectory = [paths objectAtIndex:0];\n        NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];\n\n        // Check if the file exists\n        if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {\n            callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"File not found\\\"}\"]);\n            return;\n        }\n\n        // Get the file URL\n        NSURL *fileURL = [NSURL fileURLWithPath:filePath];\n        \n        // Get the MIME type\n        NSString *mimeType = [self getMimeTypeFromPath:filePath];\n        RCTLogInfo(@\"openFileWithOption: MIMETYPE %@ %@\", mimeType, fileName);\n\n        dispatch_async(dispatch_get_main_queue(), ^{\n            // Initialize and present UIDocumentInteractionController\n            self.documentController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];\n            self.documentController.delegate = self;\n            self.documentController.UTI = mimeType;\n            \n            UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;\n            \n            BOOL presented = [self.documentController presentOptionsMenuFromRect:CGRectZero inView:rootViewController.view animated:YES];\n\n            if (presented) {\n                callback(@[@\"{\\\"success\\\": true}\"]);\n            } else {\n                callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No app available\\\"}\"]);\n            }\n        });\n\n    } @catch (NSException *exception) {\n        callback(@[[NSString stringWithFormat:@\"{\\\"success\\\": false, \\\"error\\\": \\\"%@\\\"}\", exception.reason]]);\n    }\n}\n\n// Function to get MIME type\n- (NSString *)getMimeTypeFromPath:(NSString *)filePath {\n    NSString *fileExtension = [filePath pathExtension];\n    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);\n    CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);\n    CFRelease(UTI);\n    \n    if (!mimeType) {\n        return @\"public.data\"; // Default type if unknown\n    }\n    \n    return (__bridge_transfer NSString *)mimeType;\n}\n\n- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller {\n    return 1;\n}\n\n- (id <QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index {\n    return [NSURL URLWithString:_url];\n}\n\n- (BOOL)previewController:(QLPreviewController *)controller shouldOpenURL:(NSURL *)url forPreviewItem:(id <QLPreviewItem>)item {\n    return YES;\n}\n\nRCT_EXPORT_METHOD(requestResourcesPermission:(NSArray *)resourceTypes resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)\n{\n    __block NSUInteger permissionsToCheckCount = resourceTypes.count;\n    NSMutableDictionary *permDict = [NSMutableDictionary new];\n    for (NSString *resourceType in resourceTypes) {\n        NSString *mediaType;\n        if ([resourceType isEqualToString:@\"camera\"]) {\n            mediaType = AVMediaTypeVideo;\n        } else if ([resourceType isEqualToString:@\"mic\"]) {\n            mediaType = AVMediaTypeAudio;\n        } else {\n            reject(@\"INVALID_RESOURCE_TYPE\", @\"Invalid resource type requested\", nil);\n            return;\n        }\n        [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {\n            NSNumber *authStatusObj = granted ? @(AVAuthorizationStatusAuthorized) : @(AVAuthorizationStatusDenied);\n            permDict[resourceType] = authStatusObj;\n            \n            permissionsToCheckCount--;\n            if (permissionsToCheckCount == 0) {\n                resolve(permDict);\n            }\n        }];\n    }\n}\n\nRCT_EXPORT_METHOD(checkResourcesPermission:(NSArray *)resourceTypes resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)\n{\n    NSMutableDictionary *permDict = [NSMutableDictionary new];\n    \n    for (NSString *resourceType in resourceTypes) {\n        NSString *mediaType = AVMediaTypeVideo;\n        if ([resourceType isEqualToString:@\"camera\"]) {\n            mediaType = AVMediaTypeVideo;\n        } else if ([resourceType isEqualToString:@\"mic\"]) {\n            mediaType = AVMediaTypeAudio;\n        } else {\n            reject(@\"INVALID_RESOURCE_TYPE\", @\"Invalid resource type requested\", nil);\n            return;\n        }\n        \n        AVAuthorizationStatus authStatusCamera = [AVCaptureDevice authorizationStatusForMediaType:mediaType];\n\n        NSNumber *authStatusCameraObj = @(authStatusCamera);\n        \n        permDict[resourceType] = authStatusCameraObj;\n    }\n    \n    resolve(permDict);\n}\n\nRCT_EXPORT_METHOD(openCamera: (NSString *) type callback:(RCTResponseSenderBlock) call) {\n    callback = call;\n    UIViewController *presentedViewController = RCTPresentedViewController();\n    dispatch_async(dispatch_get_main_queue(), ^{\n        if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) {\n            UIImagePickerController *imageController = [[UIImagePickerController alloc] init];\n            imageController.delegate = self;\n            imageController.title = @\"Select an image\";\n            imageController.sourceType = UIImagePickerControllerSourceTypeCamera;\n            [presentedViewController presentViewController:imageController animated:YES completion:nil];\n        } else {\n            NSMutableString *myMutableString = [NSMutableString stringWithString:@\"Camera not available\"];\n            if (TARGET_IPHONE_SIMULATOR) {\n                [myMutableString setString:@\"Camera not available in simulator\"];\n            }\n            UIAlertController* alertController = [UIAlertController alertControllerWithTitle:@\"Error\"\n                                            message:myMutableString\n                                            preferredStyle:UIAlertControllerStyleAlert];\n\n            UIAlertAction* okAction = [UIAlertAction actionWithTitle:@\"OK\" style:UIAlertActionStyleDefault\n            handler:^(UIAlertAction * action) {\n                // Action when OK button is pressed\n            }];\n\n            [alertController addAction:okAction];\n\n            [presentedViewController presentViewController:alertController animated:YES completion:nil];\n        }\n    });\n}\n\nRCT_EXPORT_METHOD(openFileChooser:(NSString *)type callback:(RCTResponseSenderBlock)call) {\n    callback = call;\n    UIViewController *presentedViewController = RCTPresentedViewController();\n\n    dispatch_async(dispatch_get_main_queue(), ^{\n        if ([@\"image\" isEqualToString:type]) {\n            UIImagePickerController *imageController = [[UIImagePickerController alloc] init];\n            imageController.delegate = self;\n            imageController.title = @\"Select an image\";\n            imageController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;\n            [presentedViewController presentViewController:imageController animated:YES completion:nil];\n        } else {\n            NSArray *documentTypes;\n\n            if ([@\"audio\" isEqualToString:type]) {\n                documentTypes = @[@\"public.audio\"];\n            } else if ([@\"video\" isEqualToString:type]) {\n                documentTypes = @[@\"public.movie\", @\"public.video\"];\n            } else if ([@\"text\" isEqualToString:type]) {\n                documentTypes = @[@\"public.text\"];\n            } else if ([@\"zip\" isEqualToString:type]) {\n                documentTypes = @[@\"com.pkware.zip-archive\"];\n            } else {\n                // Fallback to general types (if needed)\n                documentTypes = @[@\"public.data\"];\n            }\n\n            UIDocumentPickerViewController *documentController =\n                [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes\n                                                                       inMode:UIDocumentPickerModeImport];\n            documentController.delegate = self;\n            documentController.title = @\"Select a file\";\n            [presentedViewController presentViewController:documentController animated:YES completion:nil];\n        }\n    });\n}\n\n\nRCT_EXPORT_METHOD(shareMessage: (NSDictionary *) shareObj myCallback:(RCTResponseSenderBlock)callback) {\n    NSString *message = shareObj[@\"message\"];\n    NSString *type = shareObj[@\"type\"];\n    NSString *mediaName = shareObj[@\"mediaName\"];\n    NSString *fileUrl = shareObj[@\"fileUrl\"];\n    NSString *mimeType = shareObj[@\"mimeType\"];\n    \n    if ([type isEqualToString:@\"text\"]) {\n        [self shareMedia:message];\n        callback(@[@\"{\\\"success\\\": true}\"]);\n    } else {\n        if (fileUrl) {\n            NSURL *url = [NSURL URLWithString:fileUrl];\n            if (url) {\n                [self downloadMediaMessage:url completion:^(NSURL *fileLocation) {\n                    if (fileLocation) {\n                        [self shareMedia:fileLocation];\n                        callback(@[@\"{\\\"success\\\": true}\"]);\n                    }\n                }];\n            } else {\n                NSLog(@\"Url is empty\");\n                callback(@[@\"{\\\"success\\\": false}\"]);\n            }\n        }\n    }\n}\n\n- (void)downloadMediaMessage:(NSURL *)url completion:(void (^)(NSURL *fileLocation))completion {\n    NSURL *documentsDirectoryURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];\n    NSURL *destinationUrl = [documentsDirectoryURL URLByAppendingPathComponent:[url lastPathComponent]];\n    \n    if ([[NSFileManager defaultManager] fileExistsAtPath:[destinationUrl path]]) {\n        completion(destinationUrl);\n    } else {\n        NSURLSessionDownloadTask *downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {\n            if (location && !error) {\n                NSError *moveError = nil;\n                [[NSFileManager defaultManager] moveItemAtURL:location toURL:destinationUrl error:&moveError];\n                if (!moveError) {\n                    completion(destinationUrl);\n                } else {\n                    completion(nil);\n                }\n            } else {\n                completion(nil);\n            }\n        }];\n        [downloadTask resume];\n    }\n}\n\n- (void)shareMedia:(id)item {\n    UIViewController *controller = RCTPresentedViewController();\n    if (controller) {\n        UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[item] applicationActivities:nil];\n        activityViewController.popoverPresentationController.sourceView = controller.view;\n        activityViewController.excludedActivityTypes = @[UIActivityTypeAirDrop];\n        \n        dispatch_async(dispatch_get_main_queue(), ^{\n            [controller presentViewController:activityViewController animated:YES completion:nil];\n        });\n    }\n}\n\n\nRCT_EXPORT_METHOD(startRecording:(RCTResponseSenderBlock)callback) {\n    self.recordingSession = [AVAudioSession sharedInstance];\n    NSError *error = nil;\n    if ([self.recordingSession setCategory:AVAudioSessionCategoryPlayAndRecord\n                               withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker\n                                     error:&error] &&\n        [self.recordingSession setActive:YES error:&error]) {\n        [self.recordingSession requestRecordPermission:^(BOOL granted) {\n            dispatch_async(dispatch_get_main_queue(), ^{\n                if (granted) {\n                    //                    [self setupRecorderWithResult]; //setupRecorderWithResult\n                    [self setupRecorderWithResult:callback];\n                    //                    callback(@[@\"{\\\"success\\\": true}\"]);\n                } else {\n                    callback(@[@\"{\\\"granted\\\": false}\"]);\n                    // Failed to record\n                }\n            });\n        }];\n    } else {\n        // Failed to record\n    }\n}\n\nRCT_EXPORT_METHOD(pauseRecording:(RCTPromiseResolveBlock)resolve\n                  rejecter:(RCTPromiseRejectBlock)reject) {\n    if (self.audioRecorder.isRecording) {\n        [self.audioRecorder pause];\n        [self stopAmplitudeTimer];\n        resolve(@\"success\");\n    } else {\n        resolve(@\"error\");\n    }\n}\n\nRCT_EXPORT_METHOD(resumeRecording:(RCTPromiseResolveBlock)resolve\n                  rejecter:(RCTPromiseRejectBlock)reject) {\n    if (![self.audioRecorder isRecording]) {\n        [self.audioRecorder record];\n        [self startAmplitudeTimer];\n        resolve(@\"success\");\n    } else {\n        resolve(@\"error\");\n    }\n}\n\n- (void)setupRecorderWithResult:(RCTResponseSenderBlock)callback {\n    self.audioFilename = [self getFileURL];\n    NSDictionary *settings = @{\n        AVFormatIDKey : [NSNumber numberWithInt:kAudioFormatMPEG4AAC],\n        AVSampleRateKey : [NSNumber numberWithFloat:12000.0],\n        AVNumberOfChannelsKey : [NSNumber numberWithInt:1],\n        AVEncoderAudioQualityKey : [NSNumber numberWithInt:AVAudioQualityHigh]\n    };\n    NSError *error = nil;\n    self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:self.audioFilename settings:settings error:&error];\n    if (error) {\n        [self stopRecordingWithSuccess:NO];\n    } else {\n        self.audioRecorder.delegate = self;\n        self.audioRecorder.meteringEnabled = YES;\n        [self.audioRecorder record];\n        \n        // Start amplitude timer for real-time waveform\n        [self startAmplitudeTimer];\n        \n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\"}\", [self.audioFilename path]];\n        NSLog(@\"FILENAME: %@\", jsonString);\n        callback(@[jsonString]);\n    }\n}\n\n- (void)startAmplitudeTimer {\n    [self stopAmplitudeTimer];\n    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {\n        if (self.audioRecorder && self.audioRecorder.isRecording && self->hasListeners) {\n            [self.audioRecorder updateMeters];\n            float decibels = [self.audioRecorder averagePowerForChannel:0];\n            // Convert decibels to linear scale (0.0 to 1.0)\n            // Decibels range from -160 (silence) to 0 (max)\n            // We normalize to 0-1 range\n            float linear = pow(10, decibels / 20.0);\n            linear = MIN(1.0, MAX(0.0, linear));\n            [self sendEventWithName:@\"audioAmplitude\" body:@{@\"amplitude\": @(linear)}];\n        }\n    }];\n}\n\n- (void)stopAmplitudeTimer {\n    if (self.timer) {\n        [self.timer invalidate];\n        self.timer = nil;\n    }\n}\n\nRCT_EXPORT_METHOD(stopRecordingAudio:(RCTResponseSenderBlock)callback) {\n    if (self.audioRecorder != nil) {\n        NSString *path = [self stopRecordingWithSuccess:YES];\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\"}\", path];\n        NSLog(@\"FILENAME: %@\", jsonString);\n        callback(@[jsonString]);\n//        result(path);\n    }\n}\n\n- (NSString *)stopRecordingWithSuccess:(BOOL)success {\n    [self stopAmplitudeTimer];\n    if (success) {\n        [self.audioRecorder stop];\n        self.audioRecorder = nil;\n        NSError *error = nil;\n        [self.recordingSession setActive:NO error:&error];\n        if (error) {\n            NSLog(@\"Error stopping recording: %@\", [error localizedDescription]);\n        }\n        return [self.audioFilename path];\n    } else {\n        return nil;\n    }\n}\n\n- (NSURL *)getDocumentsDirectory {\n    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n    return [NSURL fileURLWithPath:paths[0]];\n}\n\n- (NSURL *)getFileURL {\n    self.dateFormatter = [[NSDateFormatter alloc] init];\n    self.dateFormatter.dateFormat = @\"yyyyMMddHHmmss\";\n    NSURL *path = [[self getDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@\"audio-recording-%@.m4a\", [self.dateFormatter stringFromDate:[NSDate date]]]];\n    return path;\n}\n\n- (void)preparePlayer {\n    NSError *error = nil;\n    self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:self.audioFilename error:&error];\n    if (error) {\n        self.player = nil;\n        NSLog(@\"AVAudioPlayer error: %@\", [error localizedDescription]);\n    } else {\n        self.player.delegate = self;\n        [self.player prepareToPlay];\n        self.player.volume = 10.0;\n    }\n}\n\nRCT_EXPORT_METHOD(playAudio:(RCTResponseSenderBlock)callback) {\n    [self preparePlayer];\n    [self.player play];\n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\"}\", [self.audioFilename path]];\n    NSLog(@\"FILENAME: %@\", jsonString);\n    callback(@[jsonString]);\n}\n\nRCT_EXPORT_METHOD(pausePlaying:(RCTResponseSenderBlock)callback) {\n    [self.player pause];\n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\"}\", [self.audioFilename path]];\n    NSLog(@\"FILENAME: %@\", jsonString);\n    callback(@[jsonString]);\n}\n\nRCT_EXPORT_METHOD(resumePlaying:(RCTResponseSenderBlock)callback) {\n    [self.player play];\n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\"}\", [self.audioFilename path]];\n    NSLog(@\"FILENAME: %@\", jsonString);\n    callback(@[jsonString]);\n}\n\n/**\n * Get current playback position in milliseconds.\n * Used for accurate waveform sync during playback.\n */\nRCT_EXPORT_METHOD(getPlaybackPosition:(RCTResponseSenderBlock)callback) {\n    if (self.player != nil) {\n        NSTimeInterval currentTime = self.player.currentTime;\n        NSTimeInterval duration = self.player.duration;\n        int positionMs = (int)(currentTime * 1000);\n        int durationMs = (int)(duration * 1000);\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"position\\\": %d, \\\"duration\\\": %d}\", positionMs, durationMs];\n        callback(@[jsonString]);\n    } else if (self.queuePlayer != nil) {\n        // For queue player (segment playback)\n        CMTime currentTime = self.queuePlayer.currentTime;\n        int positionMs = (int)(CMTimeGetSeconds(currentTime) * 1000);\n        \n        // Get total duration from all items in queue\n        int totalDurationMs = 0;\n        for (AVPlayerItem *item in self.queuePlayer.items) {\n            CMTime itemDuration = item.asset.duration;\n            if (CMTIME_IS_VALID(itemDuration) && !CMTIME_IS_INDEFINITE(itemDuration)) {\n                totalDurationMs += (int)(CMTimeGetSeconds(itemDuration) * 1000);\n            }\n        }\n        \n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"position\\\": %d, \\\"duration\\\": %d}\", positionMs, totalDurationMs];\n        callback(@[jsonString]);\n    } else {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio player active\\\"}\"]);\n    }\n}\n\n/**\n * Seek to a specific position in the audio playback.\n * @param positionMs Position in milliseconds\n */\nRCT_EXPORT_METHOD(seekTo:(int)positionMs callback:(RCTResponseSenderBlock)callback) {\n    if (self.player != nil) {\n        NSTimeInterval positionSeconds = positionMs / 1000.0;\n        self.player.currentTime = positionSeconds;\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"position\\\": %d}\", positionMs];\n        callback(@[jsonString]);\n    } else {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio player active\\\"}\"]);\n    }\n}\n\n/**\n * Start playback from a specific position.\n * @param positionMs Position in milliseconds to start from\n */\nRCT_EXPORT_METHOD(playFromPosition:(int)positionMs callback:(RCTResponseSenderBlock)callback) {\n    if (self.audioFilename == nil) {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No audio file to play\\\"}\"]);\n        return;\n    }\n    \n    [self preparePlayer];\n    \n    if (self.player != nil) {\n        NSTimeInterval positionSeconds = positionMs / 1000.0;\n        self.player.currentTime = positionSeconds;\n        [self.player play];\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"position\\\": %d}\", positionMs];\n        callback(@[jsonString]);\n    } else {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"Failed to create audio player\\\"}\"]);\n    }\n}\n\n- (void)stopPlaying {\n    [self.player stop];\n    self.player = nil;\n}\n\nRCT_EXPORT_METHOD(releaseMediaResources:(RCTResponseSenderBlock)callback) {\n    if (self.audioRecorder != nil) {\n        NSTimeInterval duration = self.audioRecorder.currentTime;\n        [self stopRecordingWithSuccess:YES];\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\", \\\"duration\\\": %f}\", [self.audioFilename path], duration];\n        NSLog(@\"FILENAME: %@\", jsonString);\n        callback(@[jsonString]);\n        return;\n    }\n\n    if (self.player != nil) {\n        [self stopPlaying];\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\"}\", [self.audioFilename path]];\n        NSLog(@\"FILENAME: %@\", jsonString);\n        callback(@[jsonString]);\n        return;\n    }\n    \n    callback(@[@\"{\\\"success\\\": true}\"]);\n}\n\nRCT_EXPORT_METHOD(deleteFile:(RCTResponseSenderBlock)callback) {\n    NSString *filePath = [self.audioFilename path];\n    \n    if (filePath == nil) {\n        callback(@[@\"{\\\"success\\\": false}\"]);\n    } else {\n        NSFileManager *fileManager = [NSFileManager defaultManager];\n        NSError *error;\n        if ([fileManager removeItemAtPath:filePath error:&error]) {\n            callback(@[@\"{\\\"success\\\": true}\"]);\n        } else {\n            NSLog(@\"Error deleting file: %@\", error);\n            callback(@[@\"{\\\"success\\\": false}\"]);\n        }\n    }\n}\n\n#pragma mark - Segment-Based Recording Methods\n\n/**\n * Finalize the current recording segment without stopping the recorder.\n * This allows the user to preview the recorded audio while paused.\n * @validates Requirements 11.1\n */\nRCT_EXPORT_METHOD(finalizeSegment:(RCTResponseSenderBlock)callback) {\n    if (self.audioRecorder == nil || !self.audioRecorder.isRecording) {\n        // If not recording, check if we have a paused recording\n        if (self.audioRecorder != nil) {\n            NSTimeInterval duration = self.audioRecorder.currentTime;\n            [self.audioRecorder stop];\n            \n            // Initialize segments array if needed\n            if (self.segmentPaths == nil) {\n                self.segmentPaths = [NSMutableArray array];\n            }\n            \n            // Add current file to segments\n            if (self.audioFilename != nil) {\n                [self.segmentPaths addObject:self.audioFilename];\n            }\n            \n            NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"segmentPath\\\": \\\"%@\\\", \\\"duration\\\": %f, \\\"segmentIndex\\\": %lu}\", \n                [self.audioFilename path], \n                duration,\n                (unsigned long)(self.segmentPaths.count - 1)];\n            callback(@[jsonString]);\n            return;\n        }\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No active recording\\\"}\"]);\n        return;\n    }\n    \n    [self stopAmplitudeTimer];\n    NSTimeInterval duration = self.audioRecorder.currentTime;\n    [self.audioRecorder stop];\n    \n    // Initialize segments array if needed\n    if (self.segmentPaths == nil) {\n        self.segmentPaths = [NSMutableArray array];\n    }\n    \n    // Add current file to segments\n    [self.segmentPaths addObject:self.audioFilename];\n    \n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"segmentPath\\\": \\\"%@\\\", \\\"duration\\\": %f, \\\"segmentIndex\\\": %lu}\", \n        [self.audioFilename path], \n        duration,\n        (unsigned long)(self.segmentPaths.count - 1)];\n    callback(@[jsonString]);\n}\n\n/**\n * Start recording a new segment after pausing.\n * This creates a new audio file for the next segment.\n * @validates Requirements 11.2\n */\nRCT_EXPORT_METHOD(startNewSegment:(RCTResponseSenderBlock)callback) {\n    self.recordingSession = [AVAudioSession sharedInstance];\n    NSError *error = nil;\n    if ([self.recordingSession setCategory:AVAudioSessionCategoryPlayAndRecord\n                               withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker\n                                     error:&error] &&\n        [self.recordingSession setActive:YES error:&error]) {\n        \n        // Create a new file for this segment\n        self.audioFilename = [self getFileURL];\n        \n        NSDictionary *settings = @{\n            AVFormatIDKey : [NSNumber numberWithInt:kAudioFormatMPEG4AAC],\n            AVSampleRateKey : [NSNumber numberWithFloat:12000.0],\n            AVNumberOfChannelsKey : [NSNumber numberWithInt:1],\n            AVEncoderAudioQualityKey : [NSNumber numberWithInt:AVAudioQualityHigh]\n        };\n        \n        NSError *recorderError = nil;\n        self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:self.audioFilename settings:settings error:&recorderError];\n        \n        if (recorderError) {\n            callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"Failed to create recorder\\\"}\"]);\n            return;\n        }\n        \n        self.audioRecorder.delegate = self;\n        self.audioRecorder.meteringEnabled = YES;\n        [self.audioRecorder record];\n        \n        // Start amplitude timer for real-time waveform\n        [self startAmplitudeTimer];\n        \n        // Initialize segments array if needed\n        if (self.segmentPaths == nil) {\n            self.segmentPaths = [NSMutableArray array];\n        }\n        \n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"file\\\": \\\"%@\\\", \\\"segmentIndex\\\": %lu}\", \n            [self.audioFilename path],\n            (unsigned long)self.segmentPaths.count];\n        callback(@[jsonString]);\n    } else {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"Failed to setup audio session\\\"}\"]);\n    }\n}\n\n/**\n * Merge all recorded segments into a single audio file.\n * Uses AVMutableComposition for seamless audio concatenation.\n * @validates Requirements 11.4\n */\nRCT_EXPORT_METHOD(mergeSegments:(NSArray<NSString *> *)segmentPaths callback:(RCTResponseSenderBlock)callback) {\n    if (segmentPaths == nil || segmentPaths.count == 0) {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No segments to merge\\\"}\"]);\n        return;\n    }\n    \n    // If only one segment, just return it\n    if (segmentPaths.count == 1) {\n        NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"mergedPath\\\": \\\"%@\\\", \\\"totalDuration\\\": 0}\", segmentPaths[0]];\n        callback(@[jsonString]);\n        return;\n    }\n    \n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        AVMutableComposition *composition = [AVMutableComposition composition];\n        AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];\n        \n        CMTime currentTime = kCMTimeZero;\n        Float64 totalDuration = 0;\n        \n        for (NSString *segmentPath in segmentPaths) {\n            NSURL *segmentURL = [NSURL fileURLWithPath:segmentPath];\n            AVURLAsset *asset = [AVURLAsset assetWithURL:segmentURL];\n            \n            NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeAudio];\n            if (tracks.count == 0) {\n                NSLog(@\"No audio track found in segment: %@\", segmentPath);\n                continue;\n            }\n            \n            AVAssetTrack *assetTrack = tracks[0];\n            CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);\n            \n            NSError *error = nil;\n            [audioTrack insertTimeRange:timeRange ofTrack:assetTrack atTime:currentTime error:&error];\n            \n            if (error) {\n                NSLog(@\"Error inserting track: %@\", error);\n                continue;\n            }\n            \n            currentTime = CMTimeAdd(currentTime, asset.duration);\n            totalDuration += CMTimeGetSeconds(asset.duration);\n        }\n        \n        // Create output file\n        NSURL *outputURL = [self getMergedFileURL];\n        \n        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];\n        exportSession.outputURL = outputURL;\n        exportSession.outputFileType = AVFileTypeAppleM4A;\n        \n        [exportSession exportAsynchronouslyWithCompletionHandler:^{\n            dispatch_async(dispatch_get_main_queue(), ^{\n                if (exportSession.status == AVAssetExportSessionStatusCompleted) {\n                    // Update audioFilename to point to merged file\n                    self.audioFilename = outputURL;\n                    \n                    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"mergedPath\\\": \\\"%@\\\", \\\"totalDuration\\\": %f}\", \n                        [outputURL path], \n                        totalDuration];\n                    callback(@[jsonString]);\n                } else {\n                    NSString *errorMsg = exportSession.error ? [exportSession.error localizedDescription] : @\"Unknown error\";\n                    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": false, \\\"error\\\": \\\"%@\\\"}\", errorMsg];\n                    callback(@[jsonString]);\n                }\n            });\n        }];\n    });\n}\n\n/**\n * Play multiple segments sequentially.\n * Uses AVQueuePlayer for seamless playback.\n * @validates Requirements 11.4\n */\nRCT_EXPORT_METHOD(playSegments:(NSArray<NSString *> *)segmentPaths callback:(RCTResponseSenderBlock)callback) {\n    if (segmentPaths == nil || segmentPaths.count == 0) {\n        callback(@[@\"{\\\"success\\\": false, \\\"error\\\": \\\"No segments to play\\\"}\"]);\n        return;\n    }\n    \n    // Stop any existing playback\n    if (self.queuePlayer) {\n        [self.queuePlayer pause];\n        if (self.playbackObserver) {\n            [self.queuePlayer removeTimeObserver:self.playbackObserver];\n            self.playbackObserver = nil;\n        }\n        self.queuePlayer = nil;\n    }\n    \n    // Create player items for each segment\n    NSMutableArray<AVPlayerItem *> *playerItems = [NSMutableArray array];\n    for (NSString *segmentPath in segmentPaths) {\n        NSURL *segmentURL = [NSURL fileURLWithPath:segmentPath];\n        AVPlayerItem *item = [AVPlayerItem playerItemWithURL:segmentURL];\n        [playerItems addObject:item];\n    }\n    \n    // Create queue player\n    self.queuePlayer = [AVQueuePlayer queuePlayerWithItems:playerItems];\n    self.currentSegmentIndex = 0;\n    \n    // Add observer for playback completion\n    __weak typeof(self) weakSelf = self;\n    [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification \n                                                      object:nil \n                                                       queue:[NSOperationQueue mainQueue] \n                                                  usingBlock:^(NSNotification *note) {\n        __strong typeof(weakSelf) strongSelf = weakSelf;\n        if (!strongSelf) return;\n        if (strongSelf.queuePlayer.currentItem == note.object) {\n            strongSelf.currentSegmentIndex++;\n            if (strongSelf.currentSegmentIndex >= segmentPaths.count) {\n                // All segments played\n                if (strongSelf->hasListeners) {\n                    [strongSelf sendEventWithName:@\"status\" body:@{@\"state\": @\"playbackComplete\"}];\n                }\n            }\n        }\n    }];\n    \n    [self.queuePlayer play];\n    \n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"segmentCount\\\": %lu}\", (unsigned long)segmentPaths.count];\n    callback(@[jsonString]);\n}\n\n/**\n * Delete all segment files.\n * @validates Requirements 11.5\n */\nRCT_EXPORT_METHOD(deleteSegments:(NSArray<NSString *> *)segmentPaths callback:(RCTResponseSenderBlock)callback) {\n    if (segmentPaths == nil || segmentPaths.count == 0) {\n        callback(@[@\"{\\\"success\\\": true, \\\"deletedCount\\\": 0}\"]);\n        return;\n    }\n    \n    NSFileManager *fileManager = [NSFileManager defaultManager];\n    NSInteger deletedCount = 0;\n    \n    for (NSString *segmentPath in segmentPaths) {\n        NSError *error = nil;\n        if ([fileManager removeItemAtPath:segmentPath error:&error]) {\n            deletedCount++;\n        } else {\n            NSLog(@\"Error deleting segment: %@, error: %@\", segmentPath, error);\n        }\n    }\n    \n    // Clear internal segments array\n    [self.segmentPaths removeAllObjects];\n    \n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"deletedCount\\\": %ld}\", (long)deletedCount];\n    callback(@[jsonString]);\n}\n\n/**\n * Get all current segment paths.\n */\nRCT_EXPORT_METHOD(getSegmentPaths:(RCTResponseSenderBlock)callback) {\n    if (self.segmentPaths == nil || self.segmentPaths.count == 0) {\n        callback(@[@\"{\\\"success\\\": true, \\\"segments\\\": []}\"]);\n        return;\n    }\n    \n    NSMutableArray<NSString *> *paths = [NSMutableArray array];\n    for (NSURL *url in self.segmentPaths) {\n        [paths addObject:[url path]];\n    }\n    \n    NSError *error = nil;\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:paths options:0 error:&error];\n    NSString *pathsJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n    \n    NSString *jsonString = [NSString stringWithFormat:@\"{\\\"success\\\": true, \\\"segments\\\": %@}\", pathsJson];\n    callback(@[jsonString]);\n}\n\n/**\n * Clear all segments and reset state.\n */\nRCT_EXPORT_METHOD(clearSegments:(RCTResponseSenderBlock)callback) {\n    [self.segmentPaths removeAllObjects];\n    self.currentSegmentIndex = 0;\n    \n    if (self.queuePlayer) {\n        [self.queuePlayer pause];\n        if (self.playbackObserver) {\n            [self.queuePlayer removeTimeObserver:self.playbackObserver];\n            self.playbackObserver = nil;\n        }\n        self.queuePlayer = nil;\n    }\n    \n    callback(@[@\"{\\\"success\\\": true}\"]);\n}\n\n- (NSURL *)getMergedFileURL {\n    self.dateFormatter = [[NSDateFormatter alloc] init];\n    self.dateFormatter.dateFormat = @\"yyyyMMddHHmmss\";\n    NSURL *path = [[self getDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@\"audio-merged-%@.m4a\", [self.dateFormatter stringFromDate:[NSDate date]]]];\n    return path;\n}\n\n- (NSURL *) saveImage:(NSString *) imageName image:(UIImage *) image {\n    NSFileManager *defaultManager = [NSFileManager defaultManager];\n    NSURL *documentUrl = [defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;\n    if (!documentUrl) {\n        return [NSURL alloc];\n    }\n    NSURL *fileURL = [documentUrl URLByAppendingPathComponent:imageName];\n\n    if ([defaultManager fileExistsAtPath:fileURL.path]) {\n        [defaultManager removeItemAtPath:fileURL.path error:nil];\n    }\n    [UIImageJPEGRepresentation(image, 1.0) writeToURL:fileURL atomically:TRUE];\n    return fileURL;\n}\n\n- (void)imagePickerController:(UIImagePickerController *)picker\ndidFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info {\n    switch (picker.sourceType) {\n        case UIImagePickerControllerSourceTypeCamera: {\n            UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];\n            if (image) {\n                NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];\n                NSString *subPart = [NSString stringWithFormat:@\"%f\", interval];\n                NSString *filename = [NSString stringWithFormat:@\"Image_%@.png\", [subPart stringByReplacingOccurrencesOfString:@\".\" withString:@\"\"]];\n                NSURL *new_image_url = [self saveImage: filename image:image];\n                NSString *type = [self getMimeType:new_image_url];\n                NSString *name = [new_image_url lastPathComponent];\n                NSString *uri = [NSString stringWithFormat:@\"%@\", new_image_url];\n                callback(@[@{@\"name\": name, @\"type\": type, @\"uri\": uri}]);\n            } else {\n                NSLog(@\"failed to get image\");\n                callback(@[@{@\"error\": @\"unable to get image\"}]);\n            }\n            break;\n        }\n        case UIImagePickerControllerSourceTypePhotoLibrary: {\n            NSLog(@\"image from photo library\");\n            NSURL *url = (NSURL *) [info valueForKey:UIImagePickerControllerImageURL];\n            if (url) {\n                NSLog(@\"image path in url format : %@\", url);\n                NSString *name = [url lastPathComponent];\n                NSString *extention = [url pathExtension];\n                NSString *imageUrl = [[NSString alloc] initWithFormat:@\"%@\", url];\n                NSString *videoUrl = [[NSString alloc] initWithFormat:@\"%@\", [info valueForKey:UIImagePickerControllerMediaURL]];\n                NSString *fileurl;\n                if (imageUrl)\n                    fileurl = imageUrl;\n                if (videoUrl)\n                    fileurl = videoUrl;\n//                NSString *type = [[NSString alloc] initWithFormat:@\"%@\", [info valueForKey:UIImagePickerControllerMediaType]];\n                NSString *type = [self getMimeType:url];\n                callback(@[@{@\"name\": name, @\"uri\": fileurl, @\"type\": type}]);\n            } else {\n                callback(@[@{@\"error\": @\"invalid\"}]);\n            }\n            break;\n        }\n        case UIImagePickerControllerSourceTypeSavedPhotosAlbum: {\n            NSLog(@\"image from saved photo library\");\n            NSString *imageUrl = [[NSString alloc] initWithFormat:@\"%@\", [info valueForKey:UIImagePickerControllerImageURL]];\n            NSString *videoUrl = [[NSString alloc] initWithFormat:@\"%@\", [info valueForKey:UIImagePickerControllerMediaURL]];\n            NSString *type = [[NSString alloc] initWithFormat:@\"%@\", [info valueForKey:UIImagePickerControllerMediaType]];\n            NSURL *imgUrl = [[NSURL alloc] initWithString:imageUrl];\n            NSString *name = [imgUrl lastPathComponent];\n            callback(@[@{@\"name\": name, @\"uri\": imageUrl, @\"type\":type}]);\n            break;\n        }\n    }\n    [picker dismissViewControllerAnimated:TRUE completion:nil];\n}\n\n- (NSString *) getMimeType:(NSURL *) url {\n    CFStringRef extension = (__bridge CFStringRef) url.pathExtension;\n    CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(UTTagClassFilenameExtension, extension, NULL);\n    CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(uti, UTTagClassMIMEType);\n    if (uti) {\n        CFRelease(uti);\n    }\n    NSString *mimeTypeString = (__bridge_transfer NSString *)mimeType;\n    return mimeTypeString;\n}\n\n- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {\n    NSLog(@\"image selection cancelled\");\n    [picker dismissViewControllerAnimated:TRUE completion:nil];\n}\n\n- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {\n    NSLog(@\"document selected %@\", urls[0]);\n    NSString *uri = [NSString stringWithFormat:@\"%@\", urls[0]];\n    NSString *type = [self getMimeType:urls[0]];\n    NSString *name = [urls[0] lastPathComponent];\n    if (!name) name = @\"Unknown\";\n    if (!type) type = @\"Unknown\";\n    callback(@[@{@\"name\": name, @\"type\": type, @\"uri\": uri}]);\n}\n\n- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {\n    NSLog(@\"document pick cancelled\");\n    [controller dismissViewControllerAnimated:TRUE completion:nil];\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Proximity.h",
    "content": "#import <Foundation/Foundation.h>\n#import <React/RCTBridgeModule.h>\n\n@interface Proximity : NSObject <RCTBridgeModule>\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Proximity.m",
    "content": "#import \"Proximity.h\"\n#import <UIKit/UIKit.h>\n\n@implementation Proximity\n\n// Expose this module to React Native\nRCT_EXPORT_MODULE();\n\n// Ensure methods run on main queue (we interact with UIKit)\n- (dispatch_queue_t)methodQueue {\n  return dispatch_get_main_queue();\n}\n\n/**\n * Enables / disables the proximity sensor monitoring. On iOS enabling the\n * proximity sensor automatically dims the screen and disables touch controls.\n *\n * @param enabled `YES` to enable proximity (sensor) monitoring; `NO`, otherwise.\n */\nRCT_EXPORT_METHOD(setEnabled:(BOOL)enabled)\n{\n  [[UIDevice currentDevice] setProximityMonitoringEnabled:enabled];\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/RichTextEditorView.swift",
    "content": "import UIKit\nimport React\n\nclass FloatingToolbar: UIView, UIScrollViewDelegate {\n    weak var editorView: RichTextEditorView?\n\n    private var buttons: [UIButton] = []\n    private var scrollView: UIScrollView!\n    private var stackView: UIStackView!\n    private var leftArrow: UILabel!\n    private var rightArrow: UILabel!\n    private var enabledOptions: [String] = [\n        \"bold\", \"italic\", \"underline\", \"strikethrough\", \"code\", \"highlight\",\n        \"heading\", \"bullet\", \"numbered\", \"quote\", \"checklist\",\n        \"link\", \"undo\", \"redo\", \"clearFormatting\",\n        \"indent\", \"outdent\",\n        \"alignLeft\", \"alignCenter\", \"alignRight\"\n    ]\n\n    private let optionToIndex: [String: Int] = [\n        \"bold\": 0, \"italic\": 1, \"strikethrough\": 2, \"underline\": 3, \"code\": 4, \"highlight\": 5,\n        \"heading\": 6, \"bullet\": 7, \"numbered\": 8, \"quote\": 9, \"checklist\": 10,\n        \"link\": 11, \"undo\": 12, \"redo\": 13, \"clearFormatting\": 14,\n        \"indent\": 15, \"outdent\": 16,\n        \"alignLeft\": 17, \"alignCenter\": 18, \"alignRight\": 19\n    ]\n\n    private let toolbarBackgroundColor = UIColor(red: 45/255, green: 45/255, blue: 45/255, alpha: 1.0)\n    private let activeColor = UIColor(red: 80/255, green: 130/255, blue: 200/255, alpha: 1.0)\n    private let inactiveColor = UIColor.white\n    private let arrowColor = UIColor(white: 1.0, alpha: 0.7)\n\n    override init(frame: CGRect) {\n        super.init(frame: frame)\n        setupToolbar()\n    }\n\n    required init?(coder: NSCoder) {\n        super.init(coder: coder)\n        setupToolbar()\n    }\n\n    func setToolbarOptions(_ options: [String]?) {\n        if let options = options, !options.isEmpty {\n            enabledOptions = options\n        } else {\n            enabledOptions = [\n                \"bold\", \"italic\", \"underline\", \"strikethrough\", \"code\", \"highlight\",\n                \"heading\", \"bullet\", \"numbered\", \"quote\", \"checklist\",\n                \"link\", \"undo\", \"redo\", \"clearFormatting\",\n                \"indent\", \"outdent\",\n                \"alignLeft\", \"alignCenter\", \"alignRight\"\n            ]\n        }\n        rebuildButtons()\n        updateScrollIndicators()\n    }\n\n    private func setupToolbar() {\n        backgroundColor = toolbarBackgroundColor\n        layer.cornerRadius = 10\n        layer.shadowColor = UIColor.black.cgColor\n        layer.shadowOffset = CGSize(width: 0, height: 2)\n        layer.shadowOpacity = 0.3\n        layer.shadowRadius = 6\n\n        leftArrow = UILabel()\n        leftArrow.text = \"‹\"\n        leftArrow.font = UIFont.systemFont(ofSize: 20, weight: .bold)\n        leftArrow.textColor = arrowColor\n        leftArrow.textAlignment = .center\n        leftArrow.translatesAutoresizingMaskIntoConstraints = false\n        leftArrow.isHidden = true\n        leftArrow.isUserInteractionEnabled = true\n        let leftTap = UITapGestureRecognizer(target: self, action: #selector(leftArrowTapped))\n        leftArrow.addGestureRecognizer(leftTap)\n        addSubview(leftArrow)\n\n        rightArrow = UILabel()\n        rightArrow.text = \"›\"\n        rightArrow.font = UIFont.systemFont(ofSize: 20, weight: .bold)\n        rightArrow.textColor = arrowColor\n        rightArrow.textAlignment = .center\n        rightArrow.translatesAutoresizingMaskIntoConstraints = false\n        rightArrow.isHidden = false\n        rightArrow.isUserInteractionEnabled = true\n        let rightTap = UITapGestureRecognizer(target: self, action: #selector(rightArrowTapped))\n        rightArrow.addGestureRecognizer(rightTap)\n        addSubview(rightArrow)\n\n        scrollView = UIScrollView()\n        scrollView.showsHorizontalScrollIndicator = false\n        scrollView.showsVerticalScrollIndicator = false\n        scrollView.translatesAutoresizingMaskIntoConstraints = false\n        scrollView.delegate = self\n        addSubview(scrollView)\n\n        stackView = UIStackView()\n        stackView.axis = .horizontal\n        stackView.spacing = 8\n        stackView.distribution = .fill\n        stackView.translatesAutoresizingMaskIntoConstraints = false\n        scrollView.addSubview(stackView)\n\n        NSLayoutConstraint.activate([\n            leftArrow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4),\n            leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor),\n            leftArrow.widthAnchor.constraint(equalToConstant: 16),\n\n            rightArrow.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4),\n            rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor),\n            rightArrow.widthAnchor.constraint(equalToConstant: 16),\n\n            scrollView.leadingAnchor.constraint(equalTo: leftArrow.trailingAnchor, constant: 2),\n            scrollView.trailingAnchor.constraint(equalTo: rightArrow.leadingAnchor, constant: -2),\n            scrollView.topAnchor.constraint(equalTo: topAnchor, constant: 8),\n            scrollView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),\n\n            stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),\n            stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),\n            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),\n            stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),\n            stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)\n        ])\n\n        rebuildButtons()\n    }\n\n    func scrollViewDidScroll(_ scrollView: UIScrollView) {\n        updateScrollIndicators()\n    }\n\n    private func updateScrollIndicators() {\n        guard let scrollView = scrollView else { return }\n\n        let contentWidth = scrollView.contentSize.width\n        let scrollViewWidth = scrollView.bounds.width\n        let offsetX = scrollView.contentOffset.x\n\n        leftArrow.isHidden = offsetX <= 5\n        rightArrow.isHidden = offsetX >= (contentWidth - scrollViewWidth - 5)\n    }\n\n    @objc private func leftArrowTapped() {\n        guard let scrollView = scrollView else { return }\n        let scrollAmount: CGFloat = 120\n        let newOffsetX = max(0, scrollView.contentOffset.x - scrollAmount)\n        scrollView.setContentOffset(CGPoint(x: newOffsetX, y: 0), animated: true)\n    }\n\n    @objc private func rightArrowTapped() {\n        guard let scrollView = scrollView else { return }\n        let scrollAmount: CGFloat = 120\n        let maxOffsetX = scrollView.contentSize.width - scrollView.bounds.width\n        let newOffsetX = min(maxOffsetX, scrollView.contentOffset.x + scrollAmount)\n        scrollView.setContentOffset(CGPoint(x: newOffsetX, y: 0), animated: true)\n    }\n\n    override func layoutSubviews() {\n        super.layoutSubviews()\n        DispatchQueue.main.async {\n            self.updateScrollIndicators()\n        }\n    }\n\n    private let iconSize: CGFloat = 20\n    private let buttonSize: CGFloat = 36\n    private let iconPadding: CGFloat = 8\n\n    private func rebuildButtons() {\n        buttons.forEach { $0.removeFromSuperview() }\n        buttons.removeAll()\n\n        for option in enabledOptions {\n            guard let index = optionToIndex[option] else { continue }\n\n            let button = UIButton(type: .custom)\n            button.tag = index\n            button.layer.cornerRadius = 6\n            button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)\n            button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true\n            button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true\n\n            // Use vector icons with consistent sizing\n            if let icon = ToolbarIcons.getIcon(for: option, color: inactiveColor, size: CGSize(width: iconSize, height: iconSize)) {\n                button.setImage(icon.withRenderingMode(.alwaysOriginal), for: .normal)\n                button.imageView?.contentMode = .scaleAspectFit\n                button.contentEdgeInsets = UIEdgeInsets(top: iconPadding, left: iconPadding, bottom: iconPadding, right: iconPadding)\n            }\n\n            buttons.append(button)\n            stackView.addArrangedSubview(button)\n        }\n    }\n\n    func getToolbarWidth() -> CGFloat {\n        let screenWidth = UIScreen.main.bounds.width\n        let maxWidth = screenWidth * 0.9\n        let buttonCount = CGFloat(enabledOptions.count)\n        let calculatedWidth = (buttonCount * 36) + ((buttonCount - 1) * 8) + 48\n        return min(calculatedWidth, maxWidth)\n    }\n\n    private func createButtonAttributedString(for index: Int, active: Bool) -> NSAttributedString {\n        let color = active ? activeColor : inactiveColor\n        let fontSize: CGFloat = 18\n\n        switch index {\n        case 0:\n            return NSAttributedString(string: \"B\", attributes: [\n                .font: UIFont.boldSystemFont(ofSize: fontSize),\n                .foregroundColor: color\n            ])\n        case 1:\n            return NSAttributedString(string: \"I\", attributes: [\n                .font: UIFont.italicSystemFont(ofSize: fontSize),\n                .foregroundColor: color\n            ])\n        case 2:\n            return NSAttributedString(string: \"S\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize, weight: .medium),\n                .foregroundColor: color,\n                .strikethroughStyle: NSUnderlineStyle.single.rawValue,\n                .strikethroughColor: color\n            ])\n        case 3:\n            return NSAttributedString(string: \"U\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize, weight: .medium),\n                .foregroundColor: color,\n                .underlineStyle: NSUnderlineStyle.single.rawValue,\n                .underlineColor: color\n            ])\n        case 4:\n            return NSAttributedString(string: \"</>\", attributes: [\n                .font: UIFont(name: \"Menlo\", size: fontSize - 4) ?? UIFont.monospacedSystemFont(ofSize: fontSize - 4, weight: .medium),\n                .foregroundColor: color\n            ])\n        case 5:\n            return NSAttributedString(string: \"H\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize, weight: .medium),\n                .foregroundColor: color,\n                .backgroundColor: active ? UIColor.yellow.withAlphaComponent(0.5) : UIColor.yellow.withAlphaComponent(0.3)\n            ])\n        case 6:\n            return NSAttributedString(string: \"H1\", attributes: [\n                .font: UIFont.boldSystemFont(ofSize: fontSize - 2),\n                .foregroundColor: color\n            ])\n        case 7:\n            return createListIcon(type: .bullet, color: color)\n        case 8:\n            return createListIcon(type: .numbered, color: color)\n        case 9:\n            return NSAttributedString(string: \"❞\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize + 2),\n                .foregroundColor: color\n            ])\n        case 10:\n            return NSAttributedString(string: \"☑\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize),\n                .foregroundColor: color\n            ])\n        case 11:\n            return NSAttributedString(string: \"🔗\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize - 2),\n                .foregroundColor: color\n            ])\n        case 12:\n            return NSAttributedString(string: \"↩\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize),\n                .foregroundColor: color\n            ])\n        case 13:\n            return NSAttributedString(string: \"↪\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize),\n                .foregroundColor: color\n            ])\n        case 14:\n            return NSAttributedString(string: \"Tx\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize - 2, weight: .medium),\n                .foregroundColor: color,\n                .strikethroughStyle: NSUnderlineStyle.single.rawValue,\n                .strikethroughColor: color\n            ])\n        case 15:\n            return NSAttributedString(string: \"→⊢\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize - 4),\n                .foregroundColor: color\n            ])\n        case 16:\n            return NSAttributedString(string: \"⊣←\", attributes: [\n                .font: UIFont.systemFont(ofSize: fontSize - 4),\n                .foregroundColor: color\n            ])\n        case 17:\n            return createAlignmentIcon(alignment: .left, color: color)\n        case 18:\n            return createAlignmentIcon(alignment: .center, color: color)\n        case 19:\n            return createAlignmentIcon(alignment: .right, color: color)\n        default:\n            return NSAttributedString(string: \"\")\n        }\n    }\n\n    private func createAlignmentIcon(alignment: NSTextAlignment, color: UIColor) -> NSAttributedString {\n        let result = NSMutableAttributedString()\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineSpacing = 1\n        paragraphStyle.alignment = alignment\n        paragraphStyle.lineHeightMultiple = 0.9\n\n        let attrs: [NSAttributedString.Key: Any] = [\n            .font: UIFont.systemFont(ofSize: 8, weight: .medium),\n            .foregroundColor: color,\n            .paragraphStyle: paragraphStyle\n        ]\n\n        let lines = [\"────\", \"──────\", \"────\"]\n        for (i, line) in lines.enumerated() {\n            result.append(NSAttributedString(string: line, attributes: attrs))\n            if i < lines.count - 1 {\n                result.append(NSAttributedString(string: \"\\n\", attributes: attrs))\n            }\n        }\n\n        return result\n    }\n\n    private enum ListIconType {\n        case bullet\n        case numbered\n    }\n\n    private func createListIcon(type: ListIconType, color: UIColor) -> NSAttributedString {\n        let result = NSMutableAttributedString()\n\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineSpacing = 1\n        paragraphStyle.alignment = .left\n        paragraphStyle.lineHeightMultiple = 0.9\n\n        let attrs: [NSAttributedString.Key: Any] = [\n            .font: UIFont.systemFont(ofSize: 9, weight: .medium),\n            .foregroundColor: color,\n            .paragraphStyle: paragraphStyle\n        ]\n\n        for i in 0..<3 {\n            let marker = type == .bullet ? \"•\" : \"\\(i + 1)\"\n            let line = \"\\(marker) ──\"\n            result.append(NSAttributedString(string: line, attributes: attrs))\n            if i < 2 {\n                result.append(NSAttributedString(string: \"\\n\", attributes: attrs))\n            }\n        }\n\n        return result\n    }\n\n    @objc private func buttonTapped(_ sender: UIButton) {\n        switch sender.tag {\n        case 0: editorView?.toggleBold()\n        case 1: editorView?.toggleItalic()\n        case 2: editorView?.toggleStrikethrough()\n        case 3: editorView?.toggleUnderline()\n        case 4: editorView?.toggleCode()\n        case 5: editorView?.toggleHighlight(color: nil)\n        case 6: editorView?.setHeading()\n        case 7: editorView?.toggleBulletList()\n        case 8: editorView?.toggleNumberedList()\n        case 9: editorView?.setQuote()\n        case 10: editorView?.setChecklist()\n        case 11: editorView?.promptInsertLink()\n        case 12: editorView?.undo()\n        case 13: editorView?.redo()\n        case 14: editorView?.clearFormatting()\n        case 15: editorView?.indent()\n        case 16: editorView?.outdent()\n        case 17: editorView?.setAlignment(.left)\n        case 18: editorView?.setAlignment(.center)\n        case 19: editorView?.setAlignment(.right)\n        default: break\n        }\n        editorView?.updateToolbarButtonStates()\n    }\n\n    private let indexToOption: [Int: String] = [\n        0: \"bold\", 1: \"italic\", 2: \"strikethrough\", 3: \"underline\", 4: \"code\", 5: \"highlight\",\n        6: \"heading\", 7: \"bullet\", 8: \"numbered\", 9: \"quote\", 10: \"checklist\",\n        11: \"link\", 12: \"undo\", 13: \"redo\", 14: \"clearFormatting\",\n        15: \"indent\", 16: \"outdent\",\n        17: \"alignLeft\", 18: \"alignCenter\", 19: \"alignRight\"\n    ]\n\n    func updateButtonStates(bold: Bool, italic: Bool, underline: Bool, strikethrough: Bool, code: Bool = false, highlight: Bool = false, heading: Bool = false, bullet: Bool, numbered: Bool, quote: Bool = false, checklist: Bool = false, alignLeft: Bool = true, alignCenter: Bool = false, alignRight: Bool = false) {\n        let styleStates: [Int: Bool] = [\n            0: bold, 1: italic, 2: strikethrough, 3: underline, 4: code, 5: highlight,\n            6: heading, 7: bullet, 8: numbered, 9: quote, 10: checklist,\n            11: false, 12: false, 13: false, 14: false,\n            15: false, 16: false,\n            17: alignLeft, 18: alignCenter, 19: alignRight\n        ]\n\n        for button in buttons {\n            let tag = button.tag\n            let isActive = styleStates[tag] ?? false\n            let color = isActive ? activeColor : inactiveColor\n\n            // Update icon with new color\n            if let option = indexToOption[tag],\n               let icon = ToolbarIcons.getIcon(for: option, color: color, size: CGSize(width: iconSize, height: iconSize)) {\n                button.setImage(icon.withRenderingMode(.alwaysOriginal), for: .normal)\n            }\n\n            button.backgroundColor = isActive ? UIColor.white.withAlphaComponent(0.15) : .clear\n        }\n    }\n}\n\n/// Custom NSLayoutManager that clips inline code `.backgroundColor` rects\n/// to the actual glyph bounds instead of the full line fragment width.\n/// Custom NSLayoutManager that clips inline code `.backgroundColor` rects\n/// to the actual glyph bounds instead of the full line fragment width.\n/// This keeps the background tight-wrapped to the text while staying in\n/// the text rendering pipeline (so it scrolls correctly with content).\nclass InlineCodeLayoutManager: NSLayoutManager {\n\n    /// Suppress rendering of the blockquote marker character (▎ U+258E).\n    /// The character is kept in the text storage so line metrics and hit-testing\n    /// remain correct, but its glyph is never drawn — only the custom grey bar\n    /// painted in RichTextView.draw(_:) is visible.\n    override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {\n        guard let textStorage = self.textStorage else {\n            super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)\n            return\n        }\n\n        let fullString = textStorage.string as NSString\n        var nonQuoteRanges: [NSRange] = []\n        var current = glyphsToShow.location\n        let end = glyphsToShow.location + glyphsToShow.length\n\n        while current < end {\n            let charIndex = self.characterIndexForGlyph(at: current)\n            if charIndex < fullString.length && fullString.character(at: charIndex) == 0x258E {\n                // Skip this single glyph (▎)\n                current += 1\n            } else {\n                let rangeStart = current\n                current += 1\n                while current < end {\n                    let ci = self.characterIndexForGlyph(at: current)\n                    if ci < fullString.length && fullString.character(at: ci) == 0x258E {\n                        break\n                    }\n                    current += 1\n                }\n                nonQuoteRanges.append(NSRange(location: rangeStart, length: current - rangeStart))\n            }\n        }\n\n        for range in nonQuoteRanges {\n            super.drawGlyphs(forGlyphRange: range, at: origin)\n        }\n    }\n\n    override func fillBackgroundRectArray(_ rectArray: UnsafePointer<CGRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: UIColor) {\n        // Only intercept inline code background color; pass everything else through\n        guard color == inlineCodeBgColor, let textStorage = self.textStorage else {\n            super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color)\n            return\n        }\n\n        // Skip whitespace/newline-only ranges\n        let nsString = textStorage.string as NSString\n        let rangeStr = nsString.substring(with: charRange)\n        if rangeStr.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return }\n\n        let glyphRange = self.glyphRange(forCharacterRange: charRange, actualCharacterRange: nil)\n        guard glyphRange.length > 0 else { return }\n\n        guard let ctx = UIGraphicsGetCurrentContext() else { return }\n        ctx.saveGState()\n        color.setFill()\n\n        let hPad: CGFloat = 2.0\n\n        // Compute the Y offset between enumerateLineFragments coordinates\n        // (text container space) and fillBackgroundRectArray coordinates\n        // (drawing context space, which includes textContainerInset offset).\n        // We derive this offset by comparing the first system rect Y with\n        // the first line fragment Y for the same glyph range.\n        var yOffset: CGFloat = 0.0\n        if rectCount > 0 {\n            var firstLineY: CGFloat = 0.0\n            var found = false\n            enumerateLineFragments(forGlyphRange: NSRange(location: glyphRange.location, length: 1)) { lineRect, usedRect, container, lineGlyphRange, stop in\n                firstLineY = lineRect.origin.y\n                found = true\n                stop.pointee = true\n            }\n            if found {\n                yOffset = rectArray[0].origin.y - firstLineY\n            }\n        }\n\n        // Walk each visual line that intersects the inline code glyph range.\n        // Compute tight X from glyph positions, apply yOffset to align Y\n        // with the drawing coordinate space.\n        enumerateLineFragments(forGlyphRange: glyphRange) { lineRect, usedRect, container, lineGlyphRange, stop in\n            let intersectStart = max(glyphRange.location, lineGlyphRange.location)\n            let intersectEnd = min(glyphRange.location + glyphRange.length, lineGlyphRange.location + lineGlyphRange.length)\n            guard intersectStart < intersectEnd else { return }\n\n            // Skip line segments that are only whitespace/newlines\n            let charStart = self.characterIndexForGlyph(at: intersectStart)\n            let charEnd = self.characterIndexForGlyph(at: intersectEnd - 1) + 1\n            let segLen = min(charEnd, nsString.length) - charStart\n            if segLen > 0 {\n                let segStr = nsString.substring(with: NSRange(location: charStart, length: segLen))\n                if segStr.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return }\n            }\n\n            // Tight horizontal bounds from glyph positions\n            let leftX = self.location(forGlyphAt: intersectStart).x + lineRect.origin.x\n            var rightX: CGFloat\n            if intersectEnd < lineGlyphRange.location + lineGlyphRange.length {\n                rightX = self.location(forGlyphAt: intersectEnd).x + lineRect.origin.x\n            } else {\n                let lastGlyph = intersectEnd - 1\n                let lastGlyphX = self.location(forGlyphAt: lastGlyph).x + lineRect.origin.x\n                let charIndex = self.characterIndexForGlyph(at: lastGlyph)\n                let charFont = textStorage.attribute(.font, at: charIndex, effectiveRange: nil) as? UIFont ?? UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)\n                let charStr = nsString.substring(with: NSRange(location: charIndex, length: 1))\n                let charWidth = (charStr as NSString).size(withAttributes: [.font: charFont]).width\n                rightX = lastGlyphX + charWidth\n            }\n\n            // Apply yOffset to convert lineRect Y from text container space\n            // to the drawing coordinate space used by fillBackgroundRectArray\n            let bgRect = CGRect(\n                x: leftX - hPad,\n                y: lineRect.origin.y + yOffset,\n                width: (rightX - leftX) + hPad * 2,\n                height: lineRect.height\n            )\n            ctx.fill(bgRect)\n        }\n\n        ctx.restoreGState()\n    }\n}\n\nclass RichTextView: UITextView {\n    /// Weak reference to the container RichTextEditorView for paste-URL-over-selection.\n    weak var editorContainer: RichTextEditorView?\n\n    override init(frame: CGRect, textContainer: NSTextContainer?) {\n        super.init(frame: frame, textContainer: textContainer)\n        disableAutofill()\n    }\n\n    required init?(coder: NSCoder) {\n        super.init(coder: coder)\n        disableAutofill()\n    }\n\n    /// Force redraw on scroll so code block containers stay fixed relative to text.\n    /// UITextView uses tiled rendering and only redraws the dirty rect; custom\n    /// drawing in draw(_:) for code block borders can miss regions during scroll.\n    override func layoutSubviews() {\n        super.layoutSubviews()\n        setNeedsDisplay()\n    }\n\n    /// Intercept paste to detect URL-on-selected-text → create styled hyperlink\n    /// (matches Android onTextContextMenuItem paste handling).\n    override func paste(_ sender: Any?) {\n        guard let pastedString = UIPasteboard.general.string else {\n            super.paste(sender)\n            return\n        }\n        let sel = selectedRange\n        // URL on clipboard + text selected → styled hyperlink\n        if sel.length > 0, let editor = editorContainer, editor.isURL(pastedString) {\n            let selectedText = (self.text as NSString).substring(with: sel)\n            editor.insertLinkOverSelection(url: pastedString, displayText: selectedText, range: sel)\n            return\n        }\n        // Markdown content → parse and insert as styled text\n        if let editor = editorContainer, editor.looksLikeMarkdown(pastedString) {\n            let styledText = editor.markdownToAttributedString(pastedString)\n            editor.insertStyledPasteContent(styledText, at: sel)\n            return\n        }\n        super.paste(sender)\n    }\n\n    private func disableAutofill() {\n        autocorrectionType = .no\n        autocapitalizationType = .none\n        spellCheckingType = .no\n        smartQuotesType = .no\n        smartDashesType = .no\n        smartInsertDeleteType = .no\n\n        if #available(iOS 10.0, *) {\n            textContentType = UITextContentType(rawValue: \"\")\n        }\n\n        if #available(iOS 9.0, *) {\n            inputAssistantItem.leadingBarButtonGroups = []\n            inputAssistantItem.trailingBarButtonGroups = []\n        }\n\n        inputAccessoryView = UIView(frame: .zero)\n\n        if #available(iOS 16.0, *) {\n            isFindInteractionEnabled = false\n        }\n\n        isSecureTextEntry = true\n        isSecureTextEntry = false\n    }\n\n    /// Custom drawing for blockquote vertical bars (matches Android onDraw implementation).\n    /// Custom drawing for code block containers and blockquote vertical bars.\n    /// Code block containers are drawn BEHIND text (matching Android onDraw).\n    /// The ▎ character is made invisible and a custom bar is drawn instead.\n    override func draw(_ rect: CGRect) {\n        // Guard: ensure text storage has content before custom drawing\n        let textStore = self.textStorage\n        let layoutMgr = self.layoutManager\n        let textContainer = self.textContainer\n\n        // ── Code block containers (unified rect per region, matching Android) ──\n        if textStore.length > 0 {\n            // Find all code block regions by enumerating CodeBlockAttributeKey ranges\n            var codeRegions: [(Int, Int)] = []\n            textStore.enumerateAttribute(CodeBlockAttributeKey, in: NSRange(location: 0, length: textStore.length), options: []) { value, range, _ in\n                guard value != nil else { return }\n                if !codeRegions.isEmpty && range.location <= codeRegions.last!.1 {\n                    codeRegions[codeRegions.count - 1] = (codeRegions.last!.0, range.location + range.length)\n                } else {\n                    codeRegions.append((range.location, range.location + range.length))\n                }\n            }\n\n            if !codeRegions.isEmpty, let ctx = UIGraphicsGetCurrentContext() {\n                let isDark = self.traitCollection.userInterfaceStyle == .dark\n                let bgColor = isDark ? codeBlockDarkBgColor : codeBlockBgColor\n                let borderColor = isDark ? codeBlockDarkBorderColor : codeBlockBorderColor\n\n                for (regionStart, regionEnd) in codeRegions {\n                    let clampedEnd = max(regionStart, min(regionEnd - 1, textStore.length - 1))\n                    let charRange = NSRange(location: regionStart, length: clampedEnd - regionStart + 1)\n                    let glyphRange = layoutMgr.glyphRange(forCharacterRange: charRange, actualCharacterRange: nil)\n                    guard glyphRange.length > 0 else { continue }\n\n                    let boundingRect = layoutMgr.boundingRect(forGlyphRange: glyphRange, in: textContainer)\n\n                    let vPad = codeBlockVerticalPadding\n                    let codeTop = boundingRect.origin.y + textContainerInset.top\n                    let codeBottom = boundingRect.origin.y + boundingRect.height + textContainerInset.top\n\n                    // Clamp top padding so the container never overlaps the previous line.\n                    // If there's content above, limit padding to half the gap between\n                    // the code block edge and the previous line's bottom (matching Android).\n                    let topPad: CGFloat\n                    if regionStart > 0 {\n                        let prevCharRange = NSRange(location: max(0, regionStart - 1), length: 1)\n                        let prevGlyphRange = layoutMgr.glyphRange(forCharacterRange: prevCharRange, actualCharacterRange: nil)\n                        var prevLineBottom: CGFloat = codeTop\n                        layoutMgr.enumerateLineFragments(forGlyphRange: prevGlyphRange) { lineRect, _, _, _, _ in\n                            prevLineBottom = lineRect.origin.y + lineRect.height + self.textContainerInset.top\n                        }\n                        let gap = codeTop - prevLineBottom\n                        topPad = min(vPad, max(gap / 2.0, 0))\n                    } else {\n                        topPad = vPad\n                    }\n\n                    // Clamp bottom padding so the container never overlaps the next line.\n                    let bottomPad: CGFloat\n                    if regionEnd < textStore.length {\n                        let nextCharRange = NSRange(location: min(regionEnd, textStore.length - 1), length: 1)\n                        let nextGlyphRange = layoutMgr.glyphRange(forCharacterRange: nextCharRange, actualCharacterRange: nil)\n                        var nextLineTop: CGFloat = codeBottom\n                        layoutMgr.enumerateLineFragments(forGlyphRange: nextGlyphRange) { lineRect, _, _, _, _ in\n                            nextLineTop = lineRect.origin.y + self.textContainerInset.top\n                        }\n                        let gap = nextLineTop - codeBottom\n                        bottomPad = min(vPad, max(gap / 2.0, 0))\n                    } else {\n                        bottomPad = vPad\n                    }\n\n                    let topY = codeTop - topPad\n                    let bottomY = codeBottom + bottomPad\n\n                    let leftX = textContainerInset.left\n                    let rightX = self.bounds.width - textContainerInset.right\n\n                    let containerRect = CGRect(x: leftX, y: max(topY, 1), width: rightX - leftX, height: bottomY - max(topY, 1))\n                    let path = UIBezierPath(roundedRect: containerRect, cornerRadius: codeBlockCornerRadius)\n\n                    ctx.saveGState()\n                    bgColor.setFill()\n                    path.fill()\n\n                    borderColor.setStroke()\n                    path.lineWidth = 1.0\n                    path.stroke()\n                    ctx.restoreGState()\n                }\n            }\n        }\n\n        // Inline code backgrounds are rendered via NSAttributedString.backgroundColor\n        // (set in toggleCode/setContent/applyPendingStylesToTypingAttributes)\n        // which handles scrolling and dirty-rect rendering natively.\n        super.draw(rect)\n\n        // Draw blockquote bars on top of text (decorative side bars)\n        guard textStore.length > 0 else { return }\n\n        let plainText = text ?? \"\"\n        guard !plainText.isEmpty else { return }\n\n        let lines = plainText.components(separatedBy: \"\\n\")\n\n        var quoteRegions: [(Int, Int)] = []\n        var offset = 0\n        for line in lines {\n            let lineEnd = offset + line.count\n            if line.hasPrefix(\"▎\") {\n                if !quoteRegions.isEmpty && offset <= quoteRegions.last!.1 + 1 {\n                    quoteRegions[quoteRegions.count - 1] = (quoteRegions.last!.0, lineEnd)\n                } else {\n                    quoteRegions.append((offset, lineEnd))\n                }\n            }\n            offset = lineEnd + 1\n        }\n\n        guard !quoteRegions.isEmpty else { return }\n\n        let barColor = UIColor(red: 220/255.0, green: 220/255.0, blue: 220/255.0, alpha: 1.0)\n        let barWidth: CGFloat = 3.0\n        let barRadius = barWidth / 2.0\n\n        for (regionStart, regionEnd) in quoteRegions {\n            let clampedEnd = min(regionEnd, textStore.length - 1)\n            let safeEnd = max(clampedEnd, regionStart)\n\n            let glyphRange = layoutMgr.glyphRange(forCharacterRange: NSRange(location: regionStart, length: safeEnd - regionStart + 1), actualCharacterRange: nil)\n\n            var firstLineRect = CGRect.zero\n            var lastLineRect = CGRect.zero\n\n            layoutMgr.enumerateLineFragments(forGlyphRange: glyphRange) { (rect, usedRect, container, range, stop) in\n                if firstLineRect == .zero {\n                    firstLineRect = rect\n                }\n                lastLineRect = rect\n            }\n\n            guard firstLineRect != .zero else { continue }\n\n            let topY = firstLineRect.origin.y + textContainerInset.top\n            let bottomY = lastLineRect.origin.y + lastLineRect.height + textContainerInset.top\n            let barX = textContainerInset.left + 1.0\n\n            guard bottomY > topY else { continue }\n\n            let barRect = CGRect(x: barX, y: topY, width: barWidth, height: bottomY - topY)\n            let barPath = UIBezierPath(roundedRect: barRect, cornerRadius: barRadius)\n            barColor.setFill()\n            barPath.fill()\n        }\n    }\n\n    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {\n        // Allow paste so clipboard content can be inserted and placeholder hides correctly\n        if action == #selector(UIResponderStandardEditActions.paste(_:)) {\n            return UIPasteboard.general.hasStrings || UIPasteboard.general.hasImages || UIPasteboard.general.hasURLs\n        }\n        // Allow standard text editing actions (matching Android system context menu)\n        if action == #selector(UIResponderStandardEditActions.cut(_:)) ||\n           action == #selector(UIResponderStandardEditActions.copy(_:)) ||\n           action == #selector(UIResponderStandardEditActions.selectAll(_:)) ||\n           action == #selector(UIResponderStandardEditActions.select(_:)) {\n            return super.canPerformAction(action, withSender: sender)\n        }\n        // Allow custom formatting actions when text is selected and showTextSelectionMenuItems is true\n        if selectedRange.length > 0, editorContainer?.showTextSelectionMenuItems == true {\n            if action == #selector(formatBold) ||\n               action == #selector(formatItalic) ||\n               action == #selector(formatUnderline) ||\n               action == #selector(formatStrikethrough) {\n                return true\n            }\n        }\n        return false\n    }\n\n    /// Custom action: apply bold formatting to selection only (context menu — does not affect typing state)\n    @objc func formatBold() {\n        guard let editor = editorContainer else { return }\n        editor.applyStyleToSelectionOnly(trait: .traitBold)\n    }\n\n    /// Custom action: apply italic formatting to selection only (context menu — does not affect typing state)\n    @objc func formatItalic() {\n        guard let editor = editorContainer else { return }\n        editor.applyStyleToSelectionOnly(trait: .traitItalic)\n    }\n\n    /// Custom action: apply underline formatting to selection only (context menu — does not affect typing state)\n    @objc func formatUnderline() {\n        guard let editor = editorContainer else { return }\n        editor.applyAttributeToSelectionOnly(key: .underlineStyle, value: NSUnderlineStyle.single.rawValue)\n    }\n\n    /// Custom action: apply strikethrough formatting to selection only (context menu — does not affect typing state)\n    @objc func formatStrikethrough() {\n        guard let editor = editorContainer else { return }\n        editor.applyAttributeToSelectionOnly(key: .strikethroughStyle, value: NSUnderlineStyle.single.rawValue)\n    }\n}\n\n// Inline code colors matching Android defaults (#6852D6 text, #F5F5F5 bg, #E8E8E8 border)\nprivate let inlineCodeTextColor = UIColor(red: 104.0/255.0, green: 82.0/255.0, blue: 214.0/255.0, alpha: 1.0)\nprivate let inlineCodeBgColor = UIColor(red: 245.0/255.0, green: 245.0/255.0, blue: 245.0/255.0, alpha: 1.0)\nprivate let inlineCodeBorderColor = UIColor(red: 232.0/255.0, green: 232.0/255.0, blue: 232.0/255.0, alpha: 1.0)\n\n// Custom NSAttributedString key for link URL storage.\n// Using a custom key instead of .link prevents UITextView from opening URLs\n// in the browser on tap. Link taps are handled by our custom gesture recognizer.\nprivate let LinkURLAttributeKey = NSAttributedString.Key(\"cometchat.linkURL\")\n\n// Custom NSAttributedString key for code block marker (equivalent to Android CodeBlockBorderSpan).\n// Presence indicates the range is inside a code block. Drawing is handled in RichTextView.draw().\nprivate let CodeBlockAttributeKey = NSAttributedString.Key(\"cometchat.codeBlock\")\n\n// Custom NSAttributedString key for mention marker (equivalent to Android MentionSpan).\n// Used to selectively clear old mention styling (bold, foreground, background) without\n// affecting inline code or link styling.\nprivate let MentionMarkerKey = NSAttributedString.Key(\"cometchat.mention\")\n\n// Code block visual constants (matching Android CodeBlockBorderSpan companion)\nprivate let codeBlockCornerRadius: CGFloat = 6.0\nprivate let codeBlockPadding: CGFloat = 12.0\nprivate let codeBlockVerticalPadding: CGFloat = 10.0\nprivate let codeBlockBgColor = UIColor(red: 250.0/255.0, green: 250.0/255.0, blue: 250.0/255.0, alpha: 1.0) // #FAFAFA\nprivate let codeBlockBorderColor = UIColor(red: 232.0/255.0, green: 232.0/255.0, blue: 232.0/255.0, alpha: 1.0) // #E8E8E8\nprivate let codeBlockDarkBgColor = UIColor(white: 1.0, alpha: 0.1) // subtle white overlay\nprivate let codeBlockDarkBorderColor = UIColor(white: 1.0, alpha: 0.2)\n\n@objcMembers\nclass RichTextEditorView: UIView, UITextViewDelegate, UIGestureRecognizerDelegate {\n    private static let defaultLineHeightMultiple: CGFloat = 1.3\n\n    private let textView: RichTextView = {\n        let textStorage = NSTextStorage()\n        let layoutManager = InlineCodeLayoutManager()\n        textStorage.addLayoutManager(layoutManager)\n        let textContainer = NSTextContainer(size: CGSize(width: 0, height: CGFloat.greatestFiniteMagnitude))\n        textContainer.widthTracksTextView = true\n        layoutManager.addTextContainer(textContainer)\n        let tv = RichTextView(frame: .zero, textContainer: textContainer)\n        tv.font = UIFont.systemFont(ofSize: 16)\n        tv.textContainerInset = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4)\n        tv.translatesAutoresizingMaskIntoConstraints = false\n        tv.backgroundColor = .clear\n\n        // Set default paragraph style with consistent line height\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineHeightMultiple = defaultLineHeightMultiple\n        tv.typingAttributes = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .paragraphStyle: paragraphStyle\n        ]\n\n        return tv\n    }()\n\n    private let placeholderLabel: UILabel = {\n        let label = UILabel()\n        label.textColor = UIColor.placeholderText\n        label.font = UIFont.systemFont(ofSize: 16)\n        label.translatesAutoresizingMaskIntoConstraints = false\n        return label\n    }()\n\n    private let floatingToolbar: FloatingToolbar = {\n        let toolbar = FloatingToolbar()\n        toolbar.translatesAutoresizingMaskIntoConstraints = true\n        toolbar.isHidden = true\n        return toolbar\n    }()\n\n    private lazy var toolbarBackdrop: UIView = {\n        let view = UIView()\n        view.backgroundColor = .clear\n        view.isUserInteractionEnabled = false\n        view.isHidden = true\n        return view\n    }()\n\n    private var maxHeightConstraint: NSLayoutConstraint?\n    private var placeholderTopConstraint: NSLayoutConstraint?\n    private var undoStack: [NSAttributedString] = []\n    private var redoStack: [NSAttributedString] = []\n    private var isInternalChange = false\n    private var currentKeyboardHeight: CGFloat = 0\n    private var savedSelectionRange: NSRange = NSRange(location: 0, length: 0)\n\n    @objc var placeholder: String = \"\" {\n        didSet { placeholderLabel.text = placeholder }\n    }\n\n    @objc var editable: Bool = true {\n        didSet {\n            textView.isEditable = editable\n            applyNumberOfLines()\n        }\n    }\n\n    @objc var maxHeight: CGFloat = 0 {\n        didSet {\n            if maxHeight > 0 {\n                maxHeightConstraint?.isActive = false\n                maxHeightConstraint = textView.heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight)\n                maxHeightConstraint?.isActive = true\n            }\n        }\n    }\n\n    @objc var numberOfLines: Int = 0 {\n        didSet {\n            applyNumberOfLines()\n        }\n    }\n\n    @objc var showToolbar: Bool = true\n\n    /// When true, Bold/Italic/Underline/Strikethrough appear in the text selection context menu.\n    @objc var showTextSelectionMenuItems: Bool = true {\n        didSet {\n            if showTextSelectionMenuItems {\n                let boldItem = UIMenuItem(title: \"Bold\", action: #selector(RichTextView.formatBold))\n                let italicItem = UIMenuItem(title: \"Italic\", action: #selector(RichTextView.formatItalic))\n                let underlineItem = UIMenuItem(title: \"Underline\", action: #selector(RichTextView.formatUnderline))\n                let strikethroughItem = UIMenuItem(title: \"Strikethrough\", action: #selector(RichTextView.formatStrikethrough))\n                UIMenuController.shared.menuItems = [boldItem, italicItem, underlineItem, strikethroughItem]\n            } else {\n                UIMenuController.shared.menuItems = []\n            }\n        }\n    }\n\n    @objc var toolbarOptions: [String]? {\n        didSet {\n            floatingToolbar.setToolbarOptions(toolbarOptions)\n        }\n    }\n\n    @objc var initialContentJson: String? {\n        didSet {\n            if let jsonString = initialContentJson,\n               let data = jsonString.data(using: .utf8),\n               let blocks = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {\n                setContent(blocks: blocks)\n            }\n        }\n    }\n\n    @objc var variant: String = \"outlined\" {\n        didSet {\n            applyVariantStyle()\n        }\n    }\n\n    @objc var onContentChange: RCTDirectEventBlock?\n    @objc var onSelectionChange: RCTDirectEventBlock?\n    @objc var onEditorFocus: RCTDirectEventBlock?\n    @objc var onEditorBlur: RCTDirectEventBlock?\n    @objc var onSizeChange: RCTDirectEventBlock?\n    @objc var onActiveStylesChange: RCTDirectEventBlock?\n    @objc var onLinkTap: RCTDirectEventBlock?\n\n    // Props sent from JS that need @objc declarations to avoid doesNotRecognizeSelector crash\n    @objc var toolbarMode: String?\n    @objc var selection: NSDictionary?\n    @objc var textStyle: NSDictionary? {\n        didSet {\n            guard let style = textStyle else { return }\n            if let fontSize = style[\"fontSize\"] as? CGFloat, fontSize > 0 {\n                let currentFont = textView.font ?? UIFont.systemFont(ofSize: 16)\n                textView.font = currentFont.withSize(fontSize)\n                placeholderLabel.font = UIFont.systemFont(ofSize: fontSize)\n                // Update typingAttributes so new text uses the correct size\n                var attrs = textView.typingAttributes\n                if let existingFont = attrs[.font] as? UIFont {\n                    attrs[.font] = existingFont.withSize(fontSize)\n                } else {\n                    attrs[.font] = UIFont.systemFont(ofSize: fontSize)\n                }\n                textView.typingAttributes = attrs\n            }\n            if let colorStr = style[\"color\"] as? String {\n                // Parse hex color string (#RRGGBB or #AARRGGBB)\n                var hex = colorStr.trimmingCharacters(in: .whitespacesAndNewlines)\n                if hex.hasPrefix(\"#\") { hex = String(hex.dropFirst()) }\n                if let val = UInt64(hex, radix: 16) {\n                    let r, g, b, a: CGFloat\n                    if hex.count == 8 {\n                        a = CGFloat((val >> 24) & 0xFF) / 255.0\n                        r = CGFloat((val >> 16) & 0xFF) / 255.0\n                        g = CGFloat((val >> 8) & 0xFF) / 255.0\n                        b = CGFloat(val & 0xFF) / 255.0\n                    } else {\n                        a = 1.0\n                        r = CGFloat((val >> 16) & 0xFF) / 255.0\n                        g = CGFloat((val >> 8) & 0xFF) / 255.0\n                        b = CGFloat(val & 0xFF) / 255.0\n                    }\n                    textView.textColor = UIColor(red: r, green: g, blue: b, alpha: a)\n                }\n            }\n            updateContentSize()\n        }\n    }\n    @objc var placeholderTextColor: String?\n    @objc var text: String?\n    @objc var codeBackgroundColor: String?\n    @objc var codeBorderColor: String?\n    @objc var codeTextColor: String?\n    @objc var codeFontSize: NSNumber?\n\n    private var lastReportedHeight: CGFloat = 0\n    private var calculatedHeight: CGFloat = 22\n\n    // Pending styles for type-ahead formatting (matches Android pendingStyles/explicitlyOffStyles)\n    private var pendingStyles: Set<String> = []\n    private var explicitlyOffStyles: Set<String> = []\n    private var pendingStylesInsertPos: Int = -1\n\n    /// Typing attributes to forcefully restore after all processing (including async\n    /// JS bridge callbacks like syncMentionRanges) completes. Set by autoContinueListOnEnter\n    /// so that inline styles (bold, italic, etc.) survive across list line continuation.\n    private var deferredTypingAttrs: [NSAttributedString.Key: Any]? = nil\n\n    /// Capture active inline styles from typingAttributes into pendingStyles\n    /// so they survive attributedText resets during list continuation.\n    /// Mirrors Android behavior where pendingStyles persist across Enter in lists.\n    private func captureInlineStylesToPending() {\n        let attrs = textView.typingAttributes\n        if let font = attrs[.font] as? UIFont {\n            let traits = font.fontDescriptor.symbolicTraits\n            if traits.contains(.traitBold) { pendingStyles.insert(\"bold\") }\n            if traits.contains(.traitItalic) { pendingStyles.insert(\"italic\") }\n        }\n        if let underline = attrs[.underlineStyle] as? Int, underline != 0 {\n            pendingStyles.insert(\"underline\")\n        }\n        if let strike = attrs[.strikethroughStyle] as? Int, strike != 0 {\n            pendingStyles.insert(\"strikethrough\")\n        }\n        pendingStylesInsertPos = textView.selectedRange.location\n    }\n\n    override init(frame: CGRect) {\n        super.init(frame: frame)\n        setupView()\n    }\n\n    required init?(coder: NSCoder) {\n        super.init(coder: coder)\n        setupView()\n    }\n\n    private lazy var bottomBorder: UIView = {\n        let view = UIView()\n        view.backgroundColor = UIColor.separator\n        view.translatesAutoresizingMaskIntoConstraints = false\n        view.isHidden = true\n        return view\n    }()\n\n    private func setupView() {\n        backgroundColor = .systemBackground\n\n        addSubview(textView)\n        addSubview(placeholderLabel)\n        addSubview(bottomBorder)\n\n        NSLayoutConstraint.activate([\n            bottomBorder.leadingAnchor.constraint(equalTo: leadingAnchor),\n            bottomBorder.trailingAnchor.constraint(equalTo: trailingAnchor),\n            bottomBorder.bottomAnchor.constraint(equalTo: bottomAnchor),\n            bottomBorder.heightAnchor.constraint(equalToConstant: 1)\n        ])\n\n        applyVariantStyle()\n\n        floatingToolbar.editorView = self\n\n        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(backdropTapped))\n        toolbarBackdrop.addGestureRecognizer(tapGesture)\n\n        DispatchQueue.main.async { [weak self] in\n            if let window = self?.window {\n                window.addSubview(self!.toolbarBackdrop)\n                window.addSubview(self!.floatingToolbar)\n            }\n        }\n\n        clipsToBounds = true\n\n        NSLayoutConstraint.activate([\n            textView.topAnchor.constraint(equalTo: topAnchor),\n            textView.leadingAnchor.constraint(equalTo: leadingAnchor),\n            textView.trailingAnchor.constraint(equalTo: trailingAnchor),\n            textView.bottomAnchor.constraint(equalTo: bottomAnchor),\n\n            placeholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 9)\n        ])\n\n        placeholderTopConstraint = placeholderLabel.topAnchor.constraint(equalTo: textView.topAnchor, constant: 4)\n        placeholderTopConstraint?.isActive = true\n\n        textView.delegate = self\n        textView.editorContainer = self\n        textView.isScrollEnabled = false\n        textView.alwaysBounceVertical = false\n        textView.showsVerticalScrollIndicator = false\n\n        // Register custom context menu items for text selection (matching Android Bold/Italic + Underline/Strikethrough)\n        let boldItem = UIMenuItem(title: \"Bold\", action: #selector(RichTextView.formatBold))\n        let italicItem = UIMenuItem(title: \"Italic\", action: #selector(RichTextView.formatItalic))\n        let underlineItem = UIMenuItem(title: \"Underline\", action: #selector(RichTextView.formatUnderline))\n        let strikethroughItem = UIMenuItem(title: \"Strikethrough\", action: #selector(RichTextView.formatStrikethrough))\n        UIMenuController.shared.menuItems = [boldItem, italicItem, underlineItem, strikethroughItem]\n\n        // Tap gesture for intercepting link taps in editable mode\n        // (shouldInteractWith delegate doesn't fire when isEditable=true)\n        let linkTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleLinkTap(_:)))\n        linkTapGesture.delegate = self\n        textView.addGestureRecognizer(linkTapGesture)\n\n        NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: UITextView.textDidChangeNotification, object: textView)\n        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)\n        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)\n    }\n\n    @objc private func keyboardWillShow(_ notification: Notification) {\n        if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {\n            currentKeyboardHeight = keyboardFrame.height\n        }\n    }\n\n    @objc private func keyboardWillHide(_ notification: Notification) {\n        currentKeyboardHeight = 0\n    }\n\n    @objc private func backdropTapped() {\n        hideToolbar()\n        textView.selectedRange = NSRange(location: textView.selectedRange.location, length: 0)\n    }\n\n    private func hideToolbar() {\n        floatingToolbar.isHidden = true\n        toolbarBackdrop.isHidden = true\n    }\n\n    override func didMoveToWindow() {\n        super.didMoveToWindow()\n        if let window = window {\n            toolbarBackdrop.removeFromSuperview()\n            floatingToolbar.removeFromSuperview()\n            window.addSubview(toolbarBackdrop)\n            window.addSubview(floatingToolbar)\n        }\n    }\n\n    override func removeFromSuperview() {\n        toolbarBackdrop.removeFromSuperview()\n        floatingToolbar.removeFromSuperview()\n        super.removeFromSuperview()\n    }\n\n    deinit {\n        toolbarBackdrop.removeFromSuperview()\n        floatingToolbar.removeFromSuperview()\n        NotificationCenter.default.removeObserver(self)\n    }\n\n    private func updateToolbarPosition() {\n        guard let selectedRange = textView.selectedTextRange,\n              !selectedRange.isEmpty,\n              showToolbar,\n              let window = window else {\n            hideToolbar()\n            return\n        }\n\n       let endRect = textView.caretRect(for: selectedRange.end)\n        let startRect = textView.caretRect(for: selectedRange.start)\n\n        guard !endRect.isNull && !endRect.isInfinite else {\n            hideToolbar()\n            return\n        }\n\n        let convertedEndRect = textView.convert(endRect, to: window)\n        let convertedStartRect = textView.convert(startRect, to: window)\n\n        guard convertedEndRect.maxY > 0 && convertedStartRect.minY < window.bounds.height else {\n            hideToolbar()\n            return\n        }\n\n        let toolbarWidth: CGFloat = floatingToolbar.getToolbarWidth()\n        let toolbarHeight: CGFloat = 52\n\n        let safeAreaTop = window.safeAreaInsets.top\n        let safeAreaBottom = window.safeAreaInsets.bottom\n\n        var toolbarX = (window.bounds.width - toolbarWidth) / 2\n        var toolbarY = convertedEndRect.maxY + 8\n\n        toolbarX = max(8, min(toolbarX, window.bounds.width - toolbarWidth - 8))\n\n        let maxY = window.bounds.height - safeAreaBottom - currentKeyboardHeight - toolbarHeight - 8\n        if toolbarY > maxY {\n            toolbarY = convertedStartRect.minY - toolbarHeight - 8\n            if toolbarY < safeAreaTop + 8 {\n                toolbarY = safeAreaTop + 8\n            }\n        }\n\n        toolbarBackdrop.frame = window.bounds\n        toolbarBackdrop.isHidden = false\n        window.bringSubviewToFront(toolbarBackdrop)\n\n        floatingToolbar.frame = CGRect(x: toolbarX, y: toolbarY, width: toolbarWidth, height: toolbarHeight)\n        floatingToolbar.isHidden = false\n        window.bringSubviewToFront(floatingToolbar)\n    }\n\n    func textViewDidBeginEditing(_ textView: UITextView) {\n        onEditorFocus?([:])\n    }\n\n    func textViewDidEndEditing(_ textView: UITextView) {\n        hideToolbar()\n        onEditorBlur?([:])\n    }\n\n    /// Handle tap on links in the editable text view.\n    /// UITextView's shouldInteractWith delegate doesn't fire when isEditable=true,\n    /// so we use a tap gesture recognizer (matching Android's onSingleTapUp).\n    @objc func handleLinkTap(_ gesture: UITapGestureRecognizer) {\n        let location = gesture.location(in: textView)\n        guard let position = textView.closestPosition(to: location) else { return }\n        let charIndex = textView.offset(from: textView.beginningOfDocument, to: position)\n        let attrText = textView.attributedText ?? NSAttributedString()\n        guard charIndex >= 0 && charIndex < attrText.length else { return }\n\n        // Check if the tapped character has a link attribute\n        var effectiveRange = NSRange(location: 0, length: 0)\n        guard let linkValue = attrText.attribute(LinkURLAttributeKey, at: charIndex, effectiveRange: &effectiveRange) else { return }\n\n        let urlString: String\n        if let url = linkValue as? URL {\n            urlString = url.absoluteString\n        } else if let str = linkValue as? String {\n            urlString = str\n        } else {\n            return\n        }\n\n        let linkText = (attrText.string as NSString).substring(with: effectiveRange)\n        onLinkTap?([\n            \"url\": urlString,\n            \"text\": linkText,\n            \"location\": effectiveRange.location,\n            \"length\": effectiveRange.length\n        ])\n    }\n\n    /// Allow the link tap gesture to fire simultaneously with the text view's\n    /// built-in gestures so normal cursor placement and selection still work.\n    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {\n        return true\n    }\n\n    func textViewDidChangeSelection(_ textView: UITextView) {\n       let range = textView.selectedRange\n        if range.length > 0 {\n            savedSelectionRange = range\n        }\n\n        // Skip toolbar/style updates during internal changes (e.g. autoContinueListOnEnter)\n        // to avoid emitting stale style state before typingAttributes are restored.\n        guard !isInternalChange else { return }\n\n        // Invalidate pending styles if cursor moved away from insert position\n        if pendingStylesInsertPos >= 0 && (!pendingStyles.isEmpty || !explicitlyOffStyles.isEmpty) {\n            if range.location != pendingStylesInsertPos && range.location != pendingStylesInsertPos + 1 {\n                // Preserve \"codeBlock\" across cursor movements — it's a persistent\n                // block-level style that should survive until explicitly toggled off\n                let hadCodeBlock = pendingStyles.contains(\"codeBlock\")\n                pendingStyles.removeAll()\n                explicitlyOffStyles.removeAll()\n                pendingStylesInsertPos = -1\n                if hadCodeBlock {\n                    pendingStyles.insert(\"codeBlock\")\n                    pendingStylesInsertPos = range.location\n                }\n            } else if range.location == pendingStylesInsertPos + 1 {\n                // User typed one char — advance insert pos\n                pendingStylesInsertPos = range.location\n            }\n        }\n\n        updateToolbarPosition()\n        updateToolbarButtonStates()\n        emitActiveStyles()\n\n        // Strip mention-specific attributes from typingAttributes when cursor\n        // lands adjacent to (or inherits from) a deleted mention.\n        do {\n            var needsClean = false\n            var isMentionTriggered = false\n            if let attrText = textView.attributedText, attrText.length > 0, range.length == 0, range.location > 0 {\n                let checkIdx = min(range.location - 1, attrText.length - 1)\n                let charAttrs = attrText.attributes(at: checkIdx, effectiveRange: nil)\n                if charAttrs[MentionMarkerKey] != nil {\n                    needsClean = true\n                    isMentionTriggered = true\n                }\n            }\n            if !needsClean, let bg = textView.typingAttributes[.backgroundColor] as? UIColor {\n                var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                bg.getRed(&r, green: &g, blue: &b, alpha: &a)\n                let isInlineCodeBg = abs(r - 245.0/255.0) < 0.02 && abs(g - 245.0/255.0) < 0.02 && abs(b - 245.0/255.0) < 0.02 && a > 0.9\n                let isCodeBlockBg = abs(r - 250.0/255.0) < 0.02 && abs(g - 250.0/255.0) < 0.02 && abs(b - 250.0/255.0) < 0.02 && a > 0.9\n                if !isInlineCodeBg && !isCodeBlockBg { needsClean = true }\n            }\n            if needsClean {\n                var cleanAttrs = textView.typingAttributes\n                cleanAttrs.removeValue(forKey: .backgroundColor)\n                cleanAttrs.removeValue(forKey: MentionMarkerKey)\n                if let fg = cleanAttrs[.foregroundColor] as? UIColor {\n                    var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                    fg.getRed(&r, green: &g, blue: &b, alpha: &a)\n                    if abs(r - 104.0/255.0) < 0.05 && abs(g - 82.0/255.0) < 0.05 && abs(b - 214.0/255.0) < 0.05 && a > 0.9 {\n                        cleanAttrs[.foregroundColor] = UIColor.label\n                    }\n                }\n                // Only strip bold when cleanup was triggered by an actual mention marker.\n                // When triggered by a stray backgroundColor alone, bold is legitimate\n                // user formatting (e.g., cursor moved into bold text after backspace)\n                // and must be preserved.\n                if isMentionTriggered,\n                   let font = cleanAttrs[.font] as? UIFont,\n                   font.fontDescriptor.symbolicTraits.contains(.traitBold),\n                   !pendingStyles.contains(\"bold\") {\n                    let unbold = font.fontDescriptor.withSymbolicTraits(\n                        font.fontDescriptor.symbolicTraits.subtracting(.traitBold)\n                    ) ?? font.fontDescriptor\n                    cleanAttrs[.font] = UIFont(descriptor: unbold, size: font.pointSize)\n                }\n                textView.typingAttributes = cleanAttrs\n            }\n        }\n\n        emitActiveStyles()\n\n        // Adjust selection positions to account for zero-width spaces (\\u{200B})\n        // that are stripped from text in sendContentChange().\n        // Without this, JS-side cursor positions don't match the cleaned text length.\n        // Matches Android's onSelectionChanged ZWS adjustment.\n        let rawText = textView.text ?? \"\"\n        var zwspBeforeStart = 0\n        for i in 0..<min(range.location, rawText.count) {\n            if rawText[rawText.index(rawText.startIndex, offsetBy: i)] == \"\\u{200B}\" {\n                zwspBeforeStart += 1\n            }\n        }\n        let adjustedStart = range.location - zwspBeforeStart\n        let adjustedEnd: Int\n        if range.length == 0 {\n            adjustedEnd = adjustedStart\n        } else {\n            var zwspBeforeEnd = zwspBeforeStart\n            let endPos = range.location + range.length\n            for i in range.location..<min(endPos, rawText.count) {\n                if rawText[rawText.index(rawText.startIndex, offsetBy: i)] == \"\\u{200B}\" {\n                    zwspBeforeEnd += 1\n                }\n            }\n            adjustedEnd = endPos - zwspBeforeEnd\n        }\n\n        onSelectionChange?([\n            \"start\": adjustedStart,\n            \"end\": adjustedEnd\n        ])\n    }\n\n    private func emitActiveStyles() {\n        guard let attributedText = textView.attributedText else { return }\n\n        let range = textView.selectedRange\n        let checkRange = range.length > 0 ? range : NSRange(location: max(0, range.location - 1), length: 1)\n\n        guard checkRange.location >= 0, checkRange.location < attributedText.length else {\n            onActiveStylesChange?([\n                \"bold\": pendingStyles.contains(\"bold\") && !explicitlyOffStyles.contains(\"bold\"),\n                \"italic\": pendingStyles.contains(\"italic\") && !explicitlyOffStyles.contains(\"italic\"),\n                \"underline\": pendingStyles.contains(\"underline\") && !explicitlyOffStyles.contains(\"underline\"),\n                \"strikethrough\": pendingStyles.contains(\"strikethrough\") && !explicitlyOffStyles.contains(\"strikethrough\"),\n                \"code\": pendingStyles.contains(\"code\") && !explicitlyOffStyles.contains(\"code\"),\n                \"codeBlock\": pendingStyles.contains(\"codeBlock\"),\n                \"highlight\": false,\n                \"blockType\": \"paragraph\",\n                \"alignment\": \"left\"\n            ])\n            return\n        }\n\n        var hasBold = false\n        var hasItalic = false\n        var hasUnderline = false\n        var hasStrikethrough = false\n        var hasCode = false\n        var hasHighlight = false\n        var blockType = \"paragraph\"\n        var alignment = \"left\"\n\n        // When cursor has no selection, typingAttributes is the authoritative source\n        // for what formatting will apply to the next typed character. This is critical\n        // after list continuation (Enter in lists) where the cursor sits after a\n        // plain-styled prefix but typingAttributes carry the user's active formatting.\n        if range.length == 0 {\n            let typingAttrs = textView.typingAttributes\n            if let font = typingAttrs[.font] as? UIFont {\n                let traits = font.fontDescriptor.symbolicTraits\n                hasBold = traits.contains(.traitBold)\n                hasItalic = traits.contains(.traitItalic)\n                if font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) ||\n                   font.fontName.lowercased().contains(\"mono\") ||\n                   font.fontName.lowercased().contains(\"courier\") {\n                    hasCode = true\n                }\n            }\n            if let underlineStyle = typingAttrs[.underlineStyle] as? Int, underlineStyle != 0 {\n                hasUnderline = true\n            }\n            if let strikeStyle = typingAttrs[.strikethroughStyle] as? Int, strikeStyle != 0 {\n                hasStrikethrough = true\n            }\n            if let bgColor = typingAttrs[.backgroundColor] as? UIColor {\n                var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0\n                bgColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n                if red > 0.8 && green > 0.8 && blue < 0.5 {\n                    hasHighlight = true\n                }\n            }\n        } else {\n            if let font = attributedText.attribute(.font, at: checkRange.location, effectiveRange: nil) as? UIFont {\n                let traits = font.fontDescriptor.symbolicTraits\n                hasBold = traits.contains(.traitBold)\n                hasItalic = traits.contains(.traitItalic)\n\n                if font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) ||\n                   font.fontName.lowercased().contains(\"mono\") ||\n                   font.fontName.lowercased().contains(\"courier\") {\n                    hasCode = true\n                }\n            }\n\n            if let underlineStyle = attributedText.attribute(.underlineStyle, at: checkRange.location, effectiveRange: nil) as? Int,\n               underlineStyle != 0 {\n                hasUnderline = true\n            }\n\n            if let strikeStyle = attributedText.attribute(.strikethroughStyle, at: checkRange.location, effectiveRange: nil) as? Int,\n               strikeStyle != 0 {\n                hasStrikethrough = true\n            }\n\n            if let bgColor = attributedText.attribute(.backgroundColor, at: checkRange.location, effectiveRange: nil) as? UIColor {\n                var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0\n                bgColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n                if red > 0.8 && green > 0.8 && blue < 0.5 {\n                    hasHighlight = true\n                }\n            }\n        }\n\n        let text = textView.text ?? \"\"\n        let lineRange = (text as NSString).lineRange(for: NSRange(location: range.location, length: 0))\n        let lineText = (text as NSString).substring(with: lineRange)\n\n        // Detect combined blockquote+list types (must check before individual prefixes)\n        let hasQuotePrefix = lineText.hasPrefix(\"▎ \")\n        let contentAfterQuote = hasQuotePrefix ? String(lineText.dropFirst(2)) : lineText\n\n        if hasQuotePrefix && contentAfterQuote.hasPrefix(\"• \") {\n            blockType = \"quoteBullet\"\n        } else if hasQuotePrefix && contentAfterQuote.range(of: \"^\\\\d+\\\\.\\\\s\", options: .regularExpression) != nil {\n            blockType = \"quoteNumbered\"\n        } else if contentAfterQuote.hasPrefix(\"• \") {\n            blockType = \"bullet\"\n        } else if contentAfterQuote.range(of: \"^\\\\d+\\\\.\\\\s\", options: .regularExpression) != nil {\n            blockType = \"numbered\"\n        } else if lineText.hasPrefix(\"☐ \") || lineText.hasPrefix(\"☑ \") {\n            blockType = \"checklist\"\n        } else if hasQuotePrefix {\n            blockType = \"quote\"\n        }\n\n        if let font = attributedText.attribute(.font, at: checkRange.location, effectiveRange: nil) as? UIFont,\n           font.pointSize > 20 {\n            blockType = \"heading\"\n        }\n\n        if let paragraphStyle = attributedText.attribute(.paragraphStyle, at: checkRange.location, effectiveRange: nil) as? NSParagraphStyle {\n            switch paragraphStyle.alignment {\n            case .center:\n                alignment = \"center\"\n            case .right:\n                alignment = \"right\"\n            default:\n                alignment = \"left\"\n            }\n        }\n\n        // Detect code block state from CodeBlockAttributeKey\n        var hasCodeBlock = false\n        if checkRange.location >= 0 && checkRange.location + checkRange.length <= attributedText.length {\n            attributedText.enumerateAttribute(CodeBlockAttributeKey, in: checkRange, options: []) { value, _, _ in\n                if value != nil { hasCodeBlock = true }\n            }\n        }\n        let codeBlockActive = hasCodeBlock || pendingStyles.contains(\"codeBlock\")\n\n        onActiveStylesChange?([\n            \"bold\": (hasBold || pendingStyles.contains(\"bold\")) && !explicitlyOffStyles.contains(\"bold\"),\n            \"italic\": (hasItalic || pendingStyles.contains(\"italic\")) && !explicitlyOffStyles.contains(\"italic\"),\n            \"underline\": (hasUnderline || pendingStyles.contains(\"underline\")) && !explicitlyOffStyles.contains(\"underline\"),\n            \"strikethrough\": (hasStrikethrough || pendingStyles.contains(\"strikethrough\")) && !explicitlyOffStyles.contains(\"strikethrough\"),\n            \"code\": (hasCode || pendingStyles.contains(\"code\") || pendingStyles.contains(\"codeBlock\")) && !explicitlyOffStyles.contains(\"code\"),\n            \"codeBlock\": codeBlockActive,\n            \"highlight\": hasHighlight,\n            \"blockType\": blockType,\n            \"alignment\": alignment\n        ])\n    }\n\n    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {\n\n        // --- Mention bleed-through prevention (iOS equivalent of Android SPAN_EXCLUSIVE_EXCLUSIVE) ---\n        // Check if typingAttributes carry mention styling and strip it before insertion.\n        do {\n            var mentionCleanNeeded = false\n            var isMentionTriggered = false\n            // Check 1: cursor is adjacent to text with MentionMarkerKey\n            if let attrText = textView.attributedText, attrText.length > 0 {\n                let checkIdx = max(0, min(range.location - 1, attrText.length - 1))\n                if range.location > 0 {\n                    let charAttrs = attrText.attributes(at: checkIdx, effectiveRange: nil)\n                    if charAttrs[MentionMarkerKey] != nil {\n                        mentionCleanNeeded = true\n                        isMentionTriggered = true\n                    }\n                }\n            }\n            // Check 2: typingAttributes have a non-whitelisted backgroundColor\n            if !mentionCleanNeeded, let bg = textView.typingAttributes[.backgroundColor] as? UIColor {\n                var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                bg.getRed(&r, green: &g, blue: &b, alpha: &a)\n                // Whitelist: inline code (#F5F5F5), code block (#FAFAFA), highlight colors\n                let isInlineCodeBg = abs(r - 245.0/255.0) < 0.02 && abs(g - 245.0/255.0) < 0.02 && abs(b - 245.0/255.0) < 0.02 && a > 0.9\n                let isCodeBlockBg = abs(r - 250.0/255.0) < 0.02 && abs(g - 250.0/255.0) < 0.02 && abs(b - 250.0/255.0) < 0.02 && a > 0.9\n                if !isInlineCodeBg && !isCodeBlockBg { mentionCleanNeeded = true }\n            }\n            if mentionCleanNeeded {\n                var cleanAttrs = textView.typingAttributes\n                cleanAttrs.removeValue(forKey: .backgroundColor)\n                cleanAttrs.removeValue(forKey: MentionMarkerKey)\n                // Restore foreground if it's mention purple (#6852D6)\n                if let fg = cleanAttrs[.foregroundColor] as? UIColor {\n                    var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                    fg.getRed(&r, green: &g, blue: &b, alpha: &a)\n                    if abs(r - 104.0/255.0) < 0.05 && abs(g - 82.0/255.0) < 0.05 && abs(b - 214.0/255.0) < 0.05 && a > 0.9 {\n                        cleanAttrs[.foregroundColor] = UIColor.label\n                    }\n                }\n                // Only strip bold when cleanup was triggered by an actual mention marker.\n                // When triggered by a stray backgroundColor alone, bold is legitimate\n                // user formatting and must be preserved.\n                if isMentionTriggered,\n                   let font = cleanAttrs[.font] as? UIFont,\n                   font.fontDescriptor.symbolicTraits.contains(.traitBold),\n                   !pendingStyles.contains(\"bold\") {\n                    let unbold = font.fontDescriptor.withSymbolicTraits(\n                        font.fontDescriptor.symbolicTraits.subtracting(.traitBold)\n                    ) ?? font.fontDescriptor\n                    cleanAttrs[.font] = UIFont(descriptor: unbold, size: font.pointSize)\n                }\n                textView.typingAttributes = cleanAttrs\n            }\n        }\n\n        if text.isEmpty && range.length == 1 {\n            let currentText = textView.text ?? \"\"\n            let nsText = currentText as NSString\n\n            var lineStart = range.location\n            while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n                lineStart -= 1\n            }\n\n            var lineEnd = range.location\n            while lineEnd < currentText.count && nsText.character(at: lineEnd) != 10 {\n                lineEnd += 1\n            }\n\n            let currentLine = nsText.substring(with: NSRange(location: lineStart, length: lineEnd - lineStart))\n\n            let numberedPattern = \"^(\\\\d+)\\\\.\\\\s\"\n            if let regex = try? NSRegularExpression(pattern: numberedPattern),\n               let match = regex.firstMatch(in: currentLine, range: NSRange(location: 0, length: currentLine.count)) {\n                let prefixEnd = lineStart + match.range.length\n                if range.location < prefixEnd && range.location >= lineStart {\n                    let prefixRange = NSRange(location: lineStart, length: match.range.length)\n                    isInternalChange = true\n                    let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                    mutable.deleteCharacters(in: prefixRange)\n                    textView.attributedText = mutable\n                    textView.selectedRange = NSRange(location: lineStart, length: 0)\n                    isInternalChange = false\n                    renumberNumberedLists()\n                    saveToUndoStack()\n                    sendContentChange()\n                    return false\n                }\n            }\n\n            if currentLine.hasPrefix(\"• \") {\n                let prefixEnd = lineStart + 2\n                if range.location < prefixEnd && range.location >= lineStart {\n                    let prefixRange = NSRange(location: lineStart, length: 2)\n                    isInternalChange = true\n                    let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                    mutable.deleteCharacters(in: prefixRange)\n                    textView.attributedText = mutable\n                    textView.selectedRange = NSRange(location: lineStart, length: 0)\n                    isInternalChange = false\n                    saveToUndoStack()\n                    sendContentChange()\n                    return false\n                }\n            }\n\n           if currentLine.hasPrefix(\"☐ \") || currentLine.hasPrefix(\"☑ \") {\n                let prefixEnd = lineStart + 2\n                if range.location < prefixEnd && range.location >= lineStart {\n                    let prefixRange = NSRange(location: lineStart, length: 2)\n                    isInternalChange = true\n                    let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                    mutable.deleteCharacters(in: prefixRange)\n                    textView.attributedText = mutable\n                    textView.selectedRange = NSRange(location: lineStart, length: 0)\n                    isInternalChange = false\n                    saveToUndoStack()\n                    sendContentChange()\n                    return false\n                }\n            }\n\n            // Handle blockquote prefix (▎ ) backspace — remove entire prefix when cursor is within it\n            if currentLine.hasPrefix(\"▎ \") {\n                let prefixEnd = lineStart + 2\n                if range.location < prefixEnd && range.location >= lineStart {\n                    let prefixRange = NSRange(location: lineStart, length: 2)\n                    isInternalChange = true\n                    let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                    mutable.deleteCharacters(in: prefixRange)\n                    // Ensure remaining text on this line has visible foreground color\n                    // (the ▎ char uses .clear foreground which can bleed into adjacent text)\n                    let newLineEnd = lineEnd - 2\n                    if newLineEnd > lineStart {\n                        mutable.addAttribute(.foregroundColor, value: UIColor.label, range: NSRange(location: lineStart, length: newLineEnd - lineStart))\n                    }\n                    textView.attributedText = mutable\n                    textView.selectedRange = NSRange(location: lineStart, length: 0)\n                    // Reset typingAttributes to visible color so new typing is not invisible\n                    let paragraphStyle = NSMutableParagraphStyle()\n                    paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                    textView.typingAttributes = [\n                        .font: UIFont.systemFont(ofSize: 16),\n                        .foregroundColor: UIColor.label,\n                        .paragraphStyle: paragraphStyle\n                    ]\n                    isInternalChange = false\n                    placeholderLabel.isHidden = !textView.text.isEmpty\n                    DispatchQueue.main.async { [weak self] in\n                        self?.textView.setNeedsDisplay()\n                    }\n                    saveToUndoStack()\n                    sendContentChange()\n                    return false\n                }\n            }\n\n            // Handle standalone blockquote character (▎ without trailing space)\n            if currentLine == \"▎\" {\n                let prefixRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n                isInternalChange = true\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: prefixRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                // Reset typingAttributes to visible color\n                let paragraphStyle = NSMutableParagraphStyle()\n                paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                textView.typingAttributes = [\n                    .font: UIFont.systemFont(ofSize: 16),\n                    .foregroundColor: UIColor.label,\n                    .paragraphStyle: paragraphStyle\n                ]\n                isInternalChange = false\n                placeholderLabel.isHidden = !textView.text.isEmpty\n                DispatchQueue.main.async { [weak self] in\n                    self?.textView.setNeedsDisplay()\n                }\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n        }\n\n        guard text == \"\\n\" else { return true }\n\n        let currentText = textView.text ?? \"\"\n        let nsText = currentText as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        let lineLength = range.location - lineStart\n        let currentLine = nsText.substring(with: NSRange(location: lineStart, length: lineLength))\n\n        // Combined blockquote + numbered list (▎ N. ) — must check before individual prefixes\n        let quoteNumberedPattern = \"^▎ (\\\\d+)\\\\.\\\\s\"\n        if let qnRegex = try? NSRegularExpression(pattern: quoteNumberedPattern),\n           let qnMatch = qnRegex.firstMatch(in: currentLine, range: NSRange(location: 0, length: currentLine.count)),\n           let numberRange = Range(qnMatch.range(at: 1), in: currentLine) {\n\n            let currentNumber = Int(currentLine[numberRange]) ?? 1\n            let lineContent = String(currentLine.dropFirst(qnMatch.range.length))\n\n            if lineContent.trimmingCharacters(in: .whitespaces).isEmpty {\n                // Empty combined line — remove the prefix\n                isInternalChange = true\n                let deleteRange = NSRange(location: lineStart, length: range.location - lineStart)\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: deleteRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                let paragraphStyle = NSMutableParagraphStyle()\n                paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                textView.typingAttributes = [\n                    .font: UIFont.systemFont(ofSize: 16),\n                    .foregroundColor: UIColor.label,\n                    .paragraphStyle: paragraphStyle\n                ]\n                isInternalChange = false\n                renumberNumberedLists()\n                DispatchQueue.main.async { [weak self] in\n                    self?.textView.setNeedsDisplay()\n                }\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n\n            let nextNumber = currentNumber + 1\n            let prefix = \"▎ \\(nextNumber). \"\n            // Capture active inline styles into pendingStyles so they survive\n            // attributedText resets during list continuation (matches Android).\n            captureInlineStylesToPending()\n            isInternalChange = true\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n            let insertPos = range.location\n            let newlineAttr = NSAttributedString(string: \"\\n\", attributes: plainAttributes)\n            var quoteCharAttrs = plainAttributes\n            quoteCharAttrs[.foregroundColor] = UIColor.clear\n            let quoteCharAttr = NSAttributedString(string: \"▎\", attributes: quoteCharAttrs)\n            let restAttr = NSAttributedString(string: \" \\(nextNumber). \", attributes: plainAttributes)\n            let insertAttr = NSMutableAttributedString()\n            insertAttr.append(newlineAttr)\n            insertAttr.append(quoteCharAttr)\n            insertAttr.append(restAttr)\n            mutable.insert(insertAttr, at: insertPos)\n            textView.attributedText = mutable\n            textView.selectedRange = NSRange(location: insertPos + prefix.count + 1, length: 0)\n            applyPendingStylesToTypingAttributes()\n            deferredTypingAttrs = textView.typingAttributes\n            isInternalChange = false\n            renumberNumberedLists()\n            DispatchQueue.main.async { [weak self] in\n                self?.textView.setNeedsDisplay()\n            }\n            saveToUndoStack()\n            sendContentChange()\n            return false\n        }\n\n        // Combined blockquote + bullet list (▎ • )\n        if currentLine.hasPrefix(\"▎ • \") {\n            let lineContent = String(currentLine.dropFirst(4))\n            if lineContent.trimmingCharacters(in: .whitespaces).isEmpty {\n                // Empty combined line — remove the prefix\n                isInternalChange = true\n                let deleteRange = NSRange(location: lineStart, length: range.location - lineStart)\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: deleteRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                let paragraphStyle = NSMutableParagraphStyle()\n                paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                textView.typingAttributes = [\n                    .font: UIFont.systemFont(ofSize: 16),\n                    .foregroundColor: UIColor.label,\n                    .paragraphStyle: paragraphStyle\n                ]\n                isInternalChange = false\n                DispatchQueue.main.async { [weak self] in\n                    self?.textView.setNeedsDisplay()\n                }\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n\n            // Capture active inline styles into pendingStyles so they survive\n            // attributedText resets during list continuation (matches Android).\n            captureInlineStylesToPending()\n            isInternalChange = true\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n            let insertPos = range.location\n            let newlineAttr = NSAttributedString(string: \"\\n\", attributes: plainAttributes)\n            var quoteCharAttrs = plainAttributes\n            quoteCharAttrs[.foregroundColor] = UIColor.clear\n            let quoteCharAttr = NSAttributedString(string: \"▎\", attributes: quoteCharAttrs)\n            let restAttr = NSAttributedString(string: \" • \", attributes: plainAttributes)\n            let insertAttr = NSMutableAttributedString()\n            insertAttr.append(newlineAttr)\n            insertAttr.append(quoteCharAttr)\n            insertAttr.append(restAttr)\n            mutable.insert(insertAttr, at: insertPos)\n            textView.attributedText = mutable\n            textView.selectedRange = NSRange(location: insertPos + 5, length: 0) // \\n + ▎ + \" • \" = 5\n            applyPendingStylesToTypingAttributes()\n            deferredTypingAttrs = textView.typingAttributes\n            isInternalChange = false\n            DispatchQueue.main.async { [weak self] in\n                self?.textView.setNeedsDisplay()\n            }\n            saveToUndoStack()\n            sendContentChange()\n            return false\n        }\n\n        if currentLine.hasPrefix(\"• \") {\n            let lineContent = String(currentLine.dropFirst(2))\n            if lineContent.trimmingCharacters(in: .whitespaces).isEmpty {\n                isInternalChange = true\n                let deleteRange = NSRange(location: lineStart, length: range.location - lineStart)\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: deleteRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                isInternalChange = false\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n            // Capture active inline styles into pendingStyles so they survive\n            // attributedText resets during list continuation (matches Android).\n            captureInlineStylesToPending()\n            isInternalChange = true\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            textView.typingAttributes = plainAttributes\n            textView.insertText(\"\\n• \")\n            // Restore inline styles via pendingStyles mechanism\n            applyPendingStylesToTypingAttributes()\n            deferredTypingAttrs = textView.typingAttributes\n            isInternalChange = false\n            saveToUndoStack()\n            sendContentChange()\n            return false\n        }\n\n        let numberedPattern = \"^(\\\\d+)\\\\.\\\\s\"\n        if let regex = try? NSRegularExpression(pattern: numberedPattern),\n           let match = regex.firstMatch(in: currentLine, range: NSRange(location: 0, length: currentLine.count)),\n           let numberRange = Range(match.range(at: 1), in: currentLine) {\n\n            let currentNumber = Int(currentLine[numberRange]) ?? 1\n            let lineContent = String(currentLine.dropFirst(match.range.length))\n\n            if lineContent.trimmingCharacters(in: .whitespaces).isEmpty {\n                isInternalChange = true\n                let deleteRange = NSRange(location: lineStart, length: range.location - lineStart)\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: deleteRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                isInternalChange = false\n                renumberNumberedLists()\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n\n            let nextNumber = currentNumber + 1\n            // Capture active inline styles into pendingStyles so they survive\n            // attributedText resets during list continuation (matches Android).\n            captureInlineStylesToPending()\n            isInternalChange = true\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            textView.typingAttributes = plainAttributes\n            textView.insertText(\"\\n\\(nextNumber). \")\n            renumberNumberedLists()\n            applyPendingStylesToTypingAttributes()\n            deferredTypingAttrs = textView.typingAttributes\n            isInternalChange = false\n            saveToUndoStack()\n            sendContentChange()\n            return false\n        }\n\n        // Handle checklist Enter key\n        if currentLine.hasPrefix(\"☐ \") || currentLine.hasPrefix(\"☑ \") {\n            let lineContent = String(currentLine.dropFirst(2))\n            if lineContent.trimmingCharacters(in: .whitespaces).isEmpty {\n                isInternalChange = true\n                let deleteRange = NSRange(location: lineStart, length: range.location - lineStart)\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: deleteRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                isInternalChange = false\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n            // Capture active inline styles into pendingStyles so they survive\n            // attributedText resets during list continuation (matches Android).\n            captureInlineStylesToPending()\n            isInternalChange = true\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            textView.typingAttributes = plainAttributes\n            textView.insertText(\"\\n☐ \")\n            applyPendingStylesToTypingAttributes()\n            deferredTypingAttrs = textView.typingAttributes\n            isInternalChange = false\n            saveToUndoStack()\n            sendContentChange()\n            return false\n        }\n\n        // Handle blockquote (▎) Enter key continuation\n        if currentLine.hasPrefix(\"▎ \") {\n            let lineContent = String(currentLine.dropFirst(2))\n            if lineContent.trimmingCharacters(in: .whitespaces).isEmpty {\n                // Empty quote line - remove the prefix\n                isInternalChange = true\n                let deleteRange = NSRange(location: lineStart, length: range.location - lineStart)\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: deleteRange)\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: lineStart, length: 0)\n                // Reset typingAttributes to visible color after removing ▎ prefix\n                let paragraphStyle = NSMutableParagraphStyle()\n                paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                textView.typingAttributes = [\n                    .font: UIFont.systemFont(ofSize: 16),\n                    .foregroundColor: UIColor.label,\n                    .paragraphStyle: paragraphStyle\n                ]\n                isInternalChange = false\n                // Trigger redraw for blockquote bar asynchronously to avoid layout issues\n                DispatchQueue.main.async { [weak self] in\n                    self?.textView.setNeedsDisplay()\n                }\n                saveToUndoStack()\n                sendContentChange()\n                return false\n            }\n            // Continue quote on new line with transparent ▎ character\n            // Capture active inline styles into pendingStyles so they survive\n            // attributedText resets during list continuation (matches Android).\n            captureInlineStylesToPending()\n            isInternalChange = true\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            // Insert newline + quote prefix with transparent ▎ character\n            let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n            let insertPos = range.location\n            let newlineAttr = NSAttributedString(string: \"\\n\", attributes: plainAttributes)\n            var quoteCharAttrs = plainAttributes\n            quoteCharAttrs[.foregroundColor] = UIColor.clear // Make ▎ invisible\n            let quoteCharAttr = NSAttributedString(string: \"▎\", attributes: quoteCharAttrs)\n            let spaceAttr = NSAttributedString(string: \" \", attributes: plainAttributes)\n            let insertAttr = NSMutableAttributedString()\n            insertAttr.append(newlineAttr)\n            insertAttr.append(quoteCharAttr)\n            insertAttr.append(spaceAttr)\n            mutable.insert(insertAttr, at: insertPos)\n            textView.attributedText = mutable\n            textView.selectedRange = NSRange(location: insertPos + 3, length: 0)\n            applyPendingStylesToTypingAttributes()\n            deferredTypingAttrs = textView.typingAttributes\n            isInternalChange = false\n            // Trigger redraw for blockquote bar asynchronously to avoid layout issues\n            DispatchQueue.main.async { [weak self] in\n                self?.textView.setNeedsDisplay()\n            }\n            saveToUndoStack()\n            sendContentChange()\n            return false\n        }\n\n        // Handle code block Enter key continuation\n        // When Enter is pressed inside a code block, ensure the new line inherits\n        // code block typingAttributes (monospace font, CodeBlockAttributeKey)\n        if let attrText = textView.attributedText, range.location > 0 {\n            let checkPos = min(range.location - 1, attrText.length - 1)\n            if checkPos >= 0 {\n                let hasCodeBlockAttr = attrText.attribute(CodeBlockAttributeKey, at: checkPos, effectiveRange: nil) != nil\n                if hasCodeBlockAttr {\n                    let monoFont = UIFont.monospacedSystemFont(ofSize: 16, weight: .regular)\n                    let paragraphStyle = NSMutableParagraphStyle()\n                    paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                    textView.typingAttributes = [\n                        .font: monoFont,\n                        .foregroundColor: UIColor.label,\n                        .paragraphStyle: paragraphStyle,\n                        CodeBlockAttributeKey: true\n                    ]\n                    pendingStyles.insert(\"codeBlock\")\n                    pendingStylesInsertPos = range.location + 1\n                }\n            }\n        }\n\n        return true\n    }\n\n    func updateToolbarButtonStates() {\n        let range = textView.selectedRange\n\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineContent = lineStart < lineEnd ? nsText.substring(with: NSRange(location: lineStart, length: lineEnd - lineStart)) : \"\"\n\n        // Detect combined blockquote+list patterns (matches Android behavior)\n        let hasQuote = lineContent.hasPrefix(\"▎ \")\n        let contentAfterQuote = hasQuote ? String(lineContent.dropFirst(2)) : lineContent\n        let hasBullet = contentAfterQuote.hasPrefix(\"• \")\n        let hasNumbered = contentAfterQuote.range(of: \"^\\\\d+\\\\.\\\\s\", options: .regularExpression) != nil\n        let hasChecklist = lineContent.hasPrefix(\"☐ \") || lineContent.hasPrefix(\"☑ \")\n\n        var currentAlignment: NSTextAlignment = .left\n        if let attrText = textView.attributedText, attrText.length > 0 {\n            let checkIndex = min(max(0, range.location - 1), attrText.length - 1)\n            if let paragraphStyle = attrText.attribute(.paragraphStyle, at: checkIndex, effectiveRange: nil) as? NSParagraphStyle {\n                currentAlignment = paragraphStyle.alignment\n            }\n        }\n\n        guard range.length > 0 else {\n            // Include pending styles for type-ahead toolbar state\n            let boldActive = pendingStyles.contains(\"bold\") && !explicitlyOffStyles.contains(\"bold\")\n            let italicActive = pendingStyles.contains(\"italic\") && !explicitlyOffStyles.contains(\"italic\")\n            let underlineActive = pendingStyles.contains(\"underline\") && !explicitlyOffStyles.contains(\"underline\")\n            let strikethroughActive = pendingStyles.contains(\"strikethrough\") && !explicitlyOffStyles.contains(\"strikethrough\")\n            let codeActive = pendingStyles.contains(\"code\") && !explicitlyOffStyles.contains(\"code\")\n\n            // Also check attributes at cursor position\n            let attributedText = textView.attributedText ?? NSAttributedString()\n            if range.location > 0 && range.location <= attributedText.length {\n                let checkPos = range.location - 1\n                if let font = attributedText.attribute(.font, at: checkPos, effectiveRange: nil) as? UIFont {\n                    let traits = font.fontDescriptor.symbolicTraits\n                    let hasBoldAtCursor = traits.contains(.traitBold) && !explicitlyOffStyles.contains(\"bold\")\n                    let hasItalicAtCursor = traits.contains(.traitItalic) && !explicitlyOffStyles.contains(\"italic\")\n                    let hasCodeAtCursor = traits.contains(.traitMonoSpace) && !explicitlyOffStyles.contains(\"code\")\n                    floatingToolbar.updateButtonStates(\n                        bold: boldActive || hasBoldAtCursor, italic: italicActive || hasItalicAtCursor,\n                        underline: underlineActive || ((attributedText.attribute(.underlineStyle, at: checkPos, effectiveRange: nil) as? Int ?? 0) != 0 && !explicitlyOffStyles.contains(\"underline\")),\n                        strikethrough: strikethroughActive || ((attributedText.attribute(.strikethroughStyle, at: checkPos, effectiveRange: nil) as? Int ?? 0) != 0 && !explicitlyOffStyles.contains(\"strikethrough\")),\n                        code: codeActive || hasCodeAtCursor, highlight: false, heading: false,\n                        bullet: hasBullet, numbered: hasNumbered, quote: hasQuote, checklist: hasChecklist,\n                        alignLeft: currentAlignment == .left, alignCenter: currentAlignment == .center, alignRight: currentAlignment == .right\n                    )\n                    return\n                }\n            }\n\n            floatingToolbar.updateButtonStates(\n                bold: boldActive, italic: italicActive, underline: underlineActive, strikethrough: strikethroughActive,\n                code: codeActive, highlight: false, heading: false,\n                bullet: hasBullet, numbered: hasNumbered, quote: hasQuote, checklist: hasChecklist,\n                alignLeft: currentAlignment == .left, alignCenter: currentAlignment == .center, alignRight: currentAlignment == .right\n            )\n            return\n        }\n\n        let attributedText = textView.attributedText ?? NSAttributedString()\n        var hasBold = false\n        var hasItalic = false\n        var hasUnderline = false\n        var hasStrikethrough = false\n        var hasCode = false\n        var hasHighlight = false\n        var hasHeading = false\n\n        // Ensure range is valid for the attributed text\n        let safeRange = NSRange(\n            location: min(range.location, attributedText.length),\n            length: min(range.length, max(0, attributedText.length - range.location))\n        )\n        guard safeRange.length > 0 else {\n            floatingToolbar.updateButtonStates(\n                bold: false, italic: false, underline: false, strikethrough: false,\n                code: false, highlight: false, heading: false,\n                bullet: hasBullet, numbered: hasNumbered, quote: hasQuote, checklist: hasChecklist,\n                alignLeft: currentAlignment == .left, alignCenter: currentAlignment == .center, alignRight: currentAlignment == .right\n            )\n            return\n        }\n\n        attributedText.enumerateAttributes(in: safeRange, options: []) { attrs, _, _ in\n            if let font = attrs[.font] as? UIFont {\n                let traits = font.fontDescriptor.symbolicTraits\n                if traits.contains(.traitBold) { hasBold = true }\n                if traits.contains(.traitItalic) { hasItalic = true }\n                if traits.contains(.traitMonoSpace) { hasCode = true }\n                if font.pointSize > 18 { hasHeading = true }\n            }\n            if attrs[.underlineStyle] != nil { hasUnderline = true }\n            if attrs[.strikethroughStyle] != nil { hasStrikethrough = true }\n            if let bgColor = attrs[.backgroundColor] as? UIColor, bgColor != UIColor.systemGray5 {\n                hasHighlight = true\n            }\n        }\n\n        floatingToolbar.updateButtonStates(\n            bold: hasBold, italic: hasItalic, underline: hasUnderline, strikethrough: hasStrikethrough,\n            code: hasCode, highlight: hasHighlight, heading: hasHeading,\n            bullet: hasBullet, numbered: hasNumbered, quote: hasQuote, checklist: hasChecklist,\n            alignLeft: currentAlignment == .left, alignCenter: currentAlignment == .center, alignRight: currentAlignment == .right\n        )\n    }\n\n    @objc private func textDidChange() {\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        // Trigger redraw for blockquote bar asynchronously to avoid layout issues\n        DispatchQueue.main.async { [weak self] in\n            self?.textView.setNeedsDisplay()\n        }\n\n        if !isInternalChange {\n            // Clear pending styles after text was typed (typingAttributes already applied them).\n            // Preserve inline styles (bold, italic, underline, strikethrough) — they persist\n            // until the cursor moves to a different position (matching Android behavior where\n            // pendingStyles is only cleared in onSelectionChanged, not afterTextChanged).\n            // Also preserve \"codeBlock\" — it's a persistent block-level style.\n            if !pendingStyles.isEmpty || !explicitlyOffStyles.isEmpty {\n                let hadCodeBlock = pendingStyles.contains(\"codeBlock\")\n                let hadBold = pendingStyles.contains(\"bold\")\n                let hadItalic = pendingStyles.contains(\"italic\")\n                let hadUnderline = pendingStyles.contains(\"underline\")\n                let hadStrikethrough = pendingStyles.contains(\"strikethrough\")\n                let hadOffBold = explicitlyOffStyles.contains(\"bold\")\n                let hadOffItalic = explicitlyOffStyles.contains(\"italic\")\n                let hadOffUnderline = explicitlyOffStyles.contains(\"underline\")\n                let hadOffStrikethrough = explicitlyOffStyles.contains(\"strikethrough\")\n                pendingStyles.removeAll()\n                explicitlyOffStyles.removeAll()\n                pendingStylesInsertPos = -1\n                // Restore persistent styles\n                if hadCodeBlock {\n                    pendingStyles.insert(\"codeBlock\")\n                    pendingStylesInsertPos = textView.selectedRange.location\n                }\n                if hadBold { pendingStyles.insert(\"bold\") }\n                if hadItalic { pendingStyles.insert(\"italic\") }\n                if hadUnderline { pendingStyles.insert(\"underline\") }\n                if hadStrikethrough { pendingStyles.insert(\"strikethrough\") }\n                if hadOffBold { explicitlyOffStyles.insert(\"bold\") }\n                if hadOffItalic { explicitlyOffStyles.insert(\"italic\") }\n                if hadOffUnderline { explicitlyOffStyles.insert(\"underline\") }\n                if hadOffStrikethrough { explicitlyOffStyles.insert(\"strikethrough\") }\n                if pendingStylesInsertPos < 0 && (!pendingStyles.isEmpty || !explicitlyOffStyles.isEmpty) {\n                    pendingStylesInsertPos = textView.selectedRange.location\n                }\n                if !pendingStyles.isEmpty {\n                    applyPendingStylesToTypingAttributes()\n                }\n            }\n            detectInlineMarkdownShortcuts()\n            detectLinkShortcut()\n            detectBlockquoteShortcut()\n            checkTripleEnterExitCodeBlock()\n            autoContinueListOnEnter()\n            saveToUndoStack()\n        }\n\n        // Reset pending styles and typingAttributes when text becomes empty\n        if textView.text.isEmpty {\n            pendingStyles.removeAll()\n            explicitlyOffStyles.removeAll()\n            pendingStylesInsertPos = -1\n            deferredTypingAttrs = nil\n            // Reset typingAttributes to plain so next typed text has no ghost formatting\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            textView.typingAttributes = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle\n            ]\n        }\n\n        // Skip applyListIndentation/renumberNumberedLists during internal changes\n        // to avoid resetting typingAttributes set by autoContinueListOnEnter.\n        guard !isInternalChange else {\n            updateContentSize()\n            sendContentChange()\n            return\n        }\n\n        // When deferredTypingAttrs is set (from autoContinueListOnEnter), use it as\n        // the authoritative source for typingAttributes. This survives any resets caused\n        // by applyListIndentation/renumberNumberedLists setting textView.attributedText.\n        // On the first non-Enter keystroke after list continuation, the deferred attrs\n        // ensure the character is typed with the correct formatting; after that, clear\n        // deferredTypingAttrs since the text at cursor now carries the correct attributes\n        // and UIKit will derive typingAttributes from it naturally.\n        let savedTypingAttrs: [NSAttributedString.Key: Any]\n        if let deferred = deferredTypingAttrs {\n            savedTypingAttrs = deferred\n        } else {\n            savedTypingAttrs = textView.typingAttributes\n        }\n\n        applyListIndentation()\n        renumberNumberedLists()\n\n        // Restore typingAttributes that were wiped by attributedText assignment\n        textView.typingAttributes = savedTypingAttrs\n\n        // Strip mention styling from typingAttributes after text change.\n        // Use whitelist approach: remove any bg that isn't inline code or code block.\n        do {\n            var needsClean = false\n            var isMentionTriggered = false\n            let cursorPos = textView.selectedRange.location\n            if let attrText = textView.attributedText, attrText.length > 0, cursorPos > 0 {\n                let checkIdx = min(cursorPos - 1, attrText.length - 1)\n                let charAttrs = attrText.attributes(at: checkIdx, effectiveRange: nil)\n                if charAttrs[MentionMarkerKey] != nil {\n                    needsClean = true\n                    isMentionTriggered = true\n                }\n            }\n            if !needsClean, let bg = textView.typingAttributes[.backgroundColor] as? UIColor {\n                var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                bg.getRed(&r, green: &g, blue: &b, alpha: &a)\n                let isInlineCodeBg = abs(r - 245.0/255.0) < 0.02 && abs(g - 245.0/255.0) < 0.02 && abs(b - 245.0/255.0) < 0.02 && a > 0.9\n                let isCodeBlockBg = abs(r - 250.0/255.0) < 0.02 && abs(g - 250.0/255.0) < 0.02 && abs(b - 250.0/255.0) < 0.02 && a > 0.9\n                if !isInlineCodeBg && !isCodeBlockBg { needsClean = true }\n            }\n            if needsClean {\n                var cleanAttrs = textView.typingAttributes\n                cleanAttrs.removeValue(forKey: .backgroundColor)\n                cleanAttrs.removeValue(forKey: MentionMarkerKey)\n                if let fg = cleanAttrs[.foregroundColor] as? UIColor {\n                    var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                    fg.getRed(&r, green: &g, blue: &b, alpha: &a)\n                    if abs(r - 104.0/255.0) < 0.05 && abs(g - 82.0/255.0) < 0.05 && abs(b - 214.0/255.0) < 0.05 && a > 0.9 {\n                        cleanAttrs[.foregroundColor] = UIColor.label\n                    }\n                }\n                // Only strip bold when cleanup was triggered by an actual mention marker.\n                // When triggered by a stray backgroundColor alone, bold is legitimate\n                // user formatting (e.g., cursor moved into bold text after backspace)\n                // and must be preserved.\n                if isMentionTriggered,\n                   let font = cleanAttrs[.font] as? UIFont,\n                   font.fontDescriptor.symbolicTraits.contains(.traitBold),\n                   !pendingStyles.contains(\"bold\") {\n                    let unbold = font.fontDescriptor.withSymbolicTraits(\n                        font.fontDescriptor.symbolicTraits.subtracting(.traitBold)\n                    ) ?? font.fontDescriptor\n                    cleanAttrs[.font] = UIFont(descriptor: unbold, size: font.pointSize)\n                }\n                textView.typingAttributes = cleanAttrs\n            }\n        }\n\n        // If deferredTypingAttrs was consumed for a non-Enter keystroke, clear it.\n        // Check: if the character before cursor is NOT a newline, the user typed a\n        // regular character and the deferred attrs have been applied — safe to clear.\n        if deferredTypingAttrs != nil {\n            let cursorPos = textView.selectedRange.location\n            let nsText = (textView.text ?? \"\") as NSString\n            let isNewline = cursorPos > 0 && cursorPos <= nsText.length && nsText.character(at: cursorPos - 1) == 10\n            if !isNewline {\n                deferredTypingAttrs = nil\n            }\n        }\n\n        updateContentSize()\n        sendContentChange()\n        // Emit correct active styles after all internal changes are done.\n        // textViewDidChangeSelection is suppressed during isInternalChange,\n        // so we must emit here to keep the toolbar in sync.\n        emitActiveStyles()\n        updateToolbarButtonStates()\n\n        // Schedule a deferred restore of typingAttributes after the current run loop\n        // completes. This catches async JS bridge callbacks (e.g. syncMentionRanges →\n        // setMentionRanges) that set textView.attributedText and reset typingAttributes\n        // after textDidChange returns.\n        if let deferred = deferredTypingAttrs {\n            DispatchQueue.main.async { [weak self] in\n                guard let self = self else { return }\n                self.textView.typingAttributes = deferred\n                self.emitActiveStyles()\n                self.updateToolbarButtonStates()\n            }\n        }\n    }\n\n    /// Triple-Enter exit for code block mode (mirrors Android checkTripleEnterExitCodeBlock).\n    /// When three consecutive newlines are detected at the cursor inside a code block,\n    /// remove the trailing newlines, insert one newline after the code block, and exit\n    /// code block mode so subsequent typing is plain text.\n    private func checkTripleEnterExitCodeBlock() {\n        guard let text = textView.text, !text.isEmpty else { return }\n        let cursorPos = textView.selectedRange.location\n        guard cursorPos >= 3, cursorPos <= text.count else { return }\n\n        let nsText = text as NSString\n        // Check last 3 characters are all newlines\n        guard nsText.character(at: cursorPos - 1) == 10,\n              nsText.character(at: cursorPos - 2) == 10,\n              nsText.character(at: cursorPos - 3) == 10 else { return }\n\n        // Check if we're inside a code block via pendingStyles or CodeBlockAttributeKey\n        let inCodeBlock: Bool\n        if pendingStyles.contains(\"codeBlock\") {\n            inCodeBlock = true\n        } else if cursorPos > 3, let attrText = textView.attributedText {\n            let checkPos = cursorPos - 4\n            if checkPos >= 0 && checkPos < attrText.length {\n                inCodeBlock = attrText.attribute(CodeBlockAttributeKey, at: checkPos, effectiveRange: nil) != nil\n            } else {\n                inCodeBlock = false\n            }\n        } else {\n            inCodeBlock = false\n        }\n        guard inCodeBlock else { return }\n\n        isInternalChange = true\n\n        let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n        // Remove the trailing 3 newlines\n        let newCursorPos = cursorPos - 3\n        mutable.deleteCharacters(in: NSRange(location: newCursorPos, length: 3))\n\n        // Insert a single newline after the code block content\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n        let plainAttrs: [NSAttributedString.Key: Any] = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label,\n            .paragraphStyle: paragraphStyle\n        ]\n        let newlineAttr = NSAttributedString(string: \"\\n\", attributes: plainAttrs)\n        mutable.insert(newlineAttr, at: newCursorPos)\n\n        // Remove CodeBlockAttributeKey from the inserted newline to prevent bleed\n        let freshLinePos = newCursorPos + 1\n        mutable.removeAttribute(CodeBlockAttributeKey, range: NSRange(location: newCursorPos, length: 1))\n\n        textView.attributedText = mutable\n        textView.selectedRange = NSRange(location: freshLinePos, length: 0)\n\n        // Exit code block mode — reset typingAttributes to plain\n        textView.typingAttributes = plainAttrs\n        pendingStyles.remove(\"codeBlock\")\n        explicitlyOffStyles.removeAll()\n        pendingStylesInsertPos = -1\n\n        isInternalChange = false\n\n        textView.setNeedsDisplay()\n        emitActiveStyles()\n        sendContentChange()\n    }\n\n    private func autoContinueListOnEnter() {\n        guard let text = textView.text, !text.isEmpty else { return }\n        let cursorPos = textView.selectedRange.location\n        guard cursorPos > 0, cursorPos <= text.count else { return }\n\n        // Code block guard: don't continue lists inside code blocks (matches Android)\n        if pendingStyles.contains(\"codeBlock\") { return }\n        if let attrText = textView.attributedText, cursorPos > 0 {\n            let checkPos = min(cursorPos - 1, attrText.length - 1)\n            if checkPos >= 0 {\n                let val = attrText.attribute(CodeBlockAttributeKey, at: checkPos, effectiveRange: nil)\n                if val != nil { return }\n            }\n        }\n\n        let nsText = text as NSString\n        guard nsText.character(at: cursorPos - 1) == 10 else {\n            return\n        } // '\\n'\n\n        if cursorPos >= 2 && nsText.character(at: cursorPos - 2) == 10 {\n            return\n        }\n\n        var prevLineStart = cursorPos - 2\n        while prevLineStart > 0 && nsText.character(at: prevLineStart - 1) != 10 {\n            prevLineStart -= 1\n        }\n        if prevLineStart < 0 { prevLineStart = 0 }\n\n        let prevLine = nsText.substring(with: NSRange(location: prevLineStart, length: cursorPos - 1 - prevLineStart))\n\n        let numberedRegex = try? NSRegularExpression(pattern: \"^(\\\\d+)\\\\.\\\\s\")\n        if let match = numberedRegex?.firstMatch(in: prevLine, range: NSRange(location: 0, length: prevLine.count)) {\n            let prefixStr = (prevLine as NSString).substring(with: match.range)\n            let content = String(prevLine.dropFirst(prefixStr.count))\n\n            if content.trimmingCharacters(in: .whitespaces).isEmpty {\n                isInternalChange = true\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: NSRange(location: prevLineStart, length: cursorPos - prevLineStart))\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: prevLineStart, length: 0)\n                isInternalChange = false\n                return\n            }\n\n            let numStr = (prevLine as NSString).substring(with: match.range(at: 1))\n            let nextNum = (Int(numStr) ?? 0) + 1\n            let prefix = \"\\(nextNum). \"\n\n            isInternalChange = true\n            let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n            let attrs = mutable.attributes(at: max(0, cursorPos - 2), effectiveRange: nil)\n            mutable.insert(NSAttributedString(string: prefix, attributes: attrs), at: cursorPos)\n            textView.attributedText = mutable\n            textView.selectedRange = NSRange(location: cursorPos + prefix.count, length: 0)\n            renumberNumberedLists()\n            // Restore typingAttributes AFTER renumberNumberedLists() because it may\n            // set textView.attributedText which resets typingAttributes to defaults.\n            textView.typingAttributes = attrs\n            // Store for deferred restore — JS bridge callbacks (syncMentionRanges)\n            // will reset typingAttributes again after this method returns.\n            deferredTypingAttrs = attrs\n            isInternalChange = false\n            return\n        }\n\n        if prevLine.hasPrefix(\"• \") {\n            let content = String(prevLine.dropFirst(2))\n            if content.trimmingCharacters(in: .whitespaces).isEmpty {\n                isInternalChange = true\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: NSRange(location: prevLineStart, length: cursorPos - prevLineStart))\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: prevLineStart, length: 0)\n                isInternalChange = false\n                return\n            }\n            isInternalChange = true\n            let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n            let attrs = mutable.attributes(at: max(0, cursorPos - 2), effectiveRange: nil)\n            mutable.insert(NSAttributedString(string: \"• \", attributes: attrs), at: cursorPos)\n            textView.attributedText = mutable\n            textView.selectedRange = NSRange(location: cursorPos + 2, length: 0)\n            textView.typingAttributes = attrs\n            deferredTypingAttrs = attrs\n            isInternalChange = false\n            return\n        }\n\n        if prevLine.hasPrefix(\"☐ \") || prevLine.hasPrefix(\"☑ \") {\n            let content = String(prevLine.dropFirst(2))\n            if content.trimmingCharacters(in: .whitespaces).isEmpty {\n                isInternalChange = true\n                let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n                mutable.deleteCharacters(in: NSRange(location: prevLineStart, length: cursorPos - prevLineStart))\n                textView.attributedText = mutable\n                textView.selectedRange = NSRange(location: prevLineStart, length: 0)\n                isInternalChange = false\n                return\n            }\n            isInternalChange = true\n            let mutable = NSMutableAttributedString(attributedString: textView.attributedText)\n            let attrs = mutable.attributes(at: max(0, cursorPos - 2), effectiveRange: nil)\n            mutable.insert(NSAttributedString(string: \"☐ \", attributes: attrs), at: cursorPos)\n            textView.attributedText = mutable\n            textView.selectedRange = NSRange(location: cursorPos + 2, length: 0)\n            textView.typingAttributes = attrs\n            deferredTypingAttrs = attrs\n            isInternalChange = false\n            return\n        }\n    }\n\n    private func renumberNumberedLists() {\n        guard let text = textView.text, !text.isEmpty else { return }\n        let lines = text.components(separatedBy: \"\\n\")\n        let numberedRegex = try? NSRegularExpression(pattern: \"^(\\\\d+)\\\\.\\\\s\")\n        let quoteNumberedRegex = try? NSRegularExpression(pattern: \"^▎ (\\\\d+)\\\\.\\\\s\")\n        let bulletRegex = try? NSRegularExpression(pattern: \"^(- |• |‧ |▎ - |▎ • |▎ ‧ )\")\n\n        var counter = 0\n        var replacements: [(range: NSRange, newPrefix: String)] = []\n        var offset = 0\n\n        for line in lines {\n            // Check for blockquote + numbered: \"▎ N. \"\n            if let quoteMatch = quoteNumberedRegex?.firstMatch(in: line, range: NSRange(location: 0, length: line.count)) {\n                counter += 1\n                let oldPrefix = (line as NSString).substring(with: quoteMatch.range)\n                let newPrefix = \"▎ \\(counter). \"\n                if oldPrefix != newPrefix {\n                    replacements.append((NSRange(location: offset, length: oldPrefix.count), newPrefix))\n                }\n            } else if let match = numberedRegex?.firstMatch(in: line, range: NSRange(location: 0, length: line.count)) {\n                counter += 1\n                let oldPrefix = (line as NSString).substring(with: match.range)\n                let newPrefix = \"\\(counter). \"\n                if oldPrefix != newPrefix {\n                    replacements.append((NSRange(location: offset, length: oldPrefix.count), newPrefix))\n                }\n            } else if bulletRegex?.firstMatch(in: line, range: NSRange(location: 0, length: line.count)) != nil {\n                // Bullet lines don't reset the counter — ordered numbering\n                // continues across bullet interruptions (Slack behavior).\n            } else {\n                counter = 0\n            }\n            offset += line.count + 1\n        }\n\n        if !replacements.isEmpty {\n            let cursorPos = textView.selectedRange\n\n            // Compute cursor shift caused by prefix length changes before the cursor\n            var cursorDelta = 0\n            for replacement in replacements {\n                if replacement.range.location + replacement.range.length <= cursorPos.location {\n                    cursorDelta += replacement.newPrefix.count - replacement.range.length\n                }\n            }\n\n            let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n            // Use plain visible attributes for replacement prefixes — reading attrs\n            // from the old position can inherit foregroundColor: .clear from ▎ chars,\n            // making the entire new prefix invisible.\n            let plainAttrs: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            for replacement in replacements.reversed() {\n                mutableAttrString.replaceCharacters(in: replacement.range, with: NSAttributedString(string: replacement.newPrefix, attributes: plainAttrs))\n            }\n            // Make all ▎ characters invisible after renumbering\n            let nsStr = mutableAttrString.string as NSString\n            for i in 0..<nsStr.length {\n                if nsStr.character(at: i) == 0x258E {\n                    mutableAttrString.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: i, length: 1))\n                }\n            }\n            isInternalChange = true\n            textView.attributedText = mutableAttrString\n            // Restore cursor position adjusted for any prefix length changes\n            let adjustedLocation = max(0, min(cursorPos.location + cursorDelta, mutableAttrString.length))\n            textView.selectedRange = NSRange(location: adjustedLocation, length: 0)\n            isInternalChange = false\n        }\n    }\n\n    private func applyNumberOfLines() {\n        if numberOfLines > 0 && !editable {\n            textView.textContainer.maximumNumberOfLines = numberOfLines\n            textView.textContainer.lineBreakMode = .byTruncatingTail\n            textView.isScrollEnabled = false\n        } else {\n            textView.textContainer.maximumNumberOfLines = 0\n            textView.textContainer.lineBreakMode = .byWordWrapping\n        }\n        updateContentSize()\n    }\n\n    /// Top padding needed when a code block is at the start of the document.\n    /// Matches Android's codeBlockEdgePaddingPx() behavior.\n    private func codeBlockTopPadding() -> CGFloat {\n        guard let attrText = textView.attributedText, attrText.length > 0 else { return 0 }\n        return attrText.attribute(CodeBlockAttributeKey, at: 0, effectiveRange: nil) != nil\n            ? codeBlockVerticalPadding : 0\n    }\n\n    /// Bottom padding needed when a code block is at the end of the document.\n    private func codeBlockBottomPadding() -> CGFloat {\n        guard let attrText = textView.attributedText, attrText.length > 0 else { return 0 }\n        return attrText.attribute(CodeBlockAttributeKey, at: attrText.length - 1, effectiveRange: nil) != nil\n            ? codeBlockVerticalPadding : 0\n    }\n\n    private func updateContentSize() {\n        // Adjust textContainerInset for code block edge padding so the cursor\n        // aligns properly within the code block container (matches Android).\n        let cbTopPad = codeBlockTopPadding()\n        let cbBottomPad = codeBlockBottomPadding()\n        let currentInset = textView.textContainerInset\n        if abs(currentInset.top - cbTopPad) > 0.5 || abs(currentInset.bottom - cbBottomPad) > 0.5 {\n            textView.textContainerInset = UIEdgeInsets(\n                top: cbTopPad,\n                left: currentInset.left,\n                bottom: cbBottomPad,\n                right: currentInset.right\n            )\n            // Keep placeholder aligned with text by matching textContainerInset.top\n            placeholderTopConstraint?.constant = cbTopPad + 4\n        }\n\n        let width = bounds.width > 0 ? bounds.width : UIScreen.main.bounds.width - 32\n        let fittingSize = textView.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))\n\n        let minHeight: CGFloat = 22\n        var newHeight = max(fittingSize.height, minHeight)\n\n        if maxHeight > 0 {\n            let shouldScroll = newHeight > maxHeight\n            textView.isScrollEnabled = shouldScroll\n            textView.alwaysBounceVertical = shouldScroll\n            textView.showsVerticalScrollIndicator = shouldScroll\n\n            newHeight = min(newHeight, maxHeight)\n        } else {\n            textView.isScrollEnabled = false\n            textView.alwaysBounceVertical = false\n            textView.showsVerticalScrollIndicator = false\n\n        }\n\n        calculatedHeight = newHeight\n\n        if abs(newHeight - lastReportedHeight) > 0.5 {\n            lastReportedHeight = newHeight\n            onSizeChange?([\"height\": newHeight])\n        }\n\n        invalidateIntrinsicContentSize()\n\n        // Scroll to keep cursor visible when content exceeds maxHeight\n        // (matches Android's scrollTo(0, cursorBottom - height + paddingBottom) behavior)\n        if textView.isScrollEnabled {\n            let cursorRange = textView.selectedRange\n            DispatchQueue.main.async { [weak self] in\n                self?.textView.scrollRangeToVisible(cursorRange)\n            }\n        }\n    }\n\n    override func layoutSubviews() {\n        super.layoutSubviews()\n        updateContentSize()\n    }\n\n    override var intrinsicContentSize: CGSize {\n        return CGSize(width: UIView.noIntrinsicMetric, height: calculatedHeight)\n    }\n\n    override func sizeThatFits(_ size: CGSize) -> CGSize {\n        let width = size.width > 0 ? size.width : bounds.width > 0 ? bounds.width : UIScreen.main.bounds.width - 32\n        let fittingSize = textView.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))\n        var height = max(fittingSize.height, 22)\n\n        if maxHeight > 0 {\n            height = min(height, maxHeight)\n        }\n\n        return CGSize(width: size.width, height: height)\n    }\n\n    private func applyVariantStyle() {\n        if variant == \"flat\" {\n            layer.borderWidth = 0\n            layer.cornerRadius = 0\n            bottomBorder.isHidden = false\n        } else if variant == \"plain\" {\n            layer.borderWidth = 0\n            layer.cornerRadius = 0\n            bottomBorder.isHidden = true\n        } else {\n            layer.borderColor = UIColor.separator.cgColor\n            layer.borderWidth = 1\n            layer.cornerRadius = 8\n            bottomBorder.isHidden = true\n        }\n    }\n\n    private func applyListIndentation() {\n        guard let text = textView.text, !text.isEmpty else { return }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n        let nsText = text as NSString\n\n        let font = UIFont.systemFont(ofSize: 16)\n        let bulletPrefix = \"• \"\n        let bulletWidth = (bulletPrefix as NSString).size(withAttributes: [.font: font]).width\n\n        let numberedPattern = \"^(\\\\d+)\\\\.\\\\s\"\n        let numberedRegex = try? NSRegularExpression(pattern: numberedPattern, options: [])\n        let quoteNumberedPattern = \"^▎ (\\\\d+)\\\\.\\\\s\"\n        let quoteNumberedRegex = try? NSRegularExpression(pattern: quoteNumberedPattern, options: [])\n\n        var lineStart = 0\n        while lineStart < text.count {\n            var lineEnd = lineStart\n            while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n                lineEnd += 1\n            }\n\n            let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n            let lineText = nsText.substring(with: lineRange)\n\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.alignment = .left\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n\n            if lineText.hasPrefix(\"▎ • \") {\n                // Combined blockquote + bullet\n                let prefixWidth = (\"▎ • \" as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n            } else if let qnMatch = quoteNumberedRegex?.firstMatch(in: lineText, range: NSRange(location: 0, length: lineText.count)) {\n                // Combined blockquote + numbered: \"▎ N. \"\n                let matchedPrefix = (lineText as NSString).substring(with: qnMatch.range)\n                let prefixWidth = (matchedPrefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n            } else if lineText.hasPrefix(\"▎ \") {\n                // Plain blockquote\n                let prefixWidth = (\"▎ \" as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n            } else if lineText.hasPrefix(\"• \") {\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = bulletWidth\n            } else if let match = numberedRegex?.firstMatch(in: lineText, range: NSRange(location: 0, length: lineText.count)) {\n                let matchedPrefix = (lineText as NSString).substring(with: match.range)\n                let prefixWidth = (matchedPrefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n            } else {\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = 0\n            }\n\n            mutableAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: lineRange)\n\n            // Ensure ▎ characters remain invisible\n            if lineText.hasPrefix(\"▎\") && lineRange.length > 0 {\n                mutableAttrString.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: lineStart, length: 1))\n            }\n\n            lineStart = lineEnd + 1\n        }\n\n        let selectedRange = textView.selectedRange\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = selectedRange\n        isInternalChange = false\n    }\n\n    private func saveToUndoStack() {\n        undoStack.append(textView.attributedText)\n        if undoStack.count > 50 {\n            undoStack.removeFirst()\n        }\n        redoStack.removeAll()\n    }\n\n    private func sendContentChange() {\n        let blocks = getBlocksArray()\n        var blocksJson = \"[]\"\n        if let jsonData = try? JSONSerialization.data(withJSONObject: blocks, options: []),\n           let jsonString = String(data: jsonData, encoding: .utf8) {\n            blocksJson = jsonString\n        }\n        // Strip ZWS (code block placeholder) from text sent to JS, matching\n        // Android's sendContentChangeWithDelta which does .replace(\"\\u200B\", \"\").\n        // JS works in \"clean\" coordinates; keeping ZWS causes setTextContent's\n        // diff algorithm to misalign and destroy code block attributes on mention insertion.\n        let cleanText = (textView.text ?? \"\").replacingOccurrences(of: \"\\u{200B}\", with: \"\")\n        onContentChange?([\n            \"text\": cleanText,\n            \"blocksJson\": blocksJson\n        ])\n    }\n\n    /// Returns sub-ranges of the given range that do NOT overlap with any mention.\n    /// Mirrors Android's `nonMentionRanges()` — formatting is applied only to these\n    /// segments so mention styling remains untouched (Req 6.1).\n    private func nonMentionRanges(in range: NSRange, attributedText: NSAttributedString) -> [NSRange] {\n        var mentionRanges: [NSRange] = []\n        attributedText.enumerateAttribute(MentionMarkerKey, in: range, options: []) { value, attrRange, _ in\n            if value != nil {\n                mentionRanges.append(attrRange)\n            }\n        }\n        if mentionRanges.isEmpty { return [range] }\n        mentionRanges.sort { $0.location < $1.location }\n\n        var result: [NSRange] = []\n        var cursor = range.location\n        for mr in mentionRanges {\n            if cursor < mr.location {\n                result.append(NSRange(location: cursor, length: mr.location - cursor))\n            }\n            cursor = mr.location + mr.length\n        }\n        let rangeEnd = range.location + range.length\n        if cursor < rangeEnd {\n            result.append(NSRange(location: cursor, length: rangeEnd - cursor))\n        }\n        return result\n    }\n\n    /// Apply a font trait (bold/italic) to the current selection only, without affecting\n    /// pendingStyles or typingAttributes. Used by context menu actions so subsequent\n    /// typing reverts to the pre-selection style (matching Android behavior where\n    /// mode.finish() dismisses the action mode and deselects text).\n    func applyStyleToSelectionOnly(trait: UIFontDescriptor.SymbolicTraits) {\n        let range = textView.selectedRange\n        guard range.length > 0 else { return }\n\n        // Save typing attributes before modification (these represent the pre-format state)\n        let savedAttrs = textView.typingAttributes\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        let segments = nonMentionRanges(in: range, attributedText: mutableAttrString)\n        guard !segments.isEmpty else { return }\n\n        var hasTrait = false\n        for seg in segments {\n            mutableAttrString.enumerateAttribute(.font, in: seg, options: []) { value, _, _ in\n                if let font = value as? UIFont {\n                    hasTrait = font.fontDescriptor.symbolicTraits.contains(trait)\n                }\n            }\n        }\n\n        for seg in segments {\n            mutableAttrString.enumerateAttribute(.font, in: seg, options: []) { value, attrRange, _ in\n                if let font = value as? UIFont {\n                    var newTraits = font.fontDescriptor.symbolicTraits\n                    if hasTrait { newTraits.remove(trait) } else { newTraits.insert(trait) }\n                    if let descriptor = font.fontDescriptor.withSymbolicTraits(newTraits) {\n                        let newFont = UIFont(descriptor: descriptor, size: font.pointSize)\n                        mutableAttrString.addAttribute(.font, value: newFont, range: attrRange)\n                    }\n                }\n            }\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        // Collapse selection to end of formatted range (matches Android mode.finish() behavior)\n        // This deselects the text so subsequent typing uses typingAttributes, not selection attributes\n        let cursorPos = range.location + range.length\n        textView.selectedRange = NSRange(location: cursorPos, length: 0)\n        // Restore typing attributes so subsequent typing is NOT formatted\n        textView.typingAttributes = savedAttrs\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n    }\n\n    /// Apply a simple attribute (underline/strikethrough) to the current selection only,\n    /// without affecting pendingStyles or typingAttributes. Collapses selection after\n    /// applying (matches Android mode.finish() behavior).\n    func applyAttributeToSelectionOnly(key: NSAttributedString.Key, value: Int) {\n        let range = textView.selectedRange\n        guard range.length > 0 else { return }\n\n        // Save typing attributes before modification (these represent the pre-format state)\n        let savedAttrs = textView.typingAttributes\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        let segments = nonMentionRanges(in: range, attributedText: mutableAttrString)\n        guard !segments.isEmpty else { return }\n\n        var hasAttribute = false\n        for seg in segments {\n            if let existing = mutableAttrString.attribute(key, at: seg.location, effectiveRange: nil) as? Int, existing != 0 {\n                hasAttribute = true\n            }\n        }\n\n        for seg in segments {\n            if hasAttribute {\n                mutableAttrString.removeAttribute(key, range: seg)\n            } else {\n                mutableAttrString.addAttribute(key, value: value, range: seg)\n            }\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        // Collapse selection to end of formatted range (matches Android mode.finish() behavior)\n        let cursorPos = range.location + range.length\n        textView.selectedRange = NSRange(location: cursorPos, length: 0)\n        // Restore typing attributes so subsequent typing is NOT formatted\n        textView.typingAttributes = savedAttrs\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n    }\n\n    func toggleBold() {\n        toggleStyle(key: .font, trait: .traitBold)\n    }\n\n    func toggleItalic() {\n        toggleStyle(key: .font, trait: .traitItalic)\n    }\n\n    func toggleUnderline() {\n        toggleAttribute(key: .underlineStyle, value: NSUnderlineStyle.single.rawValue)\n    }\n\n    func toggleStrikethrough() {\n        toggleAttribute(key: .strikethroughStyle, value: NSUnderlineStyle.single.rawValue)\n    }\n\n    func toggleCode() {\n        let range = textView.selectedRange\n\n        // No selection: toggle pending code style for type-ahead (matches Android)\n        if range.length == 0 {\n            let styleName = \"code\"\n            let attributedText = textView.attributedText ?? NSAttributedString()\n            let cursorPos = range.location\n            var isInsideStyle = false\n            if cursorPos > 0 && cursorPos <= attributedText.length {\n                if let font = attributedText.attribute(.font, at: cursorPos - 1, effectiveRange: nil) as? UIFont {\n                    isInsideStyle = font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace)\n                }\n            }\n\n            if isInsideStyle {\n                if explicitlyOffStyles.contains(styleName) {\n                    explicitlyOffStyles.remove(styleName)\n                } else {\n                    explicitlyOffStyles.insert(styleName)\n                    pendingStyles.remove(styleName)\n                }\n            } else {\n                if pendingStyles.contains(styleName) {\n                    pendingStyles.remove(styleName)\n                } else {\n                    pendingStyles.insert(styleName)\n                    explicitlyOffStyles.remove(styleName)\n                }\n            }\n            pendingStylesInsertPos = cursorPos\n            emitActiveStyles()\n            updateToolbarButtonStates()\n            applyPendingStylesToTypingAttributes()\n            return\n        }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        var hasMonospace = false\n        mutableAttrString.enumerateAttribute(.font, in: range, options: []) { value, _, _ in\n            if let font = value as? UIFont {\n                if font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                    hasMonospace = true\n                }\n            }\n        }\n\n        let monoFont = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)\n        let regularFont = UIFont.systemFont(ofSize: 16)\n\n        mutableAttrString.enumerateAttribute(.font, in: range, options: []) { value, attrRange, _ in\n            if hasMonospace {\n                // Removing code: restore to system font but preserve bold/italic traits\n                if let existingFont = value as? UIFont {\n                    let traits = existingFont.fontDescriptor.symbolicTraits\n                    var newFont = regularFont\n                    if traits.contains(.traitBold) && traits.contains(.traitItalic) {\n                        if let desc = regularFont.fontDescriptor.withSymbolicTraits([.traitBold, .traitItalic]) {\n                            newFont = UIFont(descriptor: desc, size: 16)\n                        }\n                    } else if traits.contains(.traitBold) {\n                        newFont = UIFont.boldSystemFont(ofSize: 16)\n                    } else if traits.contains(.traitItalic) {\n                        newFont = UIFont.italicSystemFont(ofSize: 16)\n                    }\n                    mutableAttrString.addAttribute(.font, value: newFont, range: attrRange)\n                } else {\n                    mutableAttrString.addAttribute(.font, value: regularFont, range: attrRange)\n                }\n            } else {\n                // Adding code: switch to monospace but preserve bold/italic traits\n                if let existingFont = value as? UIFont {\n                    let traits = existingFont.fontDescriptor.symbolicTraits\n                    var newFont = monoFont\n                    if traits.contains(.traitBold) && traits.contains(.traitItalic) {\n                        // Monospace bold italic\n                        if let desc = monoFont.fontDescriptor.withSymbolicTraits([.traitBold, .traitItalic, .traitMonoSpace]) {\n                            newFont = UIFont(descriptor: desc, size: 15)\n                        } else {\n                            // Fallback: at least try bold monospace\n                            newFont = UIFont.monospacedSystemFont(ofSize: 15, weight: .bold)\n                        }\n                    } else if traits.contains(.traitBold) {\n                        newFont = UIFont.monospacedSystemFont(ofSize: 15, weight: .bold)\n                    } else if traits.contains(.traitItalic) {\n                        if let desc = monoFont.fontDescriptor.withSymbolicTraits([.traitItalic, .traitMonoSpace]) {\n                            newFont = UIFont(descriptor: desc, size: 15)\n                        }\n                    }\n                    mutableAttrString.addAttribute(.font, value: newFont, range: attrRange)\n                } else {\n                    mutableAttrString.addAttribute(.font, value: monoFont, range: attrRange)\n                }\n            }\n        }\n\n        if !hasMonospace {\n            mutableAttrString.addAttribute(.foregroundColor, value: inlineCodeTextColor, range: range)\n            mutableAttrString.addAttribute(.backgroundColor, value: inlineCodeBgColor, range: range)\n        } else {\n            mutableAttrString.removeAttribute(.backgroundColor, range: range)\n            mutableAttrString.addAttribute(.foregroundColor, value: UIColor.label, range: range)\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n        updateToolbarButtonStates()\n    }\n\n    func toggleHighlight(color: String?) {\n        let range = textView.selectedRange\n        guard range.length > 0 else { return }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n        var hasHighlight = false\n\n        mutableAttrString.enumerateAttribute(.backgroundColor, in: range, options: []) { value, _, _ in\n            if let bgColor = value as? UIColor, bgColor != UIColor.systemGray5 {\n                hasHighlight = true\n            }\n        }\n\n        if hasHighlight {\n            mutableAttrString.removeAttribute(.backgroundColor, range: range)\n        } else {\n            let highlightColor = UIColor.yellow.withAlphaComponent(0.5)\n            mutableAttrString.addAttribute(.backgroundColor, value: highlightColor, range: range)\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func setHeading() {\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        var isHeading = false\n        if lineRange.length > 0 {\n            mutableAttrString.enumerateAttribute(.font, in: lineRange, options: []) { value, _, _ in\n                if let font = value as? UIFont, font.pointSize > 18 {\n                    isHeading = true\n                }\n            }\n        }\n\n        let headingFont = UIFont.boldSystemFont(ofSize: 24)\n        let regularFont = UIFont.systemFont(ofSize: 16)\n\n        mutableAttrString.addAttribute(.font, value: isHeading ? regularFont : headingFont, range: lineRange)\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func setQuote() {\n        // Mutual exclusivity: exit code block mode before applying quote (matches Android toggleQuote)\n        // Remove code block attributes inline rather than calling toggleCodeBlock() to avoid\n        // textView.attributedText reassignment mid-flow.\n        let qRange = textView.selectedRange\n        let qText = textView.text ?? \"\"\n        let qNsText = qText as NSString\n\n        var qLineStart = qRange.location\n        while qLineStart > 0 && qNsText.character(at: qLineStart - 1) != 10 { qLineStart -= 1 }\n        var qLineEnd = qRange.location + qRange.length\n        while qLineEnd < qText.count && qNsText.character(at: qLineEnd) != 10 { qLineEnd += 1 }\n\n        let qAttrText = textView.attributedText ?? NSAttributedString()\n        var qHasCodeBlock = false\n        if qLineStart < qLineEnd && qLineEnd <= qAttrText.length {\n            qAttrText.enumerateAttribute(CodeBlockAttributeKey, in: NSRange(location: qLineStart, length: qLineEnd - qLineStart), options: []) { value, _, _ in\n                if value != nil { qHasCodeBlock = true }\n            }\n        }\n\n        if qHasCodeBlock || pendingStyles.contains(\"codeBlock\") {\n            let mutablePre = NSMutableAttributedString(attributedString: qAttrText)\n            let qLineRange = NSRange(location: qLineStart, length: qLineEnd - qLineStart)\n            if qLineRange.length > 0 && qLineEnd <= mutablePre.length {\n                mutablePre.removeAttribute(CodeBlockAttributeKey, range: qLineRange)\n                mutablePre.enumerateAttribute(.font, in: qLineRange, options: []) { value, attrRange, _ in\n                    if let font = value as? UIFont, font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                        mutablePre.addAttribute(.font, value: UIFont.systemFont(ofSize: 16), range: attrRange)\n                    }\n                }\n                mutablePre.removeAttribute(.underlineStyle, range: qLineRange)\n                mutablePre.removeAttribute(.strikethroughStyle, range: qLineRange)\n                mutablePre.removeAttribute(.backgroundColor, range: qLineRange)\n                mutablePre.addAttribute(.foregroundColor, value: UIColor.label, range: qLineRange)\n\n                // Remove ZWS placeholder from empty-line code blocks\n                let lineStr = mutablePre.string as NSString\n                var zwsOffset = 0\n                for i in qLineStart..<qLineEnd {\n                    if lineStr.character(at: i - zwsOffset) == 0x200B {\n                        mutablePre.deleteCharacters(in: NSRange(location: i - zwsOffset, length: 1))\n                        zwsOffset += 1\n                    }\n                }\n            }\n            isInternalChange = true\n            textView.attributedText = mutablePre\n            textView.selectedRange = NSRange(location: qRange.location, length: 0)\n            isInternalChange = false\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.removeAll()\n            let plainParaStyle = NSMutableParagraphStyle()\n            plainParaStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            textView.typingAttributes = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: plainParaStyle\n            ]\n            textView.setNeedsDisplay()\n        }\n\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location + range.length\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n        let lineText = nsText.substring(with: lineRange)\n\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        // Check if line already has blockquote prefix (▎ followed by space)\n        if lineText.hasPrefix(\"▎ \") {\n            // Remove blockquote prefix\n            let deleteRange = NSRange(location: lineStart, length: 2)\n            mutableAttr.deleteCharacters(in: deleteRange)\n            // Ensure remaining text has visible foreground color (▎ uses .clear)\n            let newLineLength = lineEnd - lineStart - 2\n            if newLineLength > 0 {\n                mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: NSRange(location: lineStart, length: newLineLength))\n            }\n        } else {\n            // Add blockquote prefix with transparent ▎ character\n            let plainAttributes: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label\n            ]\n            var quoteCharAttrs = plainAttributes\n            quoteCharAttrs[.foregroundColor] = UIColor.clear // Make ▎ invisible\n            let quoteCharAttr = NSAttributedString(string: \"▎\", attributes: quoteCharAttrs)\n            let spaceAttr = NSAttributedString(string: \" \", attributes: plainAttributes)\n            let insertAttr = NSMutableAttributedString()\n            insertAttr.append(quoteCharAttr)\n            insertAttr.append(spaceAttr)\n            mutableAttr.insert(insertAttr, at: lineStart)\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        textView.selectedRange = NSRange(location: lineStart + (mutableAttr.string as NSString).length - (text as NSString).length + lineRange.length, length: 0)\n        isInternalChange = false\n        // Trigger redraw for blockquote bar asynchronously to avoid layout issues\n        DispatchQueue.main.async { [weak self] in\n            self?.textView.setNeedsDisplay()\n        }\n\n        applyListIndentation()\n        renumberNumberedLists()\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n    }\n\n    func setChecklist() {\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location + range.length\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n        let lineText = nsText.substring(with: lineRange)\n\n        let uncheckedPrefix = \"☐ \"\n        let checkedPrefix = \"☑ \"\n        var newText: String\n\n        if lineText.hasPrefix(uncheckedPrefix) || lineText.hasPrefix(checkedPrefix) {\n            // Remove checklist\n            newText = String(lineText.dropFirst(2))\n        } else {\n            // Add unchecked checkbox\n            newText = uncheckedPrefix + lineText\n        }\n\n        let mutableText = NSMutableString(string: text)\n        mutableText.replaceCharacters(in: lineRange, with: newText)\n\n        isInternalChange = true\n        textView.text = mutableText as String\n        textView.selectedRange = NSRange(location: lineStart + newText.count, length: 0)\n        isInternalChange = false\n\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func toggleChecklistItem() {\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n        let lineText = nsText.substring(with: lineRange)\n\n        let uncheckedPrefix = \"☐ \"\n        let checkedPrefix = \"☑ \"\n        var newText: String\n\n        if lineText.hasPrefix(uncheckedPrefix) {\n            // Check the item\n            newText = checkedPrefix + String(lineText.dropFirst(2))\n        } else if lineText.hasPrefix(checkedPrefix) {\n            // Uncheck the item\n            newText = uncheckedPrefix + String(lineText.dropFirst(2))\n        } else {\n            return\n        }\n\n        let mutableText = NSMutableString(string: text)\n        mutableText.replaceCharacters(in: lineRange, with: newText)\n\n        isInternalChange = true\n        textView.text = mutableText as String\n        textView.selectedRange = range\n        isInternalChange = false\n\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func setParagraph() {\n        // Reset to normal paragraph style\n        let range = textView.selectedRange\n        guard range.length > 0 else { return }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n        let plainAttributes: [NSAttributedString.Key: Any] = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label\n        ]\n\n        mutableAttrString.setAttributes(plainAttributes, range: range)\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func clearFormatting() {\n        // Use saved selection if current selection is empty\n        var range = textView.selectedRange\n        if range.length == 0 && savedSelectionRange.length > 0 {\n            range = savedSelectionRange\n        }\n        guard range.length > 0 else { return }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n        let plainText = (textView.text as NSString?)?.substring(with: range) ?? \"\"\n\n        let plainAttributes: [NSAttributedString.Key: Any] = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label\n        ]\n\n        mutableAttrString.replaceCharacters(in: range, with: NSAttributedString(string: plainText, attributes: plainAttributes))\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func indent() {\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        let indentString = \"    \" // 4 spaces\n        let mutableText = NSMutableString(string: text)\n        mutableText.insert(indentString, at: lineStart)\n\n        isInternalChange = true\n        textView.text = mutableText as String\n        textView.selectedRange = NSRange(location: range.location + indentString.count, length: range.length)\n        isInternalChange = false\n\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func outdent() {\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineText = nsText.substring(with: NSRange(location: lineStart, length: lineEnd - lineStart))\n        var charsToRemove = 0\n\n        if lineText.hasPrefix(\"    \") {\n            charsToRemove = 4\n        } else if lineText.hasPrefix(\"\\t\") {\n            charsToRemove = 1\n        } else {\n            // Remove up to 4 leading spaces\n            for char in lineText {\n                if char == \" \" && charsToRemove < 4 {\n                    charsToRemove += 1\n                } else {\n                    break\n                }\n            }\n        }\n\n        guard charsToRemove > 0 else { return }\n\n        let mutableText = NSMutableString(string: text)\n        mutableText.deleteCharacters(in: NSRange(location: lineStart, length: charsToRemove))\n\n        isInternalChange = true\n        textView.text = mutableText as String\n        let newLocation = max(lineStart, range.location - charsToRemove)\n        textView.selectedRange = NSRange(location: newLocation, length: range.length)\n        isInternalChange = false\n\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func setAlignment(_ alignment: NSTextAlignment) {\n        let range = textView.selectedRange\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var lineStart = range.location\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 {\n            lineStart -= 1\n        }\n\n        var lineEnd = range.location + range.length\n        while lineEnd < text.count && nsText.character(at: lineEnd) != 10 {\n            lineEnd += 1\n        }\n\n        let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.alignment = alignment\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n\n        mutableAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: lineRange)\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func promptInsertLink() {\n        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,\n              let viewController = windowScene.windows.first?.rootViewController else {\n            return\n        }\n\n        let alert = UIAlertController(title: \"Insert Link\", message: nil, preferredStyle: .alert)\n        alert.addTextField { textField in\n            textField.placeholder = \"Link text\"\n            if let selectedText = self.textView.text(in: self.textView.selectedTextRange ?? UITextRange()) {\n                textField.text = selectedText\n            }\n        }\n        alert.addTextField { textField in\n            textField.placeholder = \"URL\"\n            textField.keyboardType = .URL\n        }\n\n        alert.addAction(UIAlertAction(title: \"Cancel\", style: .cancel))\n        alert.addAction(UIAlertAction(title: \"Insert\", style: .default) { [weak self] _ in\n            guard let text = alert.textFields?[0].text,\n                  let url = alert.textFields?[1].text,\n                  !text.isEmpty, !url.isEmpty else { return }\n            self?.insertLink(url: url, text: text)\n        })\n\n        viewController.present(alert, animated: true)\n    }\n\n    private func toggleStyle(key: NSAttributedString.Key, trait: UIFontDescriptor.SymbolicTraits) {\n        // User explicitly toggling a style overrides any deferred restore from list continuation\n        deferredTypingAttrs = nil\n\n        let range = textView.selectedRange\n\n        // No selection: toggle pending style for type-ahead (matches Android)\n        if range.length == 0 {\n            let styleName: String\n            if trait == .traitBold { styleName = \"bold\" }\n            else if trait == .traitItalic { styleName = \"italic\" }\n            else { return }\n\n            let attributedText = textView.attributedText ?? NSAttributedString()\n            let cursorPos = range.location\n            var isInsideStyle = false\n            if cursorPos > 0 && cursorPos <= attributedText.length {\n                if let font = attributedText.attribute(.font, at: cursorPos - 1, effectiveRange: nil) as? UIFont {\n                    isInsideStyle = font.fontDescriptor.symbolicTraits.contains(trait)\n                }\n            }\n\n            if isInsideStyle {\n                if explicitlyOffStyles.contains(styleName) {\n                    explicitlyOffStyles.remove(styleName)\n                } else {\n                    explicitlyOffStyles.insert(styleName)\n                    pendingStyles.remove(styleName)\n                }\n            } else {\n                if pendingStyles.contains(styleName) {\n                    pendingStyles.remove(styleName)\n                } else {\n                    pendingStyles.insert(styleName)\n                    explicitlyOffStyles.remove(styleName)\n                }\n            }\n            pendingStylesInsertPos = cursorPos\n            emitActiveStyles()\n            updateToolbarButtonStates()\n            applyPendingStylesToTypingAttributes()\n            return\n        }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        let segments = nonMentionRanges(in: range, attributedText: mutableAttrString)\n        guard !segments.isEmpty else { return }\n\n        var hasTrait = false\n        for seg in segments {\n            mutableAttrString.enumerateAttribute(.font, in: seg, options: []) { value, _, _ in\n                if let font = value as? UIFont {\n                    hasTrait = font.fontDescriptor.symbolicTraits.contains(trait)\n                }\n            }\n        }\n\n        for seg in segments {\n            mutableAttrString.enumerateAttribute(.font, in: seg, options: []) { value, attrRange, _ in\n                if let font = value as? UIFont {\n                    var newTraits = font.fontDescriptor.symbolicTraits\n                    if hasTrait {\n                        newTraits.remove(trait)\n                    } else {\n                        newTraits.insert(trait)\n                    }\n                    if let descriptor = font.fontDescriptor.withSymbolicTraits(newTraits) {\n                        let newFont = UIFont(descriptor: descriptor, size: font.pointSize)\n                        mutableAttrString.addAttribute(.font, value: newFont, range: attrRange)\n                    }\n                }\n            }\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n        updateToolbarButtonStates()\n    }\n\n    private func toggleAttribute(key: NSAttributedString.Key, value: Int) {\n        // User explicitly toggling an attribute overrides any deferred restore from list continuation\n        deferredTypingAttrs = nil\n\n        let range = textView.selectedRange\n\n        // No selection: toggle pending style for type-ahead (matches Android)\n        if range.length == 0 {\n            let styleName: String\n            if key == .underlineStyle { styleName = \"underline\" }\n            else if key == .strikethroughStyle { styleName = \"strikethrough\" }\n            else { return }\n\n            let attributedText = textView.attributedText ?? NSAttributedString()\n            let cursorPos = range.location\n            var isInsideStyle = false\n            if cursorPos > 0 && cursorPos <= attributedText.length {\n                if let attrVal = attributedText.attribute(key, at: cursorPos - 1, effectiveRange: nil) as? Int, attrVal != 0 {\n                    isInsideStyle = true\n                }\n            }\n\n            if isInsideStyle {\n                if explicitlyOffStyles.contains(styleName) {\n                    explicitlyOffStyles.remove(styleName)\n                } else {\n                    explicitlyOffStyles.insert(styleName)\n                    pendingStyles.remove(styleName)\n                }\n            } else {\n                if pendingStyles.contains(styleName) {\n                    pendingStyles.remove(styleName)\n                } else {\n                    pendingStyles.insert(styleName)\n                    explicitlyOffStyles.remove(styleName)\n                }\n            }\n            pendingStylesInsertPos = cursorPos\n            emitActiveStyles()\n            updateToolbarButtonStates()\n            applyPendingStylesToTypingAttributes()\n            return\n        }\n\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        // Skip mention ranges — apply formatting only to non-mention text (Req 6.1)\n        let segments = nonMentionRanges(in: range, attributedText: mutableAttrString)\n        guard !segments.isEmpty else { return }\n\n        var hasAttribute = false\n        for seg in segments {\n            mutableAttrString.enumerateAttribute(key, in: seg, options: []) { attrValue, _, _ in\n                if attrValue != nil {\n                    hasAttribute = true\n                }\n            }\n        }\n\n        for seg in segments {\n            if hasAttribute {\n                mutableAttrString.removeAttribute(key, range: seg)\n            } else {\n                mutableAttrString.addAttribute(key, value: value, range: seg)\n            }\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n        textView.selectedRange = range\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n        updateToolbarButtonStates()\n    }\n\n    /// Applies pending style toggles to typingAttributes so newly typed text inherits them.\n    /// Matches Android's pendingStyles mechanism via iOS's native typingAttributes API.\n    private func applyPendingStylesToTypingAttributes() {\n        var attrs = textView.typingAttributes\n        var baseFont = attrs[.font] as? UIFont ?? UIFont.systemFont(ofSize: 16)\n        var traits = baseFont.fontDescriptor.symbolicTraits\n\n        // Bold\n        if pendingStyles.contains(\"bold\") { traits.insert(.traitBold) }\n        if explicitlyOffStyles.contains(\"bold\") { traits.remove(.traitBold) }\n        // Italic\n        if pendingStyles.contains(\"italic\") { traits.insert(.traitItalic) }\n        if explicitlyOffStyles.contains(\"italic\") { traits.remove(.traitItalic) }\n\n        // Code (monospace) — switches base font family\n        if pendingStyles.contains(\"code\") || pendingStyles.contains(\"codeBlock\") {\n            let fontSize: CGFloat = pendingStyles.contains(\"codeBlock\") ? 16 : 15\n            baseFont = UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)\n            traits = baseFont.fontDescriptor.symbolicTraits\n            // Re-apply bold/italic on mono font\n            if pendingStyles.contains(\"bold\") || (attrs[.font] as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitBold) == true {\n                if !explicitlyOffStyles.contains(\"bold\") { traits.insert(.traitBold) }\n            }\n            if pendingStyles.contains(\"italic\") || (attrs[.font] as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitItalic) == true {\n                if !explicitlyOffStyles.contains(\"italic\") { traits.insert(.traitItalic) }\n            }\n            // Apply code visual styling (only inline code gets colored bg/fg)\n            if pendingStyles.contains(\"code\") && !pendingStyles.contains(\"codeBlock\") {\n                attrs[.foregroundColor] = inlineCodeTextColor\n                attrs[.backgroundColor] = inlineCodeBgColor\n            } else {\n                attrs[.foregroundColor] = UIColor.label\n                attrs.removeValue(forKey: .backgroundColor)\n                attrs[CodeBlockAttributeKey] = true\n            }\n        } else if explicitlyOffStyles.contains(\"code\") {\n            if baseFont.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                baseFont = UIFont.systemFont(ofSize: 16)\n                traits = baseFont.fontDescriptor.symbolicTraits\n                if (attrs[.font] as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitBold) == true && !explicitlyOffStyles.contains(\"bold\") {\n                    traits.insert(.traitBold)\n                }\n                if (attrs[.font] as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitItalic) == true && !explicitlyOffStyles.contains(\"italic\") {\n                    traits.insert(.traitItalic)\n                }\n            }\n            // Remove code visual styling\n            attrs.removeValue(forKey: .backgroundColor)\n            attrs[.foregroundColor] = UIColor.label\n        }\n\n        if let descriptor = baseFont.fontDescriptor.withSymbolicTraits(traits) {\n            attrs[.font] = UIFont(descriptor: descriptor, size: baseFont.pointSize)\n        }\n\n        // Underline\n        if pendingStyles.contains(\"underline\") {\n            attrs[.underlineStyle] = NSUnderlineStyle.single.rawValue\n        } else if explicitlyOffStyles.contains(\"underline\") {\n            attrs.removeValue(forKey: .underlineStyle)\n        }\n\n        // Strikethrough\n        if pendingStyles.contains(\"strikethrough\") {\n            attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue\n        } else if explicitlyOffStyles.contains(\"strikethrough\") {\n            attrs.removeValue(forKey: .strikethroughStyle)\n        }\n\n        textView.typingAttributes = attrs\n    }\n\n    func toggleBulletList() {\n        toggleListStyle(bullet: true)\n    }\n\n    func toggleNumberedList() {\n        toggleListStyle(bullet: false)\n    }\n\n    private func toggleListStyle(bullet: Bool) {\n        // Mutual exclusivity: exit code block mode before applying list (matches Android toggleListPrefix)\n        // Remove code block attributes inline rather than calling toggleCodeBlock() to avoid\n        // textView.attributedText reassignment mid-flow.\n        let range = textView.selectedRange\n        let currentText = textView.text ?? \"\"\n        let currentNsText = currentText as NSString\n\n        var checkStart = range.location\n        while checkStart > 0 && currentNsText.character(at: checkStart - 1) != 10 { checkStart -= 1 }\n        var checkEnd = range.location + range.length\n        while checkEnd < currentText.count && currentNsText.character(at: checkEnd) != 10 { checkEnd += 1 }\n\n        let preAttrText = textView.attributedText ?? NSAttributedString()\n        var hasCodeBlockAttr = false\n        if checkStart < checkEnd && checkEnd <= preAttrText.length {\n            preAttrText.enumerateAttribute(CodeBlockAttributeKey, in: NSRange(location: checkStart, length: checkEnd - checkStart), options: []) { value, _, _ in\n                if value != nil { hasCodeBlockAttr = true }\n            }\n        }\n\n        if hasCodeBlockAttr || pendingStyles.contains(\"codeBlock\") {\n            // Inline code block removal (same as Android: remove spans directly)\n            let mutablePre = NSMutableAttributedString(attributedString: preAttrText)\n            let lineRange = NSRange(location: checkStart, length: checkEnd - checkStart)\n            if lineRange.length > 0 && checkEnd <= mutablePre.length {\n                mutablePre.removeAttribute(CodeBlockAttributeKey, range: lineRange)\n                mutablePre.enumerateAttribute(.font, in: lineRange, options: []) { value, attrRange, _ in\n                    if let font = value as? UIFont, font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                        mutablePre.addAttribute(.font, value: UIFont.systemFont(ofSize: 16), range: attrRange)\n                    }\n                }\n                mutablePre.removeAttribute(.underlineStyle, range: lineRange)\n                mutablePre.removeAttribute(.strikethroughStyle, range: lineRange)\n                mutablePre.removeAttribute(.backgroundColor, range: lineRange)\n                mutablePre.addAttribute(.foregroundColor, value: UIColor.label, range: lineRange)\n\n                // Remove ZWS placeholder that toggleCodeBlock inserts for empty-line code blocks.\n                // Without this, the ZWS remains as invisible content and can confuse list logic.\n                let lineStr = mutablePre.string as NSString\n                var zwsOffset = 0\n                for i in checkStart..<checkEnd {\n                    if lineStr.character(at: i - zwsOffset) == 0x200B {\n                        mutablePre.deleteCharacters(in: NSRange(location: i - zwsOffset, length: 1))\n                        zwsOffset += 1\n                    }\n                }\n            }\n            isInternalChange = true\n            textView.attributedText = mutablePre\n            textView.selectedRange = NSRange(location: max(0, range.location - (checkEnd - checkStart > 0 ? 0 : 0)), length: 0)\n            isInternalChange = false\n            pendingStyles.remove(\"codeBlock\")\n            pendingStyles.remove(\"bold\")\n            pendingStyles.remove(\"italic\")\n            pendingStyles.remove(\"underline\")\n            pendingStyles.remove(\"strikethrough\")\n            pendingStyles.remove(\"code\")\n            explicitlyOffStyles.removeAll()\n            // Reset typingAttributes to plain so list prefix doesn't inherit code block styling\n            let plainParaStyle = NSMutableParagraphStyle()\n            plainParaStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            textView.typingAttributes = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: plainParaStyle\n            ]\n            textView.setNeedsDisplay()\n        }\n\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n\n        var selectionStart = range.location\n        var selectionEnd = range.location + range.length\n\n        while selectionStart > 0 && nsText.character(at: selectionStart - 1) != 10 {\n            selectionStart -= 1\n        }\n        while selectionEnd < text.count && nsText.character(at: selectionEnd) != 10 {\n            selectionEnd += 1\n        }\n\n        let selectedText = nsText.substring(with: NSRange(location: selectionStart, length: selectionEnd - selectionStart))\n        let lines = selectedText.components(separatedBy: \"\\n\")\n\n        let bulletPrefix = \"• \"\n        let numberedRegex = try? NSRegularExpression(pattern: \"^\\\\d+\\\\.\\\\s\")\n\n        // Check if all non-empty lines already have this prefix (accounting for optional blockquote)\n        let hasNonEmptyLines = lines.contains { !$0.trimmingCharacters(in: .whitespaces).isEmpty }\n        let allHavePrefix: Bool\n        if bullet {\n            allHavePrefix = hasNonEmptyLines && lines.allSatisfy { line in\n                if line.trimmingCharacters(in: .whitespaces).isEmpty { return true }\n                let content = line.hasPrefix(\"▎ \") ? String(line.dropFirst(2)) : line\n                return content.hasPrefix(bulletPrefix)\n            }\n        } else {\n            allHavePrefix = hasNonEmptyLines && lines.allSatisfy { line in\n                if line.trimmingCharacters(in: .whitespaces).isEmpty { return true }\n                let content = line.hasPrefix(\"▎ \") ? String(line.dropFirst(2)) : line\n                return numberedRegex?.firstMatch(in: content, range: NSRange(location: 0, length: content.count)) != nil\n            }\n        }\n\n        // Instead of replacing the entire text range with a plain NSAttributedString\n        // (which destroys bold/italic/underline/strikethrough on existing content),\n        // manipulate the mutableAttr directly: delete old prefixes and insert new ones\n        // while preserving the attributes on the actual text content.\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        let originalLength = mutableAttr.length\n        let plainAttributes: [NSAttributedString.Key: Any] = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label\n        ]\n\n        // Process lines in reverse order so earlier offsets remain valid\n        var lineOffset = selectionStart\n        var lineOffsets: [(start: Int, line: String)] = []\n        for line in lines {\n            lineOffsets.append((start: lineOffset, line: line))\n            lineOffset += line.count + 1 // +1 for \\n\n        }\n\n        for (index, entry) in lineOffsets.enumerated().reversed() {\n            let lineStart = entry.start\n            let line = entry.line\n\n            if line.trimmingCharacters(in: .whitespaces).isEmpty && allHavePrefix {\n                continue\n            }\n\n            let hasQuote = line.hasPrefix(\"▎ \")\n            let quoteOffset = hasQuote ? 2 : 0\n            let lineContent = hasQuote ? String(line.dropFirst(2)) : line\n\n            if allHavePrefix {\n                // Removing prefix — delete the prefix characters after the optional blockquote\n                if bullet && lineContent.hasPrefix(bulletPrefix) {\n                    let deleteStart = lineStart + quoteOffset\n                    mutableAttr.deleteCharacters(in: NSRange(location: deleteStart, length: bulletPrefix.count))\n                } else if !bullet, let match = numberedRegex?.firstMatch(in: lineContent, range: NSRange(location: 0, length: lineContent.count)) {\n                    let deleteStart = lineStart + quoteOffset\n                    mutableAttr.deleteCharacters(in: NSRange(location: deleteStart, length: match.range.length))\n                }\n            } else {\n                // Adding prefix — first strip any existing list prefix, then insert new one\n                let contentStart = lineStart + quoteOffset\n                var stripLength = 0\n                if lineContent.hasPrefix(bulletPrefix) {\n                    stripLength = bulletPrefix.count\n                } else if lineContent.hasPrefix(\"☐ \") || lineContent.hasPrefix(\"☑ \") {\n                    stripLength = 2\n                } else if let match = numberedRegex?.firstMatch(in: lineContent, range: NSRange(location: 0, length: lineContent.count)) {\n                    stripLength = match.range.length\n                }\n\n                if stripLength > 0 {\n                    mutableAttr.deleteCharacters(in: NSRange(location: contentStart, length: stripLength))\n                }\n\n                // Insert new prefix with plain attributes (prefix should not inherit bold/italic)\n                let newPrefix: String\n                if bullet {\n                    newPrefix = bulletPrefix\n                } else {\n                    newPrefix = \"\\(index + 1). \"\n                }\n                let prefixAttr = NSAttributedString(string: newPrefix, attributes: plainAttributes)\n                mutableAttr.insert(prefixAttr, at: contentStart)\n            }\n        }\n\n        // Make all ▎ characters invisible in the affected range\n        let newNsText = mutableAttr.string as NSString\n        let lengthDelta = mutableAttr.length - originalLength\n        let newSelEnd = selectionEnd + lengthDelta\n        var searchPos = selectionStart\n        while searchPos < newSelEnd && searchPos < mutableAttr.length {\n            if newNsText.character(at: searchPos) == 0x258E { // ▎ character\n                mutableAttr.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: searchPos, length: 1))\n            }\n            searchPos += 1\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        textView.selectedRange = NSRange(location: newSelEnd, length: 0)\n        isInternalChange = false\n\n        // Restore pending inline styles (bold, italic, underline, strikethrough) that\n        // were active before the list toggle. The attributedText assignment above resets\n        // typingAttributes to plain, wiping any styles the user had toggled on.\n        applyPendingStylesToTypingAttributes()\n\n        placeholderLabel.isHidden = !textView.text.isEmpty\n\n        applyListIndentation()\n        renumberNumberedLists()\n\n        // Restore again after applyListIndentation/renumberNumberedLists which also\n        // set textView.attributedText and wipe typingAttributes.\n        applyPendingStylesToTypingAttributes()\n        deferredTypingAttrs = textView.typingAttributes\n\n        // Re-check placeholder after all post-processing (applyListIndentation /\n        // renumberNumberedLists may set textView.attributedText which can cause\n        // UIKit to re-evaluate placeholder visibility on some iOS versions).\n        placeholderLabel.isHidden = !textView.text.isEmpty\n\n        DispatchQueue.main.async { [weak self] in\n            // Final deferred placeholder check — covers UIKit edge cases where\n            // attributedText setter schedules a layout pass that re-shows the label.\n            guard let self = self else { return }\n            let hasContent = self.textView.attributedText.length > 0\n            self.placeholderLabel.isHidden = hasContent\n            self.textView.setNeedsDisplay()\n        }\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n    }\n\n    func setContent(blocks: [[String: Any]]) {\n        let attributedString = NSMutableAttributedString()\n        let font = UIFont.systemFont(ofSize: 16)\n\n        var numberedIndex = 1\n        for (blockIndex, block) in blocks.enumerated() {\n            guard let text = block[\"text\"] as? String else { continue }\n            let blockType = block[\"type\"] as? String ?? \"paragraph\"\n\n            var displayText = text\n            var prefixLength = 0\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.alignment = .left\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n\n            switch blockType {\n            case \"bullet\":\n                let bulletPrefix = \"• \"\n                displayText = bulletPrefix + text\n                prefixLength = 2\n                let bulletWidth = (bulletPrefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = bulletWidth\n            case \"numbered\":\n                let prefix = \"\\(numberedIndex). \"\n                displayText = prefix + text\n                prefixLength = prefix.count\n                let prefixWidth = (prefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n                numberedIndex += 1\n            case \"quote\":\n                let quotePrefix = \"▎ \"\n                displayText = quotePrefix + text\n                prefixLength = 2\n                let prefixWidth = (quotePrefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n            case \"quoteBullet\":\n                let prefix = \"▎ • \"\n                displayText = prefix + text\n                prefixLength = 4\n                let prefixWidth = (prefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n            case \"quoteNumbered\":\n                let prefix = \"▎ \\(numberedIndex). \"\n                displayText = prefix + text\n                prefixLength = prefix.count\n                let prefixWidth = (prefix as NSString).size(withAttributes: [.font: font]).width\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = prefixWidth\n                numberedIndex += 1\n            case \"codeBlock\":\n                displayText = text\n                prefixLength = 0\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = 0\n            default:\n                paragraphStyle.firstLineHeadIndent = 0\n                paragraphStyle.headIndent = 0\n                numberedIndex = 1\n            }\n\n            let blockFont = blockType == \"codeBlock\" ? UIFont.monospacedSystemFont(ofSize: 16, weight: .regular) : font\n            let blockAttrString = NSMutableAttributedString(string: displayText, attributes: [\n                .font: blockFont,\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle\n            ])\n\n            // Apply code block marker attribute for the entire line\n            if blockType == \"codeBlock\" {\n                blockAttrString.addAttribute(CodeBlockAttributeKey, value: true, range: NSRange(location: 0, length: blockAttrString.length))\n            }\n\n            // Make ▎ character invisible for quote block types — bar is custom-drawn\n            if blockType == \"quote\" || blockType == \"quoteBullet\" || blockType == \"quoteNumbered\" {\n                blockAttrString.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: 0, length: 1))\n            }\n\n            if let styles = block[\"styles\"] as? [[String: Any]] {\n                for style in styles {\n                    guard let start = style[\"start\"] as? Int,\n                          let end = style[\"end\"] as? Int,\n                          let styleType = style[\"style\"] as? String,\n                          start < end && end <= text.count else { continue }\n\n                    let range = NSRange(location: start + prefixLength, length: end - start)\n\n                    switch styleType {\n                    case \"bold\":\n                        // Read existing font and add bold trait (preserves italic if already set)\n                        blockAttrString.enumerateAttribute(.font, in: range, options: []) { value, subRange, _ in\n                            let existingFont = (value as? UIFont) ?? font\n                            var traits = existingFont.fontDescriptor.symbolicTraits\n                            traits.insert(.traitBold)\n                            if let desc = existingFont.fontDescriptor.withSymbolicTraits(traits) {\n                                blockAttrString.addAttribute(.font, value: UIFont(descriptor: desc, size: existingFont.pointSize), range: subRange)\n                            }\n                        }\n                    case \"italic\":\n                        // Read existing font and add italic trait (preserves bold if already set)\n                        blockAttrString.enumerateAttribute(.font, in: range, options: []) { value, subRange, _ in\n                            let existingFont = (value as? UIFont) ?? font\n                            var traits = existingFont.fontDescriptor.symbolicTraits\n                            traits.insert(.traitItalic)\n                            if let desc = existingFont.fontDescriptor.withSymbolicTraits(traits) {\n                                blockAttrString.addAttribute(.font, value: UIFont(descriptor: desc, size: existingFont.pointSize), range: subRange)\n                            }\n                        }\n                    case \"underline\":\n                        blockAttrString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)\n                    case \"strikethrough\":\n                        blockAttrString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: range)\n                    case \"code\":\n                        let monoFont = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)\n                        blockAttrString.addAttribute(.font, value: monoFont, range: range)\n                        blockAttrString.addAttribute(.foregroundColor, value: inlineCodeTextColor, range: range)\n                        blockAttrString.addAttribute(.backgroundColor, value: inlineCodeBgColor, range: range)\n                    case \"link\":\n                        if let url = style[\"url\"] as? String, !url.isEmpty {\n                            let normalizedUrl = normalizeURL(url)\n                            blockAttrString.addAttribute(LinkURLAttributeKey, value: normalizedUrl, range: range)\n                            blockAttrString.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: range)\n                        }\n                    default:\n                        break\n                    }\n                }\n            }\n\n            if blockIndex < blocks.count - 1 {\n                var newlineAttrs: [NSAttributedString.Key: Any] = [\n                    .font: font,\n                    .foregroundColor: UIColor.label,\n                    .paragraphStyle: paragraphStyle\n                ]\n                // Carry CodeBlockAttributeKey on the newline between consecutive\n                // codeBlock lines so getBlocksArray() fallback detects empty lines\n                // inside code blocks correctly during edit round-trips.\n                if blockType == \"codeBlock\" {\n                    newlineAttrs[CodeBlockAttributeKey] = true\n                }\n                blockAttrString.append(NSAttributedString(string: \"\\n\", attributes: newlineAttrs))\n            }\n            attributedString.append(blockAttrString)\n        }\n\n        isInternalChange = true\n        textView.attributedText = attributedString\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        isInternalChange = false\n        applyListIndentation()\n\n        let endPosition = textView.text?.count ?? 0\n\n        // Detect if cursor lands inside a code block and update pendingStyles\n        // BEFORE setting selectedRange, because selectedRange triggers\n        // textViewDidChangeSelection which calls emitActiveStyles().\n        if endPosition > 0, let attrText = textView.attributedText {\n            let checkPos = min(endPosition - 1, attrText.length - 1)\n            if checkPos >= 0 {\n                let hasCodeBlockAttr = attrText.attribute(CodeBlockAttributeKey, at: checkPos, effectiveRange: nil) != nil\n                if hasCodeBlockAttr {\n                    pendingStyles.insert(\"codeBlock\")\n                    pendingStylesInsertPos = endPosition\n                    let cbParaStyle = NSMutableParagraphStyle()\n                    cbParaStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                    textView.typingAttributes = [\n                        .font: UIFont.monospacedSystemFont(ofSize: 16, weight: .regular),\n                        .foregroundColor: UIColor.label,\n                        .paragraphStyle: cbParaStyle,\n                        CodeBlockAttributeKey: true\n                    ]\n                }\n            }\n        }\n\n        textView.selectedRange = NSRange(location: endPosition, length: 0)\n\n        // Force redraw so code block borders/backgrounds render immediately\n        textView.setNeedsDisplay()\n        sendContentChange()\n\n        DispatchQueue.main.async { [weak self] in\n            self?.textView.scrollRangeToVisible(NSRange(location: endPosition, length: 0))\n            self?.updateContentSize()\n        }\n    }\n\n    func getText() -> String {\n        return textView.text ?? \"\"\n    }\n\n    func getBlocksArray() -> [[String: Any]] {\n        let text = textView.text ?? \"\"\n        let attributedText = textView.attributedText ?? NSAttributedString()\n\n        let lines = text.components(separatedBy: \"\\n\")\n        var blocks: [[String: Any]] = []\n        var currentIndex = 0\n        let numberedPattern = \"^(\\\\d+)\\\\.\\\\s\"\n        let numberedRegex = try? NSRegularExpression(pattern: numberedPattern, options: [])\n        let quoteNumberedPattern = \"^▎ (\\\\d+)\\\\.\\\\s\"\n        let quoteNumberedRegex = try? NSRegularExpression(pattern: quoteNumberedPattern, options: [])\n\n        for line in lines {\n            var blockType = \"paragraph\"\n            var displayText = line\n\n            // Combined blockquote + list (must check before individual prefixes)\n            if line.hasPrefix(\"▎ • \") {\n                blockType = \"quoteBullet\"\n                displayText = String(line.dropFirst(4))\n            } else if let qnMatch = quoteNumberedRegex?.firstMatch(in: line, range: NSRange(location: 0, length: line.count)) {\n                blockType = \"quoteNumbered\"\n                displayText = String(line.dropFirst(qnMatch.range.length))\n            } else if line.hasPrefix(\"• \") {\n                blockType = \"bullet\"\n                displayText = String(line.dropFirst(2))\n            } else if let match = numberedRegex?.firstMatch(in: line, range: NSRange(location: 0, length: line.count)) {\n                blockType = \"numbered\"\n                displayText = String(line.dropFirst(match.range.length))\n            } else if line.hasPrefix(\"▎ \") {\n                blockType = \"quote\"\n                displayText = String(line.dropFirst(2))\n            }\n\n            // Detect code block: check the line range for CodeBlockAttributeKey\n            let lineRange = NSRange(location: currentIndex, length: line.count)\n            if lineRange.location + lineRange.length <= attributedText.length && lineRange.length > 0 {\n                var hasCodeBlockAttr = false\n                attributedText.enumerateAttribute(CodeBlockAttributeKey, in: lineRange, options: []) { value, _, _ in\n                    if value != nil { hasCodeBlockAttr = true }\n                }\n                if hasCodeBlockAttr {\n                    blockType = \"codeBlock\"\n                }\n            }\n            // Fallback: empty lines inside a code block — check preceding newline\n            if blockType != \"codeBlock\" && line.isEmpty && currentIndex > 0 {\n                let prevIdx = currentIndex - 1\n                if prevIdx < attributedText.length {\n                    if attributedText.attribute(CodeBlockAttributeKey, at: prevIdx, effectiveRange: nil) != nil {\n                        blockType = \"codeBlock\"\n                    }\n                }\n            }\n\n            // Strip ZWS from display text (code block placeholder)\n            displayText = displayText.replacingOccurrences(of: \"\\u{200B}\", with: \"\")\n\n            var styles: [[String: Any]] = []\n            let prefixLength = line.count - displayText.count\n            let styleRangeStart = currentIndex + prefixLength\n            let styleRangeLength = displayText.count\n            let styleLineRange = NSRange(location: styleRangeStart, length: styleRangeLength)\n\n            if styleLineRange.location + styleLineRange.length <= attributedText.length {\n                attributedText.enumerateAttributes(in: styleLineRange, options: []) { attrs, range, _ in\n                    let relativeStart = range.location - styleRangeStart\n                    let relativeEnd = relativeStart + range.length\n\n                    if let font = attrs[.font] as? UIFont {\n                        let traits = font.fontDescriptor.symbolicTraits\n                        if traits.contains(.traitBold) {\n                            styles.append([\"style\": \"bold\", \"start\": relativeStart, \"end\": relativeEnd])\n                        }\n                        if traits.contains(.traitItalic) {\n                            styles.append([\"style\": \"italic\", \"start\": relativeStart, \"end\": relativeEnd])\n                        }\n                        if traits.contains(.traitMonoSpace) {\n                            styles.append([\"style\": \"code\", \"start\": relativeStart, \"end\": relativeEnd])\n                        }\n                    }\n                    // Detect link attribute and emit with URL (matching Android URLSpan handling)\n                    let hasLink = attrs[LinkURLAttributeKey] != nil\n                    if hasLink {\n                        var urlString = \"\"\n                        if let url = attrs[LinkURLAttributeKey] as? URL {\n                            urlString = url.absoluteString\n                        } else if let str = attrs[LinkURLAttributeKey] as? String {\n                            urlString = str\n                        }\n                        if !urlString.isEmpty {\n                            styles.append([\"style\": \"link\", \"start\": relativeStart, \"end\": relativeEnd, \"url\": urlString])\n                        }\n                    }\n                    // Skip underline if co-located with a link (links have underline by default)\n                    if attrs[.underlineStyle] != nil && !hasLink {\n                        styles.append([\"style\": \"underline\", \"start\": relativeStart, \"end\": relativeEnd])\n                    }\n                    if attrs[.strikethroughStyle] != nil {\n                        styles.append([\"style\": \"strikethrough\", \"start\": relativeStart, \"end\": relativeEnd])\n                    }\n                }\n            }\n\n            blocks.append([\n                \"type\": blockType,\n                \"text\": displayText,\n                \"styles\": styles\n            ])\n\n            currentIndex += line.count + 1\n        }\n\n        return blocks\n    }\n\n    func clear() {\n        isInternalChange = true\n        textView.text = \"\"\n        textView.attributedText = NSAttributedString()\n        placeholderLabel.isHidden = false\n        isInternalChange = false\n        pendingStyles.removeAll()\n        explicitlyOffStyles.removeAll()\n        pendingStylesInsertPos = -1\n        // Reset typingAttributes to plain so next typed text has no formatting\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n        textView.typingAttributes = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label,\n            .paragraphStyle: paragraphStyle\n        ]\n        sendContentChange()\n        // Emit all-false active styles so JS toolbar resets\n        emitActiveStyles()\n    }\n\n    func focus() {\n        textView.becomeFirstResponder()\n    }\n\n    func blur() {\n        textView.resignFirstResponder()\n    }\n\n    func insertLink(url: String, text: String) {\n        let normalizedUrl = normalizeURL(url)\n        let range = textView.selectedRange\n        let mutableAttrString = NSMutableAttributedString(attributedString: textView.attributedText)\n\n        let linkAttrString = NSAttributedString(string: text, attributes: [\n            LinkURLAttributeKey: normalizedUrl,\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.systemBlue\n        ])\n\n        if range.length > 0 {\n            mutableAttrString.replaceCharacters(in: range, with: linkAttrString)\n        } else {\n            mutableAttrString.insert(linkAttrString, at: range.location)\n        }\n\n        isInternalChange = true\n        textView.attributedText = mutableAttrString\n\n        // Place cursor after the inserted link text\n        let newCursorPos = range.location + text.count\n        textView.selectedRange = NSRange(location: newCursorPos, length: 0)\n\n        // Reset typing attributes to default so text typed after the link\n        // is not underlined / styled as a link\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.alignment = .left\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n        textView.typingAttributes = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label,\n            .paragraphStyle: paragraphStyle\n        ]\n\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n        updateToolbarButtonStates()\n        emitActiveStyles()\n    }\n\n    func undo() {\n        guard undoStack.count > 1 else { return }\n\n        let current = undoStack.removeLast()\n        redoStack.append(current)\n\n        if let previous = undoStack.last {\n            isInternalChange = true\n            textView.attributedText = previous\n            placeholderLabel.isHidden = !textView.text.isEmpty\n            isInternalChange = false\n            sendContentChange()\n        }\n    }\n\n    func redo() {\n        guard let next = redoStack.popLast() else { return }\n\n        undoStack.append(next)\n        isInternalChange = true\n        textView.attributedText = next\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        isInternalChange = false\n        sendContentChange()\n    }\n\n    // MARK: - Missing methods required by ViewManager\n\n    func toggleCodeBlock() {\n        let text = textView.text ?? \"\"\n        let nsText = text as NSString\n        let cursorPos = textView.selectedRange.location\n\n        // Find current line boundaries\n        var lineStart = min(cursorPos, nsText.length)\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 { lineStart -= 1 }\n        var lineEnd = min(max(cursorPos, textView.selectedRange.location + textView.selectedRange.length), nsText.length)\n        while lineEnd < nsText.length && nsText.character(at: lineEnd) != 10 { lineEnd += 1 }\n\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText ?? NSAttributedString())\n\n        // Mutual exclusivity: remove blockquote prefix if present\n        let lineText = lineStart < lineEnd ? nsText.substring(with: NSRange(location: lineStart, length: lineEnd - lineStart)) : \"\"\n        let quotePrefix = \"▎ \"\n        if lineText.hasPrefix(quotePrefix) {\n            isInternalChange = true\n            mutableAttr.deleteCharacters(in: NSRange(location: lineStart, length: quotePrefix.count))\n            textView.attributedText = mutableAttr\n            isInternalChange = false\n            lineEnd -= quotePrefix.count\n        }\n\n        // Mutual exclusivity: remove list prefixes (bullet, numbered, checklist)\n        // Re-read line text after possible quote removal\n        let updatedLineText = lineStart < lineEnd ? (textView.text as NSString).substring(with: NSRange(location: lineStart, length: lineEnd - lineStart)) : \"\"\n        if updatedLineText.hasPrefix(\"• \") {\n            isInternalChange = true\n            mutableAttr.deleteCharacters(in: NSRange(location: lineStart, length: 2))\n            textView.attributedText = mutableAttr\n            isInternalChange = false\n            lineEnd -= 2\n        } else if updatedLineText.hasPrefix(\"☐ \") || updatedLineText.hasPrefix(\"☑ \") {\n            isInternalChange = true\n            mutableAttr.deleteCharacters(in: NSRange(location: lineStart, length: 2))\n            textView.attributedText = mutableAttr\n            isInternalChange = false\n            lineEnd -= 2\n        } else if let numberedMatch = try? NSRegularExpression(pattern: \"^\\\\d+\\\\.\\\\s\", options: []).firstMatch(in: updatedLineText, range: NSRange(location: 0, length: updatedLineText.count)) {\n            isInternalChange = true\n            mutableAttr.deleteCharacters(in: NSRange(location: lineStart, length: numberedMatch.range.length))\n            textView.attributedText = mutableAttr\n            isInternalChange = false\n            lineEnd -= numberedMatch.range.length\n        }\n\n        // Empty line: insert ZWS with code block marker for immediate container\n        if lineStart >= lineEnd {\n            isInternalChange = true\n            let zws = \"\\u{200B}\"\n            let insertPos = min(textView.selectedRange.location, mutableAttr.length)\n            let monoFont = UIFont.monospacedSystemFont(ofSize: 16, weight: .regular)\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            let zwsAttr = NSAttributedString(string: zws, attributes: [\n                .font: monoFont,\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle,\n                CodeBlockAttributeKey: true\n            ])\n            mutableAttr.insert(zwsAttr, at: insertPos)\n            textView.attributedText = mutableAttr\n            textView.selectedRange = NSRange(location: insertPos + 1, length: 0)\n            textView.typingAttributes = [\n                .font: monoFont,\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle,\n                CodeBlockAttributeKey: true\n            ]\n            // Track code block as a pending style so it survives textDidChange clearing\n            pendingStyles.insert(\"codeBlock\")\n            pendingStylesInsertPos = textView.selectedRange.location\n            isInternalChange = false\n            textView.setNeedsDisplay()\n            updateContentSize()\n            saveToUndoStack()\n            sendContentChange()\n            emitActiveStyles()\n            return\n        }\n\n        // Non-empty line: check if already has code block attribute\n        let lineRange = NSRange(location: lineStart, length: lineEnd - lineStart)\n        var hasCodeBlock = false\n        mutableAttr.enumerateAttribute(CodeBlockAttributeKey, in: lineRange, options: []) { value, _, _ in\n            if value != nil { hasCodeBlock = true }\n        }\n\n        isInternalChange = true\n        if hasCodeBlock {\n            // Deactivating code block\n            mutableAttr.removeAttribute(CodeBlockAttributeKey, range: lineRange)\n            mutableAttr.enumerateAttribute(.font, in: lineRange, options: []) { value, attrRange, _ in\n                if let font = value as? UIFont, font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                    mutableAttr.addAttribute(.font, value: UIFont.systemFont(ofSize: 16), range: attrRange)\n                }\n            }\n            mutableAttr.removeAttribute(.underlineStyle, range: lineRange)\n            mutableAttr.removeAttribute(.strikethroughStyle, range: lineRange)\n            mutableAttr.removeAttribute(.backgroundColor, range: lineRange)\n            mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: lineRange)\n\n            // Remove ZWS placeholder that was inserted for empty-line code blocks\n            let lineStr = mutableAttr.attributedSubstring(from: lineRange).string\n            if lineStr == \"\\u{200B}\" || lineStr.allSatisfy({ $0 == \"\\u{200B}\" }) {\n                mutableAttr.deleteCharacters(in: lineRange)\n                lineEnd = lineStart\n            }\n\n            // Clear code block from pendingStyles and reset typingAttributes\n            pendingStyles.remove(\"codeBlock\")\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            textView.typingAttributes = [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle\n            ]\n        } else {\n            // Activating code block\n            let monoFont = UIFont.monospacedSystemFont(ofSize: 16, weight: .regular)\n            mutableAttr.addAttribute(.font, value: monoFont, range: lineRange)\n            mutableAttr.removeAttribute(.backgroundColor, range: lineRange)\n            mutableAttr.removeAttribute(.underlineStyle, range: lineRange)\n            mutableAttr.removeAttribute(.strikethroughStyle, range: lineRange)\n            mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: lineRange)\n            mutableAttr.addAttribute(CodeBlockAttributeKey, value: true, range: lineRange)\n            // Strip mention markers so setMentionRanges() won't re-apply mention styling\n            mutableAttr.removeAttribute(MentionMarkerKey, range: lineRange)\n            // Set pendingStyles + typingAttributes so newly typed characters\n            // inherit code block mode (matches empty-line activation path)\n            pendingStyles.insert(\"codeBlock\")\n            pendingStylesInsertPos = lineEnd\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            textView.typingAttributes = [\n                .font: monoFont,\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle,\n                CodeBlockAttributeKey: true\n            ]\n        }\n\n        textView.attributedText = mutableAttr\n        textView.selectedRange = NSRange(location: lineEnd, length: 0)\n        isInternalChange = false\n        textView.setNeedsDisplay()\n        updateContentSize()\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n    }\n\n    func setTextContent(_ text: String, force: Bool = false) {\n        let currentText = textView.text ?? \"\"\n        // Compare clean texts (ZWS stripped) — JS sends clean text while native\n        // has ZWS markers for code blocks. Raw comparison would never match.\n        let cleanCurrent = currentText.replacingOccurrences(of: \"\\u{200B}\", with: \"\")\n        let cleanNew = text.replacingOccurrences(of: \"\\u{200B}\", with: \"\")\n        if cleanCurrent == cleanNew { return }\n\n        isInternalChange = true\n\n        // For force updates (mentions, clear, edit), do a targeted replacement\n        // that preserves attributes (code block, inline code, etc.) on unchanged\n        // regions. Matches Android's span-preserving editable.replace() approach.\n        if force, let attrText = textView.attributedText, attrText.length > 0 {\n            // Build clean-to-raw index mapping (strip ZWS)\n            let rawChars = Array(currentText)\n            var cleanChars: [Character] = []\n            var cleanToRaw: [Int] = []\n            for (rawIdx, ch) in rawChars.enumerated() {\n                if ch != \"\\u{200B}\" {\n                    cleanToRaw.append(rawIdx)\n                    cleanChars.append(ch)\n                }\n            }\n            cleanToRaw.append(currentText.count) // sentinel\n\n            let newChars = Array(cleanNew)\n            let cleanLen = cleanChars.count\n            let newLen = newChars.count\n            let minLen = min(cleanLen, newLen)\n\n            // Find common prefix\n            var prefixLen = 0\n            while prefixLen < minLen && cleanChars[prefixLen] == newChars[prefixLen] {\n                prefixLen += 1\n            }\n\n            // Find common suffix (never overlap with prefix)\n            var suffixLen = 0\n            let maxSuffix = minLen - prefixLen\n            while suffixLen < maxSuffix &&\n                  cleanChars[cleanLen - 1 - suffixLen] == newChars[newLen - 1 - suffixLen] {\n                suffixLen += 1\n            }\n\n            // Map clean-coordinate range back to raw-coordinate range\n            let rawReplaceStart = cleanToRaw[prefixLen]\n            let rawReplaceEnd = (cleanLen - suffixLen < cleanToRaw.count)\n                ? cleanToRaw[cleanLen - suffixLen]\n                : currentText.count\n            let replacement = String(newChars[prefixLen ..< (newLen - suffixLen)])\n\n            // Snapshot code block regions BEFORE replacement so we can verify\n            // they survive the edit. Regions are stored as (rawStart, rawEnd).\n            var codeBlockRegions: [(Int, Int)] = []\n            attrText.enumerateAttribute(CodeBlockAttributeKey, in: NSRange(location: 0, length: attrText.length), options: []) { value, range, _ in\n                if value != nil {\n                    codeBlockRegions.append((range.location, range.location + range.length))\n                }\n            }\n\n            let mutableAttr = NSMutableAttributedString(attributedString: attrText)\n            let replaceRange = NSRange(location: rawReplaceStart, length: rawReplaceEnd - rawReplaceStart)\n\n            // Build replacement attributed string with default attributes\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n            let replacementAttr = NSAttributedString(string: replacement, attributes: [\n                .font: UIFont.systemFont(ofSize: 16),\n                .foregroundColor: UIColor.label,\n                .paragraphStyle: paragraphStyle\n            ])\n            mutableAttr.replaceCharacters(in: replaceRange, with: replacementAttr)\n\n            // Restore CodeBlockAttributeKey on regions that were NOT part of the\n            // replacement. The replacement shifts positions after rawReplaceStart\n            // by (replacement.count - replaceRange.length).\n            let delta = replacement.count - replaceRange.length\n            for (cbStart, cbEnd) in codeBlockRegions {\n                // Region entirely before replacement — unchanged\n                if cbEnd <= rawReplaceStart {\n                    let r = NSRange(location: cbStart, length: cbEnd - cbStart)\n                    if r.location + r.length <= mutableAttr.length {\n                        mutableAttr.addAttribute(CodeBlockAttributeKey, value: true, range: r)\n                        // Ensure monospace font on code block region\n                        mutableAttr.enumerateAttribute(.font, in: r, options: []) { val, fontRange, _ in\n                            if let font = val as? UIFont, !font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                                mutableAttr.addAttribute(.font, value: UIFont.monospacedSystemFont(ofSize: font.pointSize, weight: .regular), range: fontRange)\n                            }\n                        }\n                    }\n                }\n                // Region entirely after replacement — shifted by delta\n                else if cbStart >= rawReplaceEnd {\n                    let newStart = cbStart + delta\n                    let newEnd = cbEnd + delta\n                    let r = NSRange(location: newStart, length: newEnd - newStart)\n                    if r.location >= 0 && r.location + r.length <= mutableAttr.length {\n                        mutableAttr.addAttribute(CodeBlockAttributeKey, value: true, range: r)\n                        mutableAttr.enumerateAttribute(.font, in: r, options: []) { val, fontRange, _ in\n                            if let font = val as? UIFont, !font.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) {\n                                mutableAttr.addAttribute(.font, value: UIFont.monospacedSystemFont(ofSize: font.pointSize, weight: .regular), range: fontRange)\n                            }\n                        }\n                    }\n                }\n                // Region partially overlaps replacement — restore the non-overlapping part\n                else {\n                    if cbStart < rawReplaceStart {\n                        let r = NSRange(location: cbStart, length: rawReplaceStart - cbStart)\n                        if r.location + r.length <= mutableAttr.length {\n                            mutableAttr.addAttribute(CodeBlockAttributeKey, value: true, range: r)\n                        }\n                    }\n                    if cbEnd > rawReplaceEnd {\n                        let newStart = rawReplaceStart + replacement.count\n                        let newEnd = cbEnd + delta\n                        if newStart < newEnd && newEnd <= mutableAttr.length {\n                            let r = NSRange(location: newStart, length: newEnd - newStart)\n                            mutableAttr.addAttribute(CodeBlockAttributeKey, value: true, range: r)\n                        }\n                    }\n                }\n            }\n\n            textView.attributedText = mutableAttr\n            // Force redraw so code block containers render after attributedText swap\n            textView.setNeedsDisplay()\n\n            let newPosition = min(rawReplaceStart + replacement.count, mutableAttr.length)\n            textView.selectedRange = NSRange(location: newPosition, length: 0)\n        } else {\n            textView.text = text\n        }\n\n        placeholderLabel.isHidden = !text.isEmpty\n        isInternalChange = false\n        updateContentSize()\n        if force {\n            sendContentChange()\n        }\n    }\n\n    func setSelectionRange(start: Int, end: Int) {\n        // Map clean coordinates (no \\u{200B}) to raw text positions,\n        // matching Android's setSelectionRange ZWS mapping.\n        let rawText = textView.text ?? \"\"\n        var cleanToRaw: [Int] = []\n        for (rawIdx, ch) in rawText.enumerated() {\n            if ch != \"\\u{200B}\" {\n                cleanToRaw.append(rawIdx)\n            }\n        }\n        cleanToRaw.append(rawText.count) // sentinel\n\n        let rawStart = (start >= 0 && start < cleanToRaw.count) ? cleanToRaw[start] : rawText.count\n        let rawEnd = (end >= 0 && end < cleanToRaw.count) ? cleanToRaw[end] : rawText.count\n        let clampedStart = max(0, min(rawStart, rawText.count))\n        let clampedEnd = max(clampedStart, min(rawEnd, rawText.count))\n\n        if let startPos = textView.position(from: textView.beginningOfDocument, offset: clampedStart),\n           let endPos = textView.position(from: textView.beginningOfDocument, offset: clampedEnd) {\n            textView.selectedTextRange = textView.textRange(from: startPos, to: endPos)\n        }\n    }\n\n    func setMentionRanges(_ ranges: [[String: Int]]) {\n        // Apply mention styling to the specified ranges\n        guard let text = textView.text, !text.isEmpty else { return }\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        let fullRange = NSRange(location: 0, length: mutableAttr.length)\n\n        // Clear old mention styling (bold, foreground, background) using MentionMarkerKey\n        mutableAttr.enumerateAttribute(MentionMarkerKey, in: fullRange, options: []) { value, range, _ in\n            guard value != nil else { return }\n            mutableAttr.removeAttribute(MentionMarkerKey, range: range)\n            mutableAttr.removeAttribute(.backgroundColor, range: range)\n            // Restore default foreground color and un-bold the font\n            mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: range)\n            mutableAttr.enumerateAttribute(.font, in: range, options: []) { fontVal, fontRange, _ in\n                if let font = fontVal as? UIFont, font.fontDescriptor.symbolicTraits.contains(.traitBold) {\n                    let unboldDescriptor = font.fontDescriptor.withSymbolicTraits(font.fontDescriptor.symbolicTraits.subtracting(.traitBold)) ?? font.fontDescriptor\n                    mutableAttr.addAttribute(.font, value: UIFont(descriptor: unboldDescriptor, size: font.pointSize), range: fontRange)\n                }\n            }\n        }\n\n        // Remove orphaned mention backgrounds that lost their MentionMarkerKey\n        // (e.g. when toggleCodeBlock strips the marker). Preserve inline code\n        // and code block backgrounds by checking color components.\n        mutableAttr.enumerateAttribute(.backgroundColor, in: fullRange, options: []) { value, range, _ in\n            if let bgColor = value as? UIColor {\n                var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n                bgColor.getRed(&r, green: &g, blue: &b, alpha: &a)\n                // Mention bg: rgba(104/255, 82/255, 214/255, 0.15) — low alpha purple\n                let isMentionBg = abs(r - 104.0/255.0) < 0.05 && abs(g - 82.0/255.0) < 0.05 && abs(b - 214.0/255.0) < 0.05 && a < 0.3\n                if isMentionBg {\n                    mutableAttr.removeAttribute(.backgroundColor, range: range)\n                }\n            }\n        }\n\n        // Reset all foreground colors to default, then re-apply link and inline code colors\n        mutableAttr.removeAttribute(.foregroundColor, range: fullRange)\n        mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: fullRange)\n\n        // Re-apply invisible foreground to blockquote ▎ characters (U+258E)\n        // so the custom-drawn grey bar in draw(_ rect:) is the only visible bar.\n        let mentionStr = mutableAttr.string as NSString\n        for i in 0..<mentionStr.length {\n            if mentionStr.character(at: i) == 0x258E {\n                mutableAttr.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: i, length: 1))\n            }\n        }\n\n        // Re-apply link foreground color (systemBlue) to ranges with LinkURLAttributeKey\n        // so that links remain visually blue after the blanket reset above.\n        mutableAttr.enumerateAttribute(LinkURLAttributeKey, in: fullRange, options: []) { value, range, _ in\n            if value != nil {\n                mutableAttr.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: range)\n            }\n        }\n\n        // Re-apply inline code text color to ranges with inline code background\n        mutableAttr.enumerateAttribute(.backgroundColor, in: fullRange, options: []) { value, range, _ in\n            if let bgColor = value as? UIColor, bgColor == inlineCodeBgColor {\n                mutableAttr.addAttribute(.foregroundColor, value: inlineCodeTextColor, range: range)\n            }\n        }\n        \n        // Build clean-to-raw index mapping (JS sends ranges in clean coordinates\n        // with ZWS stripped, matching Android's setMentionRanges approach).\n        let rawText = mutableAttr.string\n        var cleanToRaw: [Int] = []\n        for (rawIdx, ch) in rawText.enumerated() {\n            if ch != \"​\" {\n                cleanToRaw.append(rawIdx)\n            }\n        }\n        cleanToRaw.append(rawText.count) // sentinel for end-of-string\n\n        // Apply mention styling to each range (matches Android: bold + purple + light purple bg)\n        let mentionColor = UIColor(red: 104/255, green: 82/255, blue: 214/255, alpha: 1.0)\n        let mentionBgColor = UIColor(red: 104/255, green: 82/255, blue: 214/255, alpha: 0.15)\n        for range in ranges {\n            if let cleanStart = range[\"start\"], let cleanEnd = range[\"end\"] {\n                // Map clean coordinates to raw coordinates\n                guard cleanStart >= 0, cleanEnd > cleanStart,\n                      cleanStart < cleanToRaw.count, cleanEnd < cleanToRaw.count else { continue }\n                let rawStart = cleanToRaw[cleanStart]\n                let rawEnd = cleanToRaw[cleanEnd]\n                let nsRange = NSRange(location: rawStart, length: rawEnd - rawStart)\n                if nsRange.location + nsRange.length <= mutableAttr.length {\n                    // Skip mention styling inside code blocks — plain monospace only (matches Android Req 19.1)\n                    var insideCodeBlock = false\n                    mutableAttr.enumerateAttribute(CodeBlockAttributeKey, in: nsRange, options: []) { value, _, stop in\n                        if value != nil { insideCodeBlock = true; stop.pointee = true }\n                    }\n                    if insideCodeBlock { continue }\n                    mutableAttr.addAttribute(MentionMarkerKey, value: true, range: nsRange)\n                    mutableAttr.addAttribute(.foregroundColor, value: mentionColor, range: nsRange)\n                    mutableAttr.addAttribute(.backgroundColor, value: mentionBgColor, range: nsRange)\n                    // Apply bold by adding bold trait to existing font\n                    mutableAttr.enumerateAttribute(.font, in: nsRange, options: []) { value, fontRange, _ in\n                        if let font = value as? UIFont {\n                            let boldDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor\n                            let boldFont = UIFont(descriptor: boldDescriptor, size: font.pointSize)\n                            mutableAttr.addAttribute(.font, value: boldFont, range: fontRange)\n                        }\n                    }\n                }\n            }\n        }\n        \n        isInternalChange = true\n        let savedTypingAttrs = textView.typingAttributes\n        textView.attributedText = mutableAttr\n        textView.typingAttributes = savedTypingAttrs\n        // If a deferred restore is pending (from autoContinueListOnEnter),\n        // use those attrs instead — they carry the user's active formatting.\n        if let deferred = deferredTypingAttrs {\n            textView.typingAttributes = deferred\n        }\n\n        // Strip mention-specific attributes from typingAttributes so newly typed\n        // text after a deleted mention doesn't inherit bold/purple/background.\n        // Use component comparison (not UIColor ==) to avoid color space mismatch.\n        var cleanAttrs = textView.typingAttributes\n        // Check if cursor is actually adjacent to a mention marker\n        var cursorAdjacentToMention = false\n        let cursorPos = textView.selectedRange.location\n        if cursorPos > 0, cursorPos <= mutableAttr.length {\n            let checkIdx = min(cursorPos - 1, mutableAttr.length - 1)\n            if mutableAttr.attribute(MentionMarkerKey, at: checkIdx, effectiveRange: nil) != nil {\n                cursorAdjacentToMention = true\n            }\n        }\n        // Remove ANY backgroundColor that isn't inline code or code block\n        if let bg = cleanAttrs[.backgroundColor] as? UIColor {\n            var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n            bg.getRed(&r, green: &g, blue: &b, alpha: &a)\n            let isInlineCodeBg = abs(r - 245.0/255.0) < 0.02 && abs(g - 245.0/255.0) < 0.02 && abs(b - 245.0/255.0) < 0.02 && a > 0.9\n            let isCodeBlockBg = abs(r - 250.0/255.0) < 0.02 && abs(g - 250.0/255.0) < 0.02 && abs(b - 250.0/255.0) < 0.02 && a > 0.9\n            if !isInlineCodeBg && !isCodeBlockBg {\n                cleanAttrs.removeValue(forKey: .backgroundColor)\n            }\n        }\n        // Remove mention foreground color — use component comparison\n        if let fg = cleanAttrs[.foregroundColor] as? UIColor {\n            var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0\n            fg.getRed(&r, green: &g, blue: &b, alpha: &a)\n            if abs(r - 104.0/255.0) < 0.05 && abs(g - 82.0/255.0) < 0.05 && abs(b - 214.0/255.0) < 0.05 && a > 0.9 {\n                cleanAttrs[.foregroundColor] = UIColor.label\n            }\n        }\n        // Only strip bold when cursor is adjacent to an actual mention.\n        // Otherwise bold is legitimate user formatting and must be preserved.\n        if cursorAdjacentToMention,\n           let font = cleanAttrs[.font] as? UIFont,\n           font.fontDescriptor.symbolicTraits.contains(.traitBold),\n           !pendingStyles.contains(\"bold\") {\n            let unbold = font.fontDescriptor.withSymbolicTraits(font.fontDescriptor.symbolicTraits.subtracting(.traitBold)) ?? font.fontDescriptor\n            cleanAttrs[.font] = UIFont(descriptor: unbold, size: font.pointSize)\n        }\n        // Remove mention marker key from typing attributes\n        cleanAttrs.removeValue(forKey: MentionMarkerKey)\n        textView.typingAttributes = cleanAttrs\n\n        isInternalChange = false\n\n        // Trigger redraw so code block containers render correctly after\n        // attributedText replacement (draw(_ rect:) uses CodeBlockAttributeKey).\n        textView.setNeedsDisplay()\n\n        // Recalculate height — mention styling (bold font) can cause text to\n        // wrap differently, requiring the composer to expand or contract.\n        updateContentSize()\n    }\n\n    // MARK: - URL Helpers (matches Android isURL / URL normalization)\n\n    func isURL(_ string: String) -> Bool {\n        let trimmed = string.trimmingCharacters(in: .whitespaces)\n        if trimmed.isEmpty || trimmed.contains(\" \") { return false }\n        if let comps = URLComponents(string: trimmed), comps.scheme != nil, comps.host != nil {\n            return true\n        }\n        if trimmed.contains(\".\") {\n            if let comps = URLComponents(string: \"https://\\(trimmed)\"), comps.host != nil {\n                return true\n            }\n        }\n        return false\n    }\n\n    private func normalizeURL(_ url: String) -> String {\n        let trimmed = url.trimmingCharacters(in: .whitespaces)\n        let lowered = trimmed.lowercased()\n        if lowered.hasPrefix(\"http://\") || lowered.hasPrefix(\"https://\") || lowered.hasPrefix(\"mailto:\") || lowered.hasPrefix(\"tel:\") {\n            return trimmed\n        }\n        return \"https://\\(trimmed)\"\n    }\n\n    /// Detects inline markdown shortcuts (e.g., `text`, *bold*, **bold**, ~~strike~~, <u>underline</u>, _italic_)\n    /// ending at the cursor position and converts them to live rich text formatting.\n    /// Called from textDidChange(), matches Android detectMarkdownShortcut().\n    private func detectInlineMarkdownShortcuts() {\n        let text = textView.text ?? \"\"\n        let cursorPos = textView.selectedRange.location\n        guard cursorPos > 0, cursorPos <= text.count else { return }\n\n        let nsText = text as NSString\n\n        // Pre-check: detect if there is an unmatched opening ``` in the text\n        // before the cursor. If so, suppress single-backtick inline code matching\n        // entirely — the user is in the middle of typing a triple-backtick code\n        // block and we must wait for the closing ``` to complete.\n        let textBeforeCursor = nsText.substring(to: cursorPos)\n        let hasUnmatchedTripleBacktick: Bool = {\n            var count = 0\n            var i = textBeforeCursor.startIndex\n            while i < textBeforeCursor.endIndex {\n                if textBeforeCursor[i] == \"`\" {\n                    let remaining = textBeforeCursor.distance(from: i, to: textBeforeCursor.endIndex)\n                    if remaining >= 3 {\n                        let next1 = textBeforeCursor.index(after: i)\n                        let next2 = textBeforeCursor.index(after: next1)\n                        if textBeforeCursor[next1] == \"`\" && textBeforeCursor[next2] == \"`\" {\n                            count += 1\n                            i = textBeforeCursor.index(after: next2)\n                            continue\n                        }\n                    }\n                }\n                i = textBeforeCursor.index(after: i)\n            }\n            // Odd count means there's an unmatched opening ```\n            return count % 2 != 0\n        }()\n\n        // Patterns ordered longest-delimiter-first so multi-char delimiters\n        // are checked before single-char ones to avoid false matches.\n        // (open, close, styles)\n        let patterns: [(String, String, [String])] = [\n            (\"***\", \"***\", [\"bold\", \"italic\"]),\n            (\"```\", \"```\", [\"codeBlock\"]),\n            (\"**\", \"**\", [\"bold\"]),\n            (\"~~\", \"~~\", [\"strikethrough\"]),\n            (\"<u>\", \"</u>\", [\"underline\"]),\n            (\"*\", \"*\", [\"bold\"]),\n            (\"~\", \"~\", [\"strikethrough\"]),\n            (\"`\", \"`\", [\"code\"]),\n            (\"_\", \"_\", [\"italic\"]),\n        ]\n\n        for (open, close, styles) in patterns {\n            let closeLen = close.count\n            let openLen = open.count\n\n            // Skip single-backtick inline code when an unmatched ``` exists —\n            // the user is typing a triple-backtick code block.\n            if open == \"`\" && close == \"`\" && hasUnmatchedTripleBacktick { continue }\n\n            // Text must end with the closing delimiter at cursor position\n            guard cursorPos >= closeLen else { continue }\n            let closeStart = cursorPos - closeLen\n            let closeStr = nsText.substring(with: NSRange(location: closeStart, length: closeLen))\n            guard closeStr == close else { continue }\n\n            // Skip single backtick when line ends with triple backticks\n            if open == \"`\" && cursorPos >= 3 {\n                let last3 = nsText.substring(with: NSRange(location: cursorPos - 3, length: 3))\n                if last3 == \"```\" { continue }\n            }\n\n            // For backtick-based patterns, enforce exact delimiter boundaries:\n            // the closing delimiter must not be immediately preceded by an extra\n            // backtick (beyond the delimiter itself), which would mean the user\n            // typed more backticks than the pattern expects (e.g., ```` instead of ```).\n            if (open == \"`\" || open == \"```\") && closeStart > 0 {\n                let charBeforeClose = nsText.character(at: closeStart - 1)\n                if charBeforeClose == 0x60 { continue } // 0x60 = '`'\n            }\n\n            // Search backwards for the opening delimiter\n            guard closeStart > openLen - 1 else { continue }\n\n            var found = false\n            var searchPos = closeStart - 1\n            while searchPos >= openLen - 1 {\n                let candidateStart = searchPos - (openLen - 1)\n                let candidateStr = nsText.substring(with: NSRange(location: candidateStart, length: openLen))\n                if candidateStr == open {\n                    let contentStart = candidateStart + openLen\n                    let contentLength = closeStart - contentStart\n                    if contentLength > 0 {\n                        let content = nsText.substring(with: NSRange(location: contentStart, length: contentLength))\n                        // Content must not be only whitespace\n                        guard !content.trimmingCharacters(in: .whitespaces).isEmpty else { break }\n                        // For backtick, skip if content is only backticks\n                        if open == \"`\" && content.allSatisfy({ $0 == \"`\" }) { break }\n                        // For backtick-based patterns, enforce exact opening delimiter:\n                        // skip if the character before the opening delimiter is also a\n                        // backtick (e.g., ```` instead of ``` or `` instead of `).\n                        if (open == \"`\" || open == \"```\") && candidateStart > 0 {\n                            let charBeforeOpen = nsText.character(at: candidateStart - 1)\n                            if charBeforeOpen == 0x60 { break } // 0x60 = '`'\n                        }\n                        // For single backtick, skip if the opening backtick is part of a\n                        // triple-backtick sequence (e.g., ```asdfasdf` should not match)\n                        if open == \"`\" && candidateStart >= 2 {\n                            let preceding2 = nsText.substring(with: NSRange(location: candidateStart - 2, length: 2))\n                            if preceding2 == \"``\" { break }\n                        }\n\n                        // Found valid match — replace delimiters with formatted text\n                        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n\n                        // Delete closing delimiter\n                        mutableAttr.deleteCharacters(in: NSRange(location: closeStart, length: closeLen))\n                        // Delete opening delimiter\n                        mutableAttr.deleteCharacters(in: NSRange(location: candidateStart, length: openLen))\n\n                        // Content is now at candidateStart with length contentLength\n                        let fmtStart = candidateStart\n                        let fmtEnd = candidateStart + contentLength\n                        let fmtRange = NSRange(location: fmtStart, length: contentLength)\n\n                        // Apply formatting\n                        for style in styles {\n                            switch style {\n                            case \"bold\":\n                                mutableAttr.enumerateAttribute(.font, in: fmtRange, options: []) { value, attrRange, _ in\n                                    let currentFont = (value as? UIFont) ?? UIFont.systemFont(ofSize: 16)\n                                    var traits = currentFont.fontDescriptor.symbolicTraits\n                                    traits.insert(.traitBold)\n                                    if let descriptor = currentFont.fontDescriptor.withSymbolicTraits(traits) {\n                                        mutableAttr.addAttribute(.font, value: UIFont(descriptor: descriptor, size: currentFont.pointSize), range: attrRange)\n                                    }\n                                }\n                            case \"italic\":\n                                mutableAttr.enumerateAttribute(.font, in: fmtRange, options: []) { value, attrRange, _ in\n                                    let currentFont = (value as? UIFont) ?? UIFont.systemFont(ofSize: 16)\n                                    var traits = currentFont.fontDescriptor.symbolicTraits\n                                    traits.insert(.traitItalic)\n                                    if let descriptor = currentFont.fontDescriptor.withSymbolicTraits(traits) {\n                                        mutableAttr.addAttribute(.font, value: UIFont(descriptor: descriptor, size: currentFont.pointSize), range: attrRange)\n                                    }\n                                }\n                            case \"underline\":\n                                mutableAttr.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: fmtRange)\n                            case \"strikethrough\":\n                                mutableAttr.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fmtRange)\n                            case \"code\":\n                                let monoFont = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)\n                                mutableAttr.addAttribute(.font, value: monoFont, range: fmtRange)\n                                mutableAttr.addAttribute(.foregroundColor, value: inlineCodeTextColor, range: fmtRange)\n                                mutableAttr.addAttribute(.backgroundColor, value: inlineCodeBgColor, range: fmtRange)\n                            case \"codeBlock\":\n                                let monoFont = UIFont.monospacedSystemFont(ofSize: 16, weight: .regular)\n                                mutableAttr.addAttribute(.font, value: monoFont, range: fmtRange)\n                                mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: fmtRange)\n                                mutableAttr.removeAttribute(.backgroundColor, range: fmtRange)\n                                mutableAttr.removeAttribute(.underlineStyle, range: fmtRange)\n                                mutableAttr.removeAttribute(.strikethroughStyle, range: fmtRange)\n                                mutableAttr.addAttribute(CodeBlockAttributeKey, value: true, range: fmtRange)\n                            default:\n                                break\n                            }\n                        }\n\n                        isInternalChange = true\n                        textView.attributedText = mutableAttr\n                        textView.selectedRange = NSRange(location: fmtEnd, length: 0)\n                        isInternalChange = false\n\n                        // For code block shortcut, preserve pending style so typing continues in code block\n                        if styles.contains(\"codeBlock\") {\n                            pendingStyles.removeAll()\n                            pendingStyles.insert(\"codeBlock\")\n                            pendingStylesInsertPos = fmtEnd\n                            // Set typing attributes for code block continuation\n                            let cbParaStyle = NSMutableParagraphStyle()\n                            cbParaStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                            textView.typingAttributes = [\n                                .font: UIFont.monospacedSystemFont(ofSize: 16, weight: .regular),\n                                .foregroundColor: UIColor.label,\n                                .paragraphStyle: cbParaStyle,\n                                CodeBlockAttributeKey: true\n                            ]\n                        } else {\n                            // Clear pending styles so subsequent typing is unstyled\n                            pendingStyles.removeAll()\n                            pendingStylesInsertPos = -1\n                        }\n                        explicitlyOffStyles.removeAll()\n\n                        placeholderLabel.isHidden = !textView.text.isEmpty\n                        textView.setNeedsDisplay()\n                        saveToUndoStack()\n                        sendContentChange()\n                        emitActiveStyles()\n                        found = true\n                    }\n                    break // Found the opening delimiter position\n                }\n                searchPos -= 1\n            }\n            if found { return }\n        }\n    }\n\n    /// Returns true if the string contains markdown syntax that should be parsed into rich text.\n    /// Matches Android looksLikeMarkdown().\n    func looksLikeMarkdown(_ str: String) -> Bool {\n        if str.contains(\"[\") && str.contains(\"](\") { return true }\n        if str.contains(\"**\") { return true }\n        if str.contains(\"~~\") { return true }\n        if str.contains(\"<u>\") { return true }\n        if str.filter({ $0 == \"`\" }).count >= 2 { return true }\n        if str.range(of: \"(?<![_\\\\w])_(?!_)(.+?)(?<!_)_(?![_\\\\w])\", options: .regularExpression) != nil { return true }\n        return false\n    }\n\n    /// Parses a markdown string into an NSAttributedString with proper styling.\n    /// Handles: **bold**, *bold*, _italic_, <u>underline</u>, ~~strikethrough~~, ~strike~,\n    /// `code`, and [text](url) links. Matches Android markdownToSpannable().\n    func markdownToAttributedString(_ markdown: String) -> NSAttributedString {\n        let result = NSMutableAttributedString()\n        let chars = Array(markdown)\n        let len = chars.count\n        var i = 0\n        var currentText = \"\"\n\n        var isBold = false\n        var isItalic = false\n        var isUnderline = false\n        var isStrikethrough = false\n        var isCode = false\n\n        let defaultFont = UIFont.systemFont(ofSize: 16)\n        let monoFont = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular)\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n\n        func flush() {\n            guard !currentText.isEmpty else { return }\n            var attrs: [NSAttributedString.Key: Any] = [\n                .paragraphStyle: paragraphStyle\n            ]\n\n            if isCode {\n                attrs[.font] = monoFont\n                attrs[.foregroundColor] = inlineCodeTextColor\n                attrs[.backgroundColor] = inlineCodeBgColor\n            } else {\n                var font = defaultFont\n                var traits = font.fontDescriptor.symbolicTraits\n                if isBold { traits.insert(.traitBold) }\n                if isItalic { traits.insert(.traitItalic) }\n                if let descriptor = font.fontDescriptor.withSymbolicTraits(traits) {\n                    font = UIFont(descriptor: descriptor, size: font.pointSize)\n                }\n                attrs[.font] = font\n                attrs[.foregroundColor] = UIColor.label\n                if isUnderline {\n                    attrs[.underlineStyle] = NSUnderlineStyle.single.rawValue\n                }\n                if isStrikethrough {\n                    attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue\n                }\n            }\n\n            result.append(NSAttributedString(string: currentText, attributes: attrs))\n            currentText = \"\"\n        }\n\n        while i < len {\n            if chars[i] == \"[\" {\n                if let (linkText, linkURL, fullEnd) = parseLinkAt(chars, from: i) {\n                    flush()\n                    let normalizedURL = normalizeURL(linkURL)\n                    var linkAttrs: [NSAttributedString.Key: Any] = [\n                        .font: defaultFont,\n                        .foregroundColor: UIColor.systemBlue,\n                        .paragraphStyle: paragraphStyle,\n                        LinkURLAttributeKey: normalizedURL\n                    ]\n                    if isBold {\n                        var traits = defaultFont.fontDescriptor.symbolicTraits\n                        traits.insert(.traitBold)\n                        if let desc = defaultFont.fontDescriptor.withSymbolicTraits(traits) {\n                            linkAttrs[.font] = UIFont(descriptor: desc, size: defaultFont.pointSize)\n                        }\n                    }\n                    result.append(NSAttributedString(string: linkText, attributes: linkAttrs))\n                    i = fullEnd\n                    continue\n                }\n            }\n\n            if i + 1 < len && chars[i] == \"*\" && chars[i + 1] == \"*\" {\n                flush(); isBold = !isBold; i += 2; continue\n            }\n            if i + 1 < len && chars[i] == \"~\" && chars[i + 1] == \"~\" {\n                flush(); isStrikethrough = !isStrikethrough; i += 2; continue\n            }\n            // Handle <u> and </u> HTML tags for underline\n            if i + 2 < len && chars[i] == \"<\" && chars[i + 1] == \"u\" && chars[i + 2] == \">\" {\n                flush(); isUnderline = true; i += 3; continue\n            }\n            if i + 3 < len && chars[i] == \"<\" && chars[i + 1] == \"/\" && chars[i + 2] == \"u\" && chars[i + 3] == \">\" {\n                flush(); isUnderline = false; i += 4; continue\n            }\n            // Triple-backtick code block fence: ```content``` or ```\\ncontent\\n```\n            if i + 2 < len && chars[i] == \"`\" && chars[i + 1] == \"`\" && chars[i + 2] == \"`\" {\n                flush()\n                i += 3\n                // Find closing ```\n                var codeContent = \"\"\n                var foundClose = false\n                while i < len {\n                    if i + 2 < len && chars[i] == \"`\" && chars[i + 1] == \"`\" && chars[i + 2] == \"`\" {\n                        i += 3\n                        foundClose = true\n                        break\n                    }\n                    codeContent.append(chars[i])\n                    i += 1\n                }\n                // Determine if this is a true fenced block (contains newlines) or inline usage\n                let isFencedBlock = codeContent.contains(\"\\n\") || codeContent.contains(\"\\r\")\n                let trimmedCode = codeContent.trimmingCharacters(in: CharacterSet.newlines)\n                if !trimmedCode.isEmpty {\n                    let codeBlockParaStyle = NSMutableParagraphStyle()\n                    codeBlockParaStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n                    let codeAttrs: [NSAttributedString.Key: Any] = [\n                        .font: UIFont.monospacedSystemFont(ofSize: 16, weight: .regular),\n                        .foregroundColor: UIColor.label,\n                        .paragraphStyle: codeBlockParaStyle,\n                        CodeBlockAttributeKey: true\n                    ]\n                    result.append(NSAttributedString(string: trimmedCode, attributes: codeAttrs))\n                }\n                // Only add newline separator for true fenced blocks (multi-line content),\n                // not for inline triple-backtick usage like text```code```text\n                if isFencedBlock && foundClose && i < len {\n                    result.append(NSAttributedString(string: \"\\n\", attributes: [\n                        .font: defaultFont,\n                        .foregroundColor: UIColor.label,\n                        .paragraphStyle: paragraphStyle\n                    ]))\n                }\n                continue\n            }\n            if chars[i] == \"`\" {\n                flush(); isCode = !isCode; i += 1; continue\n            }\n            if chars[i] == \"_\" {\n                flush(); isItalic = !isItalic; i += 1; continue\n            }\n\n            currentText.append(chars[i])\n            i += 1\n        }\n\n        flush()\n        return result\n    }\n\n    /// Parses a markdown link [text](url) starting at position `from`.\n    private func parseLinkAt(_ chars: [Character], from: Int) -> (String, String, Int)? {\n        guard chars[from] == \"[\" else { return nil }\n        var j = from + 1\n        while j < chars.count && chars[j] != \"]\" { j += 1 }\n        guard j < chars.count else { return nil }\n        guard j + 1 < chars.count && chars[j + 1] == \"(\" else { return nil }\n        let urlStart = j + 2\n        var k = urlStart\n        while k < chars.count && chars[k] != \")\" { k += 1 }\n        guard k < chars.count else { return nil }\n        let linkText = String(chars[(from + 1)..<j])\n        let linkURL = String(chars[urlStart..<k])\n        return (linkText, linkURL, k + 1)\n    }\n\n    /// Inserts styled paste content at the given selection range, replacing any selected text.\n    func insertStyledPasteContent(_ styledText: NSAttributedString, at range: NSRange) {\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        if range.length > 0 {\n            mutableAttr.replaceCharacters(in: range, with: styledText)\n        } else {\n            mutableAttr.insert(styledText, at: range.location)\n        }\n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        textView.selectedRange = NSRange(location: range.location + styledText.length, length: 0)\n        isInternalChange = false\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        saveToUndoStack()\n        sendContentChange()\n        emitActiveStyles()\n    }\n\n    /// Detects \"> \" markdown shortcut at the start of a line and converts to blockquote.\n    /// Called from textDidChange(), matches Android detectBlockMarkdownShortcut() for \"> \".\n    private func detectBlockquoteShortcut() {\n        let text = textView.text ?? \"\"\n        let cursorPos = textView.selectedRange.location\n        guard cursorPos >= 2 else { return }\n\n        // Code block guard: don't convert \"> \" to blockquote inside code blocks (matches Android)\n        if pendingStyles.contains(\"codeBlock\") { return }\n        if let attrText = textView.attributedText, cursorPos > 0 {\n            let checkPos = min(cursorPos - 1, attrText.length - 1)\n            if checkPos >= 0 {\n                let val = attrText.attribute(CodeBlockAttributeKey, at: checkPos, effectiveRange: nil)\n                if val != nil { return }\n            }\n        }\n\n        let nsText = text as NSString\n\n        // Find start of current line\n        var lineStart = cursorPos\n        while lineStart > 0 && nsText.character(at: lineStart - 1) != 10 { // '\\n'\n            lineStart -= 1\n        }\n\n        let lineText = nsText.substring(with: NSRange(location: lineStart, length: cursorPos - lineStart))\n        guard lineText == \"> \" else { return }\n\n        // Delete the \"> \" text and apply blockquote via setQuote()\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        mutableAttr.deleteCharacters(in: NSRange(location: lineStart, length: 2))\n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        textView.selectedRange = NSRange(location: lineStart, length: 0)\n        isInternalChange = false\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        setQuote()\n    }\n\n    /// Detects [text](url) markdown pattern and converts to styled hyperlink.\n    private func detectLinkShortcut() {\n        let text = textView.text ?? \"\"\n        let cursorPos = textView.selectedRange.location\n        guard cursorPos >= 1, cursorPos <= text.count else { return }\n        let nsText = text as NSString\n        guard nsText.character(at: cursorPos - 1) == 41 else { return } // ')'\n\n        var parenOpen = -1\n        for i in stride(from: cursorPos - 2, through: 0, by: -1) {\n            if nsText.character(at: i) == 40 { parenOpen = i; break } // '('\n        }\n        guard parenOpen >= 1 else { return }\n        guard nsText.character(at: parenOpen - 1) == 93 else { return } // ']'\n        var bracketOpen = -1\n        for i in stride(from: parenOpen - 2, through: 0, by: -1) {\n            if nsText.character(at: i) == 91 { bracketOpen = i; break } // '['\n        }\n        guard bracketOpen >= 0 else { return }\n\n        let linkText = nsText.substring(with: NSRange(location: bracketOpen + 1, length: parenOpen - 1 - bracketOpen - 1))\n        let url = nsText.substring(with: NSRange(location: parenOpen + 1, length: cursorPos - 1 - parenOpen - 1))\n        guard !linkText.isEmpty, !url.isEmpty else { return }\n\n        let normalizedUrl = normalizeURL(url)\n        let patternRange = NSRange(location: bracketOpen, length: cursorPos - bracketOpen)\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        let linkAttrString = NSAttributedString(string: linkText, attributes: [\n            LinkURLAttributeKey: normalizedUrl,\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.systemBlue\n        ])\n\n        isInternalChange = true\n        mutableAttr.replaceCharacters(in: patternRange, with: linkAttrString)\n        textView.attributedText = mutableAttr\n        textView.selectedRange = NSRange(location: bracketOpen + linkText.count, length: 0)\n\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.alignment = .left\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n        textView.typingAttributes = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label,\n            .paragraphStyle: paragraphStyle\n        ]\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    /// Creates a styled hyperlink from a pasted URL over selected text.\n    func insertLinkOverSelection(url: String, displayText: String, range: NSRange) {\n        let normalizedUrl = normalizeURL(url)\n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        let linkAttrString = NSAttributedString(string: displayText, attributes: [\n            LinkURLAttributeKey: normalizedUrl,\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.systemBlue\n        ])\n        mutableAttr.replaceCharacters(in: range, with: linkAttrString)\n\n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        let newCursorPos = range.location + displayText.count\n        textView.selectedRange = NSRange(location: newCursorPos, length: 0)\n\n        let paragraphStyle = NSMutableParagraphStyle()\n        paragraphStyle.alignment = .left\n        paragraphStyle.lineHeightMultiple = RichTextEditorView.defaultLineHeightMultiple\n        textView.typingAttributes = [\n            .font: UIFont.systemFont(ofSize: 16),\n            .foregroundColor: UIColor.label,\n            .paragraphStyle: paragraphStyle\n        ]\n        placeholderLabel.isHidden = !textView.text.isEmpty\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func removeLink(location: Int, length: Int) {\n        let range = NSRange(location: location, length: length)\n        guard range.location + range.length <= textView.attributedText.length else { return }\n        \n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        mutableAttr.removeAttribute(LinkURLAttributeKey, range: range)\n        mutableAttr.addAttribute(.foregroundColor, value: UIColor.label, range: range)\n        \n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n\n    func updateLink(location: Int, length: Int, newUrl: String, newText: String) {\n        let normalizedUrl = normalizeURL(newUrl)\n        let range = NSRange(location: location, length: length)\n        guard range.location + range.length <= textView.attributedText.length else { return }\n        \n        let mutableAttr = NSMutableAttributedString(attributedString: textView.attributedText)\n        \n        // Replace the text at the range with new text\n        let replacement = NSMutableAttributedString(string: newText)\n        \n        // Copy attributes from the original range\n        if range.length > 0 {\n            let originalAttrs = mutableAttr.attributes(at: range.location, effectiveRange: nil)\n            replacement.addAttributes(originalAttrs, range: NSRange(location: 0, length: newText.count))\n        }\n        \n        // Update the link URL with normalized URL\n        replacement.addAttribute(LinkURLAttributeKey, value: normalizedUrl, range: NSRange(location: 0, length: newText.count))\n        replacement.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: NSRange(location: 0, length: newText.count))\n        \n        mutableAttr.replaceCharacters(in: range, with: replacement)\n        \n        isInternalChange = true\n        textView.attributedText = mutableAttr\n        isInternalChange = false\n        saveToUndoStack()\n        sendContentChange()\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/ios/RichTextEditorViewManager.m",
    "content": "#import <React/RCTViewManager.h>\n\n@interface RCT_EXTERN_MODULE(RichTextEditorViewManager, RCTViewManager)\n\nRCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)\nRCT_EXPORT_VIEW_PROPERTY(editable, BOOL)\nRCT_EXPORT_VIEW_PROPERTY(maxHeight, CGFloat)\nRCT_EXPORT_VIEW_PROPERTY(numberOfLines, NSInteger)\nRCT_EXPORT_VIEW_PROPERTY(showToolbar, BOOL)\nRCT_EXPORT_VIEW_PROPERTY(toolbarOptions, NSArray)\nRCT_EXPORT_VIEW_PROPERTY(initialContentJson, NSString)\nRCT_EXPORT_VIEW_PROPERTY(variant, NSString)\nRCT_EXPORT_VIEW_PROPERTY(onContentChange, RCTDirectEventBlock)\nRCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)\nRCT_EXPORT_VIEW_PROPERTY(onEditorFocus, RCTDirectEventBlock)\nRCT_EXPORT_VIEW_PROPERTY(onEditorBlur, RCTDirectEventBlock)\nRCT_EXPORT_VIEW_PROPERTY(onSizeChange, RCTDirectEventBlock)\nRCT_EXPORT_VIEW_PROPERTY(onActiveStylesChange, RCTDirectEventBlock)\nRCT_EXPORT_VIEW_PROPERTY(onLinkTap, RCTDirectEventBlock)\n\n// New properties for CometChat integration\nRCT_EXPORT_VIEW_PROPERTY(selection, NSDictionary)\nRCT_EXPORT_VIEW_PROPERTY(textStyle, NSDictionary)\nRCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, NSString)\nRCT_EXPORT_VIEW_PROPERTY(text, NSString)\nRCT_EXPORT_VIEW_PROPERTY(toolbarMode, NSString)\nRCT_EXPORT_VIEW_PROPERTY(codeBackgroundColor, NSString)\nRCT_EXPORT_VIEW_PROPERTY(codeBorderColor, NSString)\nRCT_EXPORT_VIEW_PROPERTY(codeTextColor, NSString)\nRCT_EXPORT_VIEW_PROPERTY(codeFontSize, NSNumber)\nRCT_EXPORT_VIEW_PROPERTY(showTextSelectionMenuItems, BOOL)\n\nRCT_EXTERN_METHOD(setContent:(nonnull NSNumber *)node blocks:(nonnull NSArray *)blocks)\nRCT_EXTERN_METHOD(getText:(nonnull NSNumber *)node resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)\nRCT_EXTERN_METHOD(getBlocks:(nonnull NSNumber *)node resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)\nRCT_EXTERN_METHOD(clear:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(focus:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(blur:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(insertLink:(nonnull NSNumber *)node url:(nonnull NSString *)url text:(nonnull NSString *)text)\nRCT_EXTERN_METHOD(undo:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(redo:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleBold:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleItalic:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleUnderline:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleStrikethrough:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleCode:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleCodeBlock:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(toggleHighlight:(nonnull NSNumber *)node color:(NSString *)color)\nRCT_EXTERN_METHOD(setHeading:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setBulletList:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setNumberedList:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setQuote:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setChecklist:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setParagraph:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(clearFormatting:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(indent:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(outdent:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setAlignment:(nonnull NSNumber *)node alignment:(nonnull NSString *)alignment)\nRCT_EXTERN_METHOD(toggleChecklistItem:(nonnull NSNumber *)node)\nRCT_EXTERN_METHOD(setText:(nonnull NSNumber *)node text:(nonnull NSString *)text)\nRCT_EXTERN_METHOD(setSelection:(nonnull NSNumber *)node start:(nonnull NSInteger)start end:(nonnull NSInteger)end)\nRCT_EXTERN_METHOD(setMentionRanges:(nonnull NSNumber *)node ranges:(nonnull NSArray *)ranges)\nRCT_EXTERN_METHOD(removeLink:(nonnull NSNumber *)node location:(nonnull NSInteger)location length:(nonnull NSInteger)length)\nRCT_EXTERN_METHOD(updateLink:(nonnull NSNumber *)node location:(nonnull NSInteger)location length:(nonnull NSInteger)length newUrl:(nonnull NSString *)newUrl newText:(nonnull NSString *)newText)\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/RichTextEditorViewManager.swift",
    "content": "import React\n\n@objc(RichTextEditorViewManager)\nclass RichTextEditorViewManager: RCTViewManager {\n\n    override func view() -> UIView! {\n        return RichTextEditorView()\n    }\n\n    override static func requiresMainQueueSetup() -> Bool {\n        return true\n    }\n\n    @objc func setContent(_ node: NSNumber, blocks: NSArray) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView,\n               let blocksArray = blocks as? [[String: Any]] {\n                view.setContent(blocks: blocksArray)\n            }\n        }\n    }\n\n    @objc func getText(_ node: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                resolve(view.getText())\n            } else {\n                reject(\"ERROR\", \"View not found\", nil)\n            }\n        }\n    }\n\n    @objc func getBlocks(_ node: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                resolve(view.getBlocksArray())\n            } else {\n                reject(\"ERROR\", \"View not found\", nil)\n            }\n        }\n    }\n\n    @objc func clear(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.clear()\n            }\n        }\n    }\n\n    @objc func focus(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.focus()\n            }\n        }\n    }\n\n    @objc func blur(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.blur()\n            }\n        }\n    }\n\n    @objc func insertLink(_ node: NSNumber, url: NSString, text: NSString) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.insertLink(url: url as String, text: text as String)\n            }\n        }\n    }\n\n    @objc func undo(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.undo()\n            }\n        }\n    }\n\n    @objc func redo(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.redo()\n            }\n        }\n    }\n\n    @objc func toggleBold(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleBold()\n            }\n        }\n    }\n\n    @objc func toggleItalic(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleItalic()\n            }\n        }\n    }\n\n    @objc func toggleUnderline(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleUnderline()\n            }\n        }\n    }\n\n    @objc func toggleStrikethrough(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleStrikethrough()\n            }\n        }\n    }\n\n    @objc func toggleCode(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleCode()\n            }\n        }\n    }\n\n    @objc func toggleCodeBlock(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleCodeBlock()\n            }\n        }\n    }\n\n    @objc func toggleHighlight(_ node: NSNumber, color: NSString?) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleHighlight(color: color as String?)\n            }\n        }\n    }\n\n    @objc func setHeading(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.setHeading()\n            }\n        }\n    }\n\n    @objc func setBulletList(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleBulletList()\n            }\n        }\n    }\n\n    @objc func setNumberedList(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleNumberedList()\n            }\n        }\n    }\n\n    @objc func setQuote(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.setQuote()\n            }\n        }\n    }\n\n    @objc func setChecklist(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.setChecklist()\n            }\n        }\n    }\n\n    @objc func setParagraph(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.setParagraph()\n            }\n        }\n    }\n\n    @objc func clearFormatting(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.clearFormatting()\n            }\n        }\n    }\n\n    @objc func indent(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.indent()\n            }\n        }\n    }\n\n    @objc func outdent(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.outdent()\n            }\n        }\n    }\n\n    @objc func setAlignment(_ node: NSNumber, alignment: NSString) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                let alignmentValue: NSTextAlignment\n                switch alignment as String {\n                case \"center\":\n                    alignmentValue = .center\n                case \"right\":\n                    alignmentValue = .right\n                default:\n                    alignmentValue = .left\n                }\n                view.setAlignment(alignmentValue)\n            }\n        }\n    }\n\n    @objc func toggleChecklistItem(_ node: NSNumber) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.toggleChecklistItem()\n            }\n        }\n    }\n\n    @objc func setText(_ node: NSNumber, text: String) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.setTextContent(text, force: true)\n            }\n        }\n    }\n\n    @objc func setSelection(_ node: NSNumber, start: Int, end: Int) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.setSelectionRange(start: start, end: end)\n            }\n        }\n    }\n\n    @objc func setMentionRanges(_ node: NSNumber, ranges: NSArray) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                var parsed: [[String: Int]] = []\n                for item in ranges {\n                    if let dict = item as? [String: Any],\n                       let start = dict[\"start\"] as? Int,\n                       let end = dict[\"end\"] as? Int {\n                        parsed.append([\"start\": start, \"end\": end])\n                    }\n                }\n                view.setMentionRanges(parsed)\n            }\n        }\n    }\n\n    @objc func removeLink(_ node: NSNumber, location: Int, length: Int) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.removeLink(location: location, length: length)\n            }\n        }\n    }\n\n    @objc func updateLink(_ node: NSNumber, location: Int, length: Int, newUrl: NSString, newText: NSString) {\n        DispatchQueue.main.async {\n            if let view = self.bridge?.uiManager.view(forReactTag: node) as? RichTextEditorView {\n                view.updateLink(location: location, length: length, newUrl: newUrl as String, newText: newText as String)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/ios/SoundPlayer.h",
    "content": "#import <React/RCTBridgeModule.h>\n#import <React/RCTEventEmitter.h>\n#import <AVFoundation/AVFoundation.h>\n\n@interface CometChatSoundPlayer : RCTEventEmitter <AVAudioPlayerDelegate, RCTBridgeModule>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/SoundPlayer.m",
    "content": "#import <Foundation/Foundation.h>\n#import <React/RCTBridgeModule.h>\n#import \"SoundPlayer.h\"\n#import \"AVFoundation/AVFoundation.h\"\n\n\n@implementation CometChatSoundPlayer {\n    AVAudioPlayer *audioPlayer;\n    bool hasListeners;\n    NSString *currentUrl;\n}\n\n- (void)startObserving {\n    hasListeners = YES;\n}\n\n- (void)stopObserving {\n    hasListeners = NO;\n}\n\n- (NSArray<NSString *> *)supportedEvents {\n    return @[@\"soundPlayStatus\"];\n}\n\n- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {\n    [self sendEventWithName:@\"soundPlayStatus\" body:@{@\"url\": currentUrl}];\n}\n\nRCT_EXPORT_MODULE(SoundPlayer)\n\nRCT_EXPORT_METHOD(prepareMediaPlayer:(NSString *) url\n                  callback:(RCTResponseSenderBlock) resolve) {\n   NSURL *nsurl;\n    if ([url hasPrefix:@\"http\"] || [url hasPrefix:@\"https\"]) {\n        nsurl = [NSURL URLWithString:url];\n    } else {\n        nsurl = [NSURL fileURLWithPath:url]; // Use fileURLWithPath for local files\n    }\n   NSData *data = [NSData dataWithContentsOfURL:nsurl];\n   if ([audioPlayer isPlaying]) {\n       [audioPlayer stop];\n       [self sendEventWithName:@\"soundPlayStatus\" body:@{@\"url\": currentUrl}];\n   }\n   currentUrl = url;\n   audioPlayer = [[AVAudioPlayer alloc] initWithData:data error:nil];\n    [audioPlayer setDelegate: self];\n   int duration = [audioPlayer duration];\n   NSString *response = [NSString stringWithFormat:@\"{\\\"duration\\\":%d}\", duration];\n   resolve(@[response]);\n}\n\nRCT_EXPORT_METHOD(play:(NSString *) url\n                  callback:(RCTResponseSenderBlock) resolve) {\n   NSURL *nsurl;\n    if ([url hasPrefix:@\"http\"] || [url hasPrefix:@\"https\"]) {\n        nsurl = [NSURL URLWithString:url];\n    } else {\n        nsurl = [NSURL fileURLWithPath:url]; // Use fileURLWithPath for local files\n    }\n   NSData *data = [NSData dataWithContentsOfURL:nsurl];\n   if ([audioPlayer isPlaying]) {\n       [audioPlayer stop];\n       [self sendEventWithName:@\"soundPlayStatus\" body:@{@\"url\": currentUrl}];\n   }\n   currentUrl = url;\n   audioPlayer = [[AVAudioPlayer alloc] initWithData:data error:nil];\n    [audioPlayer setDelegate: self];\n\n   int playSuccess = [audioPlayer play];\n   int duration = [audioPlayer duration];\n   NSString *response = [NSString stringWithFormat:@\"{\\\"success\\\":%d, \\\"duration\\\":%d}\", playSuccess, duration];\n   resolve(@[response]);\n}\n\nRCT_EXPORT_METHOD(playAt:(NSInteger) atTime resolver:(RCTResponseSenderBlock)resolve) {\n    NSTimeInterval interval = (NSTimeInterval)atTime;\n    bool res = [audioPlayer playAtTime:interval];\n    NSString *response = [NSString stringWithFormat:@\"{\\\"success\\\":%d}\",res];\n    resolve(@[response]);\n}\n\nRCT_EXPORT_METHOD(resume) {\n    [audioPlayer play];\n}\n\nRCT_EXPORT_METHOD(getPosition:(RCTResponseSenderBlock) resolve) {\n    if ([audioPlayer isPlaying]) {\n        NSString *response = [NSString stringWithFormat:@\"{\\\"position\\\":%f}\", [audioPlayer currentTime]];\n        resolve(@[response]);\n    } else {\n        resolve(@[@\"Error\"]);\n    }\n}\n\nRCT_EXPORT_METHOD(pause: (RCTResponseSenderBlock) resolve) {\n    if ([audioPlayer isPlaying]) {\n        [audioPlayer pause];\n        resolve(@[@\"{\\\"success\\\": true}\"]);\n    } else {\n        resolve(@[@\"{\\\"success\\\": false}\"]);\n    }\n}\n\nRCT_EXPORT_METHOD(releaseMediaPlayer) {\n    if (audioPlayer) {\n        [audioPlayer stop];\n        audioPlayer = nil;\n    }\n    currentUrl = nil;\n}\n\nRCT_EXPORT_METHOD(getCurrentTime: (RCTResponseSenderBlock)resolve) {\n  if ([audioPlayer isPlaying]) {\n      NSString *response = [NSString stringWithFormat:@\"{\\\"time\\\": %f}\",[audioPlayer currentTime]];\n      resolve(@[response]);\n  } else {\n    resolve(@[@\"Error\"]);\n  }\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/TimeZoneCodeManager.h",
    "content": "#import <React/RCTBridgeModule.h>\n#import <QuickLook/QuickLook.h>\n\n@interface TimeZoneCodeManager : NSObject <RCTBridgeModule, QLPreviewControllerDataSource, QLPreviewControllerDelegate>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/TimeZoneCodeManager.m",
    "content": "#import \"TimeZoneCodeManager.h\"\n#import <React/RCTUtils.h>\n\n@implementation TimeZoneCodeManager {\n    \n}\n\n// This macro makes the module available to JavaScript\nRCT_EXPORT_MODULE(TimeZoneCodeManager);\n\n// Export your method to JavaScript using RCT_EXPORT_METHOD\nRCT_EXPORT_METHOD(getCurrentTimeZone:(RCTResponseSenderBlock)callback) {\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        // Get the time zone string\n        NSString *timeZone = [[NSTimeZone localTimeZone] name];\n\n        // Call the callback block with the time zone string\n        callback(@[timeZone]);\n    });\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/ToolbarIcons.swift",
    "content": "import UIKit\n\n/// Provides vector icons for the rich text editor toolbar\n/// Icons match the Android VectorDrawable implementations for cross-platform consistency\nclass ToolbarIcons {\n\n    static func getIcon(for option: String, color: UIColor, size: CGSize = CGSize(width: 24, height: 24)) -> UIImage? {\n        switch option {\n        case \"bold\": return drawBoldIcon(color: color, size: size)\n        case \"italic\": return drawItalicIcon(color: color, size: size)\n        case \"underline\": return drawUnderlineIcon(color: color, size: size)\n        case \"strikethrough\": return drawStrikethroughIcon(color: color, size: size)\n        case \"code\": return drawCodeIcon(color: color, size: size)\n        case \"highlight\": return drawHighlightIcon(color: color, size: size)\n        case \"heading\": return drawHeadingIcon(color: color, size: size)\n        case \"bullet\": return drawBulletListIcon(color: color, size: size)\n        case \"numbered\": return drawNumberedListIcon(color: color, size: size)\n        case \"quote\": return drawQuoteIcon(color: color, size: size)\n        case \"checklist\": return drawChecklistIcon(color: color, size: size)\n        case \"link\": return drawLinkIcon(color: color, size: size)\n        case \"undo\": return drawUndoIcon(color: color, size: size)\n        case \"redo\": return drawRedoIcon(color: color, size: size)\n        case \"clearFormatting\": return drawClearFormattingIcon(color: color, size: size)\n        case \"indent\": return drawIndentIcon(color: color, size: size)\n        case \"outdent\": return drawOutdentIcon(color: color, size: size)\n        case \"alignLeft\": return drawAlignLeftIcon(color: color, size: size)\n        case \"alignCenter\": return drawAlignCenterIcon(color: color, size: size)\n        case \"alignRight\": return drawAlignRightIcon(color: color, size: size)\n        default: return nil\n        }\n    }\n\n    // MARK: - Icon Drawing Methods\n\n    private static func drawBoldIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 15.6 * scale, y: 10.79 * scale))\n            path.addCurve(to: CGPoint(x: 17.25 * scale, y: 8 * scale),\n                         controlPoint1: CGPoint(x: 16.57 * scale, y: 10.12 * scale),\n                         controlPoint2: CGPoint(x: 17.25 * scale, y: 9.02 * scale))\n            path.addCurve(to: CGPoint(x: 13.25 * scale, y: 4 * scale),\n                         controlPoint1: CGPoint(x: 17.25 * scale, y: 5.74 * scale),\n                         controlPoint2: CGPoint(x: 15.5 * scale, y: 4 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 4 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 18 * scale))\n            path.addLine(to: CGPoint(x: 14.04 * scale, y: 18 * scale))\n            path.addCurve(to: CGPoint(x: 17.75 * scale, y: 14.21 * scale),\n                         controlPoint1: CGPoint(x: 16.13 * scale, y: 18 * scale),\n                         controlPoint2: CGPoint(x: 17.75 * scale, y: 16.51 * scale))\n            path.addCurve(to: CGPoint(x: 15.6 * scale, y: 10.79 * scale),\n                         controlPoint1: CGPoint(x: 17.75 * scale, y: 12.69 * scale),\n                         controlPoint2: CGPoint(x: 16.89 * scale, y: 11.39 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 10 * scale, y: 6.5 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 6.5 * scale))\n            path.addCurve(to: CGPoint(x: 14.5 * scale, y: 8 * scale),\n                         controlPoint1: CGPoint(x: 13.83 * scale, y: 6.5 * scale),\n                         controlPoint2: CGPoint(x: 14.5 * scale, y: 7.17 * scale))\n            path.addCurve(to: CGPoint(x: 13 * scale, y: 9.5 * scale),\n                         controlPoint1: CGPoint(x: 14.5 * scale, y: 8.83 * scale),\n                         controlPoint2: CGPoint(x: 13.83 * scale, y: 9.5 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 9.5 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 6.5 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 13.5 * scale, y: 15.5 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 15.5 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 12.5 * scale))\n            path.addLine(to: CGPoint(x: 13.5 * scale, y: 12.5 * scale))\n            path.addCurve(to: CGPoint(x: 15 * scale, y: 14 * scale),\n                         controlPoint1: CGPoint(x: 14.33 * scale, y: 12.5 * scale),\n                         controlPoint2: CGPoint(x: 15 * scale, y: 13.17 * scale))\n            path.addCurve(to: CGPoint(x: 13.5 * scale, y: 15.5 * scale),\n                         controlPoint1: CGPoint(x: 15 * scale, y: 14.83 * scale),\n                         controlPoint2: CGPoint(x: 14.33 * scale, y: 15.5 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawItalicIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 10 * scale, y: 4 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 12.21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 8.79 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 6 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 6 * scale, y: 18 * scale))\n            path.addLine(to: CGPoint(x: 14 * scale, y: 18 * scale))\n            path.addLine(to: CGPoint(x: 14 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 11.79 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 15.21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 18 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 18 * scale, y: 4 * scale))\n            path.close()\n            path.fill()\n        }\n    }\n\n    private static func drawUnderlineIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // U shape\n            path.move(to: CGPoint(x: 12 * scale, y: 17 * scale))\n            path.addCurve(to: CGPoint(x: 18 * scale, y: 11 * scale),\n                         controlPoint1: CGPoint(x: 15.31 * scale, y: 17 * scale),\n                         controlPoint2: CGPoint(x: 18 * scale, y: 14.31 * scale))\n            path.addLine(to: CGPoint(x: 18 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 15.5 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 15.5 * scale, y: 11 * scale))\n            path.addCurve(to: CGPoint(x: 12 * scale, y: 14.5 * scale),\n                         controlPoint1: CGPoint(x: 15.5 * scale, y: 12.93 * scale),\n                         controlPoint2: CGPoint(x: 13.93 * scale, y: 14.5 * scale))\n            path.addCurve(to: CGPoint(x: 8.5 * scale, y: 11 * scale),\n                         controlPoint1: CGPoint(x: 10.07 * scale, y: 14.5 * scale),\n                         controlPoint2: CGPoint(x: 8.5 * scale, y: 12.93 * scale))\n            path.addLine(to: CGPoint(x: 8.5 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 6 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 6 * scale, y: 11 * scale))\n            path.addCurve(to: CGPoint(x: 12 * scale, y: 17 * scale),\n                         controlPoint1: CGPoint(x: 6 * scale, y: 14.31 * scale),\n                         controlPoint2: CGPoint(x: 8.69 * scale, y: 17 * scale))\n            path.close()\n\n            // Underline\n            path.move(to: CGPoint(x: 5 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 5 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 19 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawStrikethroughIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Top part\n            path.move(to: CGPoint(x: 10 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 14 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 14 * scale, y: 16 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 16 * scale))\n            path.close()\n\n            // S shape top\n            path.move(to: CGPoint(x: 5 * scale, y: 4 * scale))\n            path.addLine(to: CGPoint(x: 5 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 10 * scale, y: 10 * scale))\n            path.addLine(to: CGPoint(x: 14 * scale, y: 10 * scale))\n            path.addLine(to: CGPoint(x: 14 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 4 * scale))\n            path.close()\n\n            // Strike line\n            path.move(to: CGPoint(x: 3 * scale, y: 14 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 14 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 12 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 12 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawCodeIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Left bracket <\n            path.move(to: CGPoint(x: 9.4 * scale, y: 16.6 * scale))\n            path.addLine(to: CGPoint(x: 4.8 * scale, y: 12 * scale))\n            path.addLine(to: CGPoint(x: 9.4 * scale, y: 7.4 * scale))\n            path.addLine(to: CGPoint(x: 8 * scale, y: 6 * scale))\n            path.addLine(to: CGPoint(x: 2 * scale, y: 12 * scale))\n            path.addLine(to: CGPoint(x: 8 * scale, y: 18 * scale))\n            path.close()\n\n            // Right bracket >\n            path.move(to: CGPoint(x: 14.6 * scale, y: 16.6 * scale))\n            path.addLine(to: CGPoint(x: 19.2 * scale, y: 12 * scale))\n            path.addLine(to: CGPoint(x: 14.6 * scale, y: 7.4 * scale))\n            path.addLine(to: CGPoint(x: 16 * scale, y: 6 * scale))\n            path.addLine(to: CGPoint(x: 22 * scale, y: 12 * scale))\n            path.addLine(to: CGPoint(x: 16 * scale, y: 18 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawHighlightIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Marker body\n            path.move(to: CGPoint(x: 6 * scale, y: 14 * scale))\n            path.addLine(to: CGPoint(x: 9 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 9 * scale, y: 22 * scale))\n            path.addLine(to: CGPoint(x: 15 * scale, y: 22 * scale))\n            path.addLine(to: CGPoint(x: 15 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 18 * scale, y: 14 * scale))\n            path.addLine(to: CGPoint(x: 18 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 6 * scale, y: 9 * scale))\n            path.close()\n\n            // Top line\n            path.move(to: CGPoint(x: 11 * scale, y: 2 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 2 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 5 * scale))\n            path.close()\n\n            // Left ray\n            path.move(to: CGPoint(x: 3.5 * scale, y: 5.88 * scale))\n            path.addLine(to: CGPoint(x: 4.91 * scale, y: 4.47 * scale))\n            path.addLine(to: CGPoint(x: 7.03 * scale, y: 6.59 * scale))\n            path.addLine(to: CGPoint(x: 5.62 * scale, y: 8 * scale))\n            path.close()\n\n            // Right ray\n            path.move(to: CGPoint(x: 16.96 * scale, y: 6.59 * scale))\n            path.addLine(to: CGPoint(x: 19.08 * scale, y: 4.47 * scale))\n            path.addLine(to: CGPoint(x: 20.49 * scale, y: 5.88 * scale))\n            path.addLine(to: CGPoint(x: 18.37 * scale, y: 8 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawHeadingIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 5 * scale, y: 4 * scale))\n            path.addLine(to: CGPoint(x: 5 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 10.5 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 10.5 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 13.5 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 13.5 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 4 * scale))\n            path.close()\n            path.fill()\n        }\n    }\n\n    private static func drawBulletListIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Bullets\n            path.append(UIBezierPath(ovalIn: CGRect(x: 2.5 * scale, y: 10.5 * scale, width: 3 * scale, height: 3 * scale)))\n            path.append(UIBezierPath(ovalIn: CGRect(x: 2.5 * scale, y: 4.5 * scale, width: 3 * scale, height: 3 * scale)))\n            path.append(UIBezierPath(ovalIn: CGRect(x: 2.5 * scale, y: 16.5 * scale, width: 3 * scale, height: 3 * scale)))\n\n            // Lines\n            path.move(to: CGPoint(x: 7 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 17 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 7 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 11 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 7 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawNumberedListIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            // Draw numbers using text\n            let paragraphStyle = NSMutableParagraphStyle()\n            paragraphStyle.alignment = .left\n            let attrs: [NSAttributedString.Key: Any] = [\n                .font: UIFont.systemFont(ofSize: 8 * scale, weight: .medium),\n                .foregroundColor: color\n            ]\n\n            \"1\".draw(at: CGPoint(x: 3 * scale, y: 4 * scale), withAttributes: attrs)\n            \"2\".draw(at: CGPoint(x: 3 * scale, y: 10 * scale), withAttributes: attrs)\n            \"3\".draw(at: CGPoint(x: 3 * scale, y: 16 * scale), withAttributes: attrs)\n\n            // Lines\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 7 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 7 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 11 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 7 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 17 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawQuoteIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Left quote\n            path.move(to: CGPoint(x: 6 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 9 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 5 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 5 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 8 * scale, y: 13 * scale))\n            path.close()\n\n            // Right quote\n            path.move(to: CGPoint(x: 14 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 19 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 16 * scale, y: 13 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawChecklistIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Lines\n            path.move(to: CGPoint(x: 22 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 22 * scale, y: 9 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 22 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 22 * scale, y: 17 * scale))\n            path.close()\n\n            // Checkmarks\n            path.move(to: CGPoint(x: 5.54 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 2 * scale, y: 7.46 * scale))\n            path.addLine(to: CGPoint(x: 3.41 * scale, y: 6.05 * scale))\n            path.addLine(to: CGPoint(x: 5.53 * scale, y: 8.17 * scale))\n            path.addLine(to: CGPoint(x: 9.77 * scale, y: 3.93 * scale))\n            path.addLine(to: CGPoint(x: 11.18 * scale, y: 5.34 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 5.54 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 2 * scale, y: 15.46 * scale))\n            path.addLine(to: CGPoint(x: 3.41 * scale, y: 14.05 * scale))\n            path.addLine(to: CGPoint(x: 5.53 * scale, y: 16.17 * scale))\n            path.addLine(to: CGPoint(x: 9.77 * scale, y: 11.93 * scale))\n            path.addLine(to: CGPoint(x: 11.18 * scale, y: 13.34 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawLinkIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Left chain\n            path.move(to: CGPoint(x: 3.9 * scale, y: 12 * scale))\n            path.addCurve(to: CGPoint(x: 7 * scale, y: 8.9 * scale),\n                         controlPoint1: CGPoint(x: 3.9 * scale, y: 10.29 * scale),\n                         controlPoint2: CGPoint(x: 5.29 * scale, y: 8.9 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 8.9 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 7 * scale))\n            path.addCurve(to: CGPoint(x: 2 * scale, y: 12 * scale),\n                         controlPoint1: CGPoint(x: 4.24 * scale, y: 7 * scale),\n                         controlPoint2: CGPoint(x: 2 * scale, y: 9.24 * scale))\n            path.addCurve(to: CGPoint(x: 7 * scale, y: 17 * scale),\n                         controlPoint1: CGPoint(x: 2 * scale, y: 14.76 * scale),\n                         controlPoint2: CGPoint(x: 4.24 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 15.1 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 15.1 * scale))\n            path.addCurve(to: CGPoint(x: 3.9 * scale, y: 12 * scale),\n                         controlPoint1: CGPoint(x: 5.29 * scale, y: 15.1 * scale),\n                         controlPoint2: CGPoint(x: 3.9 * scale, y: 13.71 * scale))\n            path.close()\n\n            // Middle bar\n            path.move(to: CGPoint(x: 8 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 16 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 16 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 8 * scale, y: 11 * scale))\n            path.close()\n\n            // Right chain\n            path.move(to: CGPoint(x: 17 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 8.9 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 8.9 * scale))\n            path.addCurve(to: CGPoint(x: 20.1 * scale, y: 12 * scale),\n                         controlPoint1: CGPoint(x: 18.71 * scale, y: 8.9 * scale),\n                         controlPoint2: CGPoint(x: 20.1 * scale, y: 10.29 * scale))\n            path.addCurve(to: CGPoint(x: 17 * scale, y: 15.1 * scale),\n                         controlPoint1: CGPoint(x: 20.1 * scale, y: 13.71 * scale),\n                         controlPoint2: CGPoint(x: 18.71 * scale, y: 15.1 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 15.1 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 17 * scale))\n            path.addCurve(to: CGPoint(x: 22 * scale, y: 12 * scale),\n                         controlPoint1: CGPoint(x: 19.76 * scale, y: 17 * scale),\n                         controlPoint2: CGPoint(x: 22 * scale, y: 14.76 * scale))\n            path.addCurve(to: CGPoint(x: 17 * scale, y: 7 * scale),\n                         controlPoint1: CGPoint(x: 22 * scale, y: 9.24 * scale),\n                         controlPoint2: CGPoint(x: 19.76 * scale, y: 7 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawUndoIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 12.5 * scale, y: 8 * scale))\n            path.addCurve(to: CGPoint(x: 5.67 * scale, y: 10.73 * scale),\n                         controlPoint1: CGPoint(x: 9.85 * scale, y: 8 * scale),\n                         controlPoint2: CGPoint(x: 7.45 * scale, y: 9.04 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 8 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 12 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 9.19 * scale, y: 14.19 * scale))\n            path.addCurve(to: CGPoint(x: 14.25 * scale, y: 12.25 * scale),\n                         controlPoint1: CGPoint(x: 10.55 * scale, y: 12.98 * scale),\n                         controlPoint2: CGPoint(x: 12.33 * scale, y: 12.25 * scale))\n            path.addCurve(to: CGPoint(x: 21.14 * scale, y: 18.25 * scale),\n                         controlPoint1: CGPoint(x: 17.77 * scale, y: 12.25 * scale),\n                         controlPoint2: CGPoint(x: 20.69 * scale, y: 14.86 * scale))\n            path.addLine(to: CGPoint(x: 23 * scale, y: 18.25 * scale))\n            path.addCurve(to: CGPoint(x: 14.25 * scale, y: 10.25 * scale),\n                         controlPoint1: CGPoint(x: 22.54 * scale, y: 13.74 * scale),\n                         controlPoint2: CGPoint(x: 18.9 * scale, y: 10.25 * scale))\n            path.addCurve(to: CGPoint(x: 12.5 * scale, y: 8 * scale),\n                         controlPoint1: CGPoint(x: 13.66 * scale, y: 9.35 * scale),\n                         controlPoint2: CGPoint(x: 13.05 * scale, y: 8.55 * scale))\n            path.close()\n            path.fill()\n        }\n    }\n\n    private static func drawRedoIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 18.4 * scale, y: 10.6 * scale))\n            path.addCurve(to: CGPoint(x: 11.5 * scale, y: 8 * scale),\n                         controlPoint1: CGPoint(x: 16.55 * scale, y: 8.99 * scale),\n                         controlPoint2: CGPoint(x: 14.15 * scale, y: 8 * scale))\n            path.addCurve(to: CGPoint(x: 1.54 * scale, y: 15.22 * scale),\n                         controlPoint1: CGPoint(x: 6.85 * scale, y: 8 * scale),\n                         controlPoint2: CGPoint(x: 2.92 * scale, y: 11.03 * scale))\n            path.addLine(to: CGPoint(x: 3.9 * scale, y: 16 * scale))\n            path.addCurve(to: CGPoint(x: 11.5 * scale, y: 10.5 * scale),\n                         controlPoint1: CGPoint(x: 4.95 * scale, y: 12.81 * scale),\n                         controlPoint2: CGPoint(x: 7.95 * scale, y: 10.5 * scale))\n            path.addCurve(to: CGPoint(x: 16.62 * scale, y: 12.38 * scale),\n                         controlPoint1: CGPoint(x: 13.45 * scale, y: 10.5 * scale),\n                         controlPoint2: CGPoint(x: 15.23 * scale, y: 11.22 * scale))\n            path.addLine(to: CGPoint(x: 13 * scale, y: 16 * scale))\n            path.addLine(to: CGPoint(x: 22 * scale, y: 16 * scale))\n            path.addLine(to: CGPoint(x: 22 * scale, y: 7 * scale))\n            path.close()\n            path.fill()\n        }\n    }\n\n    private static func drawClearFormattingIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Diagonal line\n            path.move(to: CGPoint(x: 3.27 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 2 * scale, y: 6.27 * scale))\n            path.addLine(to: CGPoint(x: 8.97 * scale, y: 13.24 * scale))\n            path.addLine(to: CGPoint(x: 6.5 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 9.5 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 11.07 * scale, y: 15.34 * scale))\n            path.addLine(to: CGPoint(x: 16.73 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 18 * scale, y: 19.73 * scale))\n            path.addLine(to: CGPoint(x: 3.55 * scale, y: 5.27 * scale))\n            path.close()\n\n            // T shape\n            path.move(to: CGPoint(x: 6 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 6 * scale, y: 5.18 * scale))\n            path.addLine(to: CGPoint(x: 8.82 * scale, y: 8 * scale))\n            path.addLine(to: CGPoint(x: 11.22 * scale, y: 8 * scale))\n            path.addLine(to: CGPoint(x: 10.5 * scale, y: 9.68 * scale))\n            path.addLine(to: CGPoint(x: 12.6 * scale, y: 11.78 * scale))\n            path.addLine(to: CGPoint(x: 14.21 * scale, y: 8 * scale))\n            path.addLine(to: CGPoint(x: 20 * scale, y: 8 * scale))\n            path.addLine(to: CGPoint(x: 20 * scale, y: 5 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawIndentIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            // Lines\n            path.move(to: CGPoint(x: 3 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 19 * scale))\n            path.close()\n\n            // Arrow\n            path.move(to: CGPoint(x: 3 * scale, y: 8 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 16 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 12 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 11 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 15 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 3 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 11 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 7 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 11 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 11 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawOutdentIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 11 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 15 * scale))\n            path.close()\n\n            // Arrow pointing left\n            path.move(to: CGPoint(x: 3 * scale, y: 12 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 16 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 8 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 19 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 3 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 11 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 7 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 11 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 11 * scale, y: 11 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawAlignLeftIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 15 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 15 * scale, y: 17 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 15 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 15 * scale, y: 9 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 11 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 19 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 3 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawAlignCenterIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 7 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 15 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 19 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 11 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 7 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 7 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 17 * scale, y: 7 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 3 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    private static func drawAlignRightIcon(color: UIColor, size: CGSize) -> UIImage {\n        return drawIcon(size: size, color: color) { ctx, scale in\n            let path = UIBezierPath()\n            path.move(to: CGPoint(x: 3 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 21 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 19 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 19 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 9 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 17 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 15 * scale))\n            path.addLine(to: CGPoint(x: 9 * scale, y: 15 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 13 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 11 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 11 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 9 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 9 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 7 * scale))\n            path.addLine(to: CGPoint(x: 9 * scale, y: 7 * scale))\n            path.close()\n\n            path.move(to: CGPoint(x: 3 * scale, y: 3 * scale))\n            path.addLine(to: CGPoint(x: 3 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 5 * scale))\n            path.addLine(to: CGPoint(x: 21 * scale, y: 3 * scale))\n            path.close()\n\n            path.fill()\n        }\n    }\n\n    // MARK: - Helper\n\n    private static func drawIcon(size: CGSize, color: UIColor, draw: (CGContext, CGFloat) -> Void) -> UIImage {\n        let renderer = UIGraphicsImageRenderer(size: size)\n        return renderer.image { context in\n            color.setFill()\n            let scale = size.width / 24.0\n            draw(context.cgContext, scale)\n        }\n    }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideo.h",
    "content": "#import <AVFoundation/AVFoundation.h>\n#import \"AVKit/AVKit.h\"\n#import \"UIView+FindUIViewController.h\"\n#import \"CCRCTVideoPlayerViewController.h\"\n#import \"CCRCTVideoPlayerViewControllerDelegate.h\"\n#import <React/RCTComponent.h>\n#import <React/RCTBridgeModule.h>\n\n#if __has_include(<react-native-cometchat-ui-kit/RCTVideoCache.h>)\n#import <RCTVideoCache.h>\n#import <DVAssetLoaderDelegate/DVURLAsset.h>\n#import <DVAssetLoaderDelegate/DVAssetLoaderDelegate.h>\n#endif\n\n@class RCTEventDispatcher;\n#if __has_include(<react-native-cometchat-ui-kit/RCTVideoCache.h>)\n@interface CCRCTVideo : UIView <CCRCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate, AVAssetResourceLoaderDelegate>\n#elif TARGET_OS_TV\n@interface CCRCTVideo : UIView <CCRCTVideoPlayerViewControllerDelegate, AVAssetResourceLoaderDelegate>\n#else\n@interface CCRCTVideo : UIView <CCRCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate, AVAssetResourceLoaderDelegate>\n#endif\n\n@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoError;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;\n@property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;\n@property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;\n@property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;\n@property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;\n@property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;\n@property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;\n@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;\n@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;\n@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;\n@property (nonatomic, copy) RCTDirectEventBlock onGetLicense;\n\ntypedef NS_ENUM(NSInteger, RCTVideoError) {\n    RCTVideoErrorFromJSPart,\n    RCTVideoErrorLicenseRequestNotOk,\n    RCTVideoErrorNoDataFromLicenseRequest,\n    RCTVideoErrorNoSPC,\n    RCTVideoErrorNoDataRequest,\n    RCTVideoErrorNoCertificateData,\n    RCTVideoErrorNoCertificateURL,\n    RCTVideoErrorNoFairplayDRM,\n    RCTVideoErrorNoDRMData\n};\n\n- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;\n\n- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;\n\n- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;\n- (void)setLicenseResult:(NSString * )license;\n- (BOOL)setLicenseResultError:(NSString * )error;\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideo.m",
    "content": "#import <React/RCTConvert.h>\n#import \"CCRCTVideo.h\"\n#import <React/RCTBridgeModule.h>\n#import <React/RCTEventDispatcher.h>\n#import <React/UIView+React.h>\n#include <MediaAccessibility/MediaAccessibility.h>\n#include <AVFoundation/AVFoundation.h>\n\nstatic NSString *const statusKeyPath = @\"status\";\nstatic NSString *const playbackLikelyToKeepUpKeyPath = @\"playbackLikelyToKeepUp\";\nstatic NSString *const playbackBufferEmptyKeyPath = @\"playbackBufferEmpty\";\nstatic NSString *const readyForDisplayKeyPath = @\"readyForDisplay\";\nstatic NSString *const playbackRate = @\"rate\";\nstatic NSString *const timedMetadata = @\"timedMetadata\";\nstatic NSString *const externalPlaybackActive = @\"externalPlaybackActive\";\n\nstatic int const RCTVideoUnset = -1;\n\n#ifdef DEBUG\n    #define DebugLog(...) NSLog(__VA_ARGS__)\n#else\n    #define DebugLog(...) (void)0\n#endif\n\n@implementation CCRCTVideo\n{\n  AVPlayer *_player;\n  AVPlayerItem *_playerItem;\n  NSDictionary *_source;\n  BOOL _playerItemObserversSet;\n  BOOL _playerBufferEmpty;\n  AVPlayerLayer *_playerLayer;\n  BOOL _playerLayerObserverSet;\n  CCRCTVideoPlayerViewController *_playerViewController;\n  NSURL *_videoURL;\n  BOOL _requestingCertificate;\n  BOOL _requestingCertificateErrored;\n  \n  /* DRM */\n  NSDictionary *_drm;\n  AVAssetResourceLoadingRequest *_loadingRequest;\n  \n  /* Required to publish events */\n  RCTEventDispatcher *_eventDispatcher;\n  BOOL _playbackRateObserverRegistered;\n  BOOL _isExternalPlaybackActiveObserverRegistered;\n  BOOL _videoLoadStarted;\n  \n  bool _pendingSeek;\n  float _pendingSeekTime;\n  float _lastSeekTime;\n  \n  /* For sending videoProgress events */\n  Float64 _progressUpdateInterval;\n  BOOL _controls;\n  id _timeObserver;\n  \n  /* Keep track of any modifiers, need to be applied after each play */\n  float _volume;\n  float _rate;\n  float _maxBitRate;\n\n  BOOL _automaticallyWaitsToMinimizeStalling;\n  BOOL _muted;\n  BOOL _paused;\n  BOOL _repeat;\n  BOOL _allowsExternalPlayback;\n  NSArray * _textTracks;\n  NSDictionary * _selectedTextTrack;\n  NSDictionary * _selectedAudioTrack;\n  BOOL _playbackStalled;\n  BOOL _playInBackground;\n  BOOL _preventsDisplaySleepDuringVideoPlayback;\n  float _preferredForwardBufferDuration;\n  BOOL _playWhenInactive;\n  BOOL _pictureInPicture;\n  NSString * _ignoreSilentSwitch;\n  NSString * _mixWithOthers;\n  NSString * _resizeMode;\n  BOOL _fullscreen;\n  BOOL _fullscreenAutorotate;\n  NSString * _fullscreenOrientation;\n  BOOL _fullscreenPlayerPresented;\n  NSString *_filterName;\n  BOOL _filterEnabled;\n  UIViewController * _presentingViewController;\n#if __has_include(<react-native-cometchat-ui-kit/RCTVideoCache.h>)\n  RCTVideoCache * _videoCache;\n#endif\n#if TARGET_OS_IOS\n  void (^__strong _Nonnull _restoreUserInterfaceForPIPStopCompletionHandler)(BOOL);\n  AVPictureInPictureController *_pipController;\n#endif\n}\n\n- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher\n{\n  if ((self = [super init])) {\n    _eventDispatcher = eventDispatcher;\n\t  _automaticallyWaitsToMinimizeStalling = YES;\n    _playbackRateObserverRegistered = NO;\n    _isExternalPlaybackActiveObserverRegistered = NO;\n    _playbackStalled = NO;\n    _rate = 1.0;\n    _volume = 1.0;\n    _resizeMode = @\"AVLayerVideoGravityResizeAspectFill\";\n    _fullscreenAutorotate = YES;\n    _fullscreenOrientation = @\"all\";\n    _pendingSeek = false;\n    _pendingSeekTime = 0.0f;\n    _lastSeekTime = 0.0f;\n    _progressUpdateInterval = 250;\n    _controls = NO;\n    _playerBufferEmpty = YES;\n    _playInBackground = false;\n    _preventsDisplaySleepDuringVideoPlayback = true;\n    _preferredForwardBufferDuration = 0.0f;\n    _allowsExternalPlayback = YES;\n    _playWhenInactive = false;\n    _pictureInPicture = false;\n    _ignoreSilentSwitch = @\"inherit\"; // inherit, ignore, obey\n    _mixWithOthers = @\"inherit\"; // inherit, mix, duck\n#if TARGET_OS_IOS\n    _restoreUserInterfaceForPIPStopCompletionHandler = NULL;\n#endif\n#if __has_include(<react-native-cometchat-ui-kit/RCTVideoCache.h>)\n    _videoCache = [RCTVideoCache sharedInstance];\n#endif\n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(applicationWillResignActive:)\n                                                 name:UIApplicationWillResignActiveNotification\n                                               object:nil];\n    \n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(applicationDidEnterBackground:)\n                                                 name:UIApplicationDidEnterBackgroundNotification\n                                               object:nil];\n    \n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(applicationWillEnterForeground:)\n                                                 name:UIApplicationWillEnterForegroundNotification\n                                               object:nil];\n    \n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(audioRouteChanged:)\n                                                 name:AVAudioSessionRouteChangeNotification\n                                               object:nil];\n  }\n  \n  return self;\n}\n\n- (CCRCTVideoPlayerViewController*)createPlayerViewController:(AVPlayer*)player\n                                             withPlayerItem:(AVPlayerItem*)playerItem {\n  CCRCTVideoPlayerViewController* viewController = [[CCRCTVideoPlayerViewController alloc] init];\n  viewController.showsPlaybackControls = YES;\n  viewController.rctDelegate = self;\n  viewController.preferredOrientation = _fullscreenOrientation;\n  \n  viewController.view.frame = self.bounds;\n  viewController.player = player;\n  return viewController;\n}\n\n/* ---------------------------------------------------------\n **  Get the duration for a AVPlayerItem.\n ** ------------------------------------------------------- */\n\n- (CMTime)playerItemDuration\n{\n  AVPlayerItem *playerItem = [_player currentItem];\n  if (playerItem.status == AVPlayerItemStatusReadyToPlay)\n  {\n    return([playerItem duration]);\n  }\n  \n  return(kCMTimeInvalid);\n}\n\n- (CMTimeRange)playerItemSeekableTimeRange\n{\n  AVPlayerItem *playerItem = [_player currentItem];\n  if (playerItem.status == AVPlayerItemStatusReadyToPlay)\n  {\n    return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue;\n  }\n  \n  return (kCMTimeRangeZero);\n}\n\n-(void)addPlayerTimeObserver\n{\n  const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000;\n  // @see endScrubbing in AVPlayerDemoPlaybackViewController.m\n  // of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html\n  __weak CCRCTVideo *weakSelf = self;\n  _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC)\n                                                        queue:NULL\n                                                   usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; }\n                   ];\n}\n\n/* Cancels the previously registered time observer. */\n-(void)removePlayerTimeObserver\n{\n  if (_timeObserver)\n  {\n    [_player removeTimeObserver:_timeObserver];\n    _timeObserver = nil;\n  }\n}\n\n#pragma mark - Progress\n\n- (void)dealloc\n{\n  [[NSNotificationCenter defaultCenter] removeObserver:self];\n  [self removePlayerLayer];\n  [self removePlayerItemObservers];\n  [_player removeObserver:self forKeyPath:playbackRate context:nil];\n  [_player removeObserver:self forKeyPath:externalPlaybackActive context: nil];\n}\n\n#pragma mark - App lifecycle handlers\n\n- (void)applicationWillResignActive:(NSNotification *)notification\n{\n  if (_playInBackground || _playWhenInactive || _paused) return;\n  \n  [_player pause];\n  [_player setRate:0.0];\n}\n\n- (void)applicationDidEnterBackground:(NSNotification *)notification\n{\n  if (_playInBackground) {\n    // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html\n    [_playerLayer setPlayer:nil];\n    [_playerViewController setPlayer:nil];\n  }\n}\n\n- (void)applicationWillEnterForeground:(NSNotification *)notification\n{\n  [self applyModifiers];\n  if (_playInBackground) {\n    [_playerLayer setPlayer:_player];\n    [_playerViewController setPlayer:_player];\n  }\n}\n\n#pragma mark - Audio events\n\n- (void)audioRouteChanged:(NSNotification *)notification\n{\n  NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey];\n  NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];\n  if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {\n    self.onVideoAudioBecomingNoisy(@{@\"target\": self.reactTag});\n  }\n}\n\n#pragma mark - Progress\n\n- (void)sendProgressUpdate\n{\n  AVPlayerItem *video = [_player currentItem];\n  if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) {\n    return;\n  }\n  \n  CMTime playerDuration = [self playerItemDuration];\n  if (CMTIME_IS_INVALID(playerDuration)) {\n    return;\n  }\n  \n  CMTime currentTime = _player.currentTime;\n  NSDate *currentPlaybackTime = _player.currentItem.currentDate;\n  const Float64 duration = CMTimeGetSeconds(playerDuration);\n  const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);\n  \n  [[NSNotificationCenter defaultCenter] postNotificationName:@\"RCTVideo_progress\" object:nil userInfo:@{@\"progress\": [NSNumber numberWithDouble: currentTimeSecs / duration]}];\n  \n  if( currentTimeSecs >= 0 && self.onVideoProgress) {\n    self.onVideoProgress(@{\n                           @\"currentTime\": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],\n                           @\"playableDuration\": [self calculatePlayableDuration],\n                           @\"atValue\": [NSNumber numberWithLongLong:currentTime.value],\n                           @\"atTimescale\": [NSNumber numberWithInt:currentTime.timescale],\n                           @\"currentPlaybackTime\": [NSNumber numberWithLongLong:[@(floor([currentPlaybackTime timeIntervalSince1970] * 1000)) longLongValue]],\n                           @\"target\": self.reactTag,\n                           @\"seekableDuration\": [self calculateSeekableDuration],\n                           });\n  }\n}\n\n/*!\n * Calculates and returns the playable duration of the current player item using its loaded time ranges.\n *\n * \\returns The playable duration of the current player item in seconds.\n */\n- (NSNumber *)calculatePlayableDuration\n{\n  AVPlayerItem *video = _player.currentItem;\n  if (video.status == AVPlayerItemStatusReadyToPlay) {\n    __block CMTimeRange effectiveTimeRange;\n    [video.loadedTimeRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {\n      CMTimeRange timeRange = [obj CMTimeRangeValue];\n      if (CMTimeRangeContainsTime(timeRange, video.currentTime)) {\n        effectiveTimeRange = timeRange;\n        *stop = YES;\n      }\n    }];\n    Float64 playableDuration = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange));\n    if (playableDuration > 0) {\n      return [NSNumber numberWithFloat:playableDuration];\n    }\n  }\n  return [NSNumber numberWithInteger:0];\n}\n\n- (NSNumber *)calculateSeekableDuration\n{\n  CMTimeRange timeRange = [self playerItemSeekableTimeRange];\n  if (CMTIME_IS_NUMERIC(timeRange.duration))\n  {\n    return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)];\n  }\n  return [NSNumber numberWithInteger:0];\n}\n\n- (void)addPlayerItemObservers\n{\n  [_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil];\n  [_playerItem addObserver:self forKeyPath:playbackBufferEmptyKeyPath options:0 context:nil];\n  [_playerItem addObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath options:0 context:nil];\n  [_playerItem addObserver:self forKeyPath:timedMetadata options:NSKeyValueObservingOptionNew context:nil];\n  _playerItemObserversSet = YES;\n}\n\n/* Fixes https://github.com/brentvatne/react-native-video/issues/43\n * Crashes caused when trying to remove the observer when there is no\n * observer set */\n- (void)removePlayerItemObservers\n{\n  if (_playerItemObserversSet) {\n    [_playerItem removeObserver:self forKeyPath:statusKeyPath];\n    [_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath];\n    [_playerItem removeObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath];\n    [_playerItem removeObserver:self forKeyPath:timedMetadata];\n    _playerItemObserversSet = NO;\n  }\n}\n\n#pragma mark - Player and source\n\n- (void)setSrc:(NSDictionary *)source\n{\n  _source = source;\n  [self removePlayerLayer];\n  [self removePlayerTimeObserver];\n  [self removePlayerItemObservers];\n  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) 0), dispatch_get_main_queue(), ^{\n    \n    // perform on next run loop, otherwise other passed react-props may not be set\n    [self playerItemForSource:self->_source withCallback:^(AVPlayerItem * playerItem) {\n      self->_playerItem = playerItem;\n      _playerItem = playerItem;\n      [self setPreferredForwardBufferDuration:_preferredForwardBufferDuration];\n      [self addPlayerItemObservers];\n      [self setFilter:self->_filterName];\n      [self setMaxBitRate:self->_maxBitRate];\n      \n      [_player pause];\n        \n      if (_playbackRateObserverRegistered) {\n        [_player removeObserver:self forKeyPath:playbackRate context:nil];\n        _playbackRateObserverRegistered = NO;\n      }\n      if (self->_isExternalPlaybackActiveObserverRegistered) {\n        [self->_player removeObserver:self forKeyPath:externalPlaybackActive context:nil];\n        self->_isExternalPlaybackActiveObserverRegistered = NO;\n      }\n      \n      self->_player = [AVPlayer playerWithPlayerItem:self->_playerItem];\n      self->_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;\n      \n      [self->_player addObserver:self forKeyPath:playbackRate options:0 context:nil];\n      self->_playbackRateObserverRegistered = YES;\n      \n      [self->_player addObserver:self forKeyPath:externalPlaybackActive options:0 context:nil];\n      self->_isExternalPlaybackActiveObserverRegistered = YES;\n      \n      [self addPlayerTimeObserver];\n      if (@available(iOS 10.0, *)) {\n        [self setAutomaticallyWaitsToMinimizeStalling:_automaticallyWaitsToMinimizeStalling];\n      }\n\n      //Perform on next run loop, otherwise onVideoLoadStart is nil\n      if (self.onVideoLoadStart) {\n        id uri = [self->_source objectForKey:@\"uri\"];\n        id type = [self->_source objectForKey:@\"type\"];\n        self.onVideoLoadStart(@{@\"src\": @{\n                                    @\"uri\": uri ? uri : [NSNull null],\n                                    @\"type\": type ? type : [NSNull null],\n                                    @\"isNetwork\": [NSNumber numberWithBool:(bool)[self->_source objectForKey:@\"isNetwork\"]]},\n                                @\"drm\": self->_drm ? self->_drm : [NSNull null],\n                                @\"target\": self.reactTag\n                                });\n      }\n    }];\n  });\n  _videoLoadStarted = YES;\n}\n\n- (void)setDrm:(NSDictionary *)drm {\n  _drm = drm;\n}\n\n- (NSURL*) urlFilePath:(NSString*) filepath {\n  if ([filepath containsString:@\"file://\"]) {\n    return [NSURL URLWithString:filepath];\n  }\n  \n  // if no file found, check if the file exists in the Document directory\n  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n  NSString* relativeFilePath = [filepath lastPathComponent];\n  // the file may be multiple levels below the documents directory\n  NSArray* fileComponents = [filepath componentsSeparatedByString:@\"Documents/\"];\n  if (fileComponents.count > 1) {\n    relativeFilePath = [fileComponents objectAtIndex:1];\n  }\n  \n  NSString *path = [paths.firstObject stringByAppendingPathComponent:relativeFilePath];\n  if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {\n    return [NSURL fileURLWithPath:path];\n  }\n  return nil;\n}\n\n- (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler\n{\n  if (!_textTracks || _textTracks.count==0) {\n    handler([AVPlayerItem playerItemWithAsset:asset]);\n    return;\n  }\n  \n  // AVPlayer can't airplay AVMutableCompositions\n  _allowsExternalPlayback = NO;\n  \n  // sideload text tracks\n  AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];\n  \n  AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;\n  AVMutableCompositionTrack *videoCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];\n  [videoCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)\n                          ofTrack:videoAsset\n                           atTime:kCMTimeZero\n                            error:nil];\n  \n  AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;\n  AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];\n  [audioCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)\n                          ofTrack:audioAsset\n                           atTime:kCMTimeZero\n                            error:nil];\n  \n  NSMutableArray* validTextTracks = [NSMutableArray array];\n  for (int i = 0; i < _textTracks.count; ++i) {\n    AVURLAsset *textURLAsset;\n    NSString *textUri = [_textTracks objectAtIndex:i][@\"uri\"];\n    if ([[textUri lowercaseString] hasPrefix:@\"http\"]) {\n      textURLAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:textUri] options:assetOptions];\n    } else {\n      textURLAsset = [AVURLAsset URLAssetWithURL:[self urlFilePath:textUri] options:nil];\n    }\n    AVAssetTrack *textTrackAsset = [textURLAsset tracksWithMediaType:AVMediaTypeText].firstObject;\n    if (!textTrackAsset) continue; // fix when there's no textTrackAsset\n    [validTextTracks addObject:[_textTracks objectAtIndex:i]];\n    AVMutableCompositionTrack *textCompTrack = [mixComposition\n                                                addMutableTrackWithMediaType:AVMediaTypeText\n                                                preferredTrackID:kCMPersistentTrackID_Invalid];\n    [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)\n                           ofTrack:textTrackAsset\n                            atTime:kCMTimeZero\n                             error:nil];\n  }\n  if (validTextTracks.count != _textTracks.count) {\n    [self setTextTracks:validTextTracks];\n  }\n  \n  handler([AVPlayerItem playerItemWithAsset:mixComposition]);\n}\n\n- (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler\n{\n  bool isNetwork = [RCTConvert BOOL:[source objectForKey:@\"isNetwork\"]];\n  bool isAsset = [RCTConvert BOOL:[source objectForKey:@\"isAsset\"]];\n  bool shouldCache = [RCTConvert BOOL:[source objectForKey:@\"shouldCache\"]];\n  NSString *uri = [source objectForKey:@\"uri\"];\n  NSString *type = [source objectForKey:@\"type\"];\n  AVURLAsset *asset;\n  if (!uri || [uri isEqualToString:@\"\"]) {\n    DebugLog(@\"Could not find video URL in source '%@'\", source);\n    return;\n  }\n  \n  NSURL *url = isNetwork || isAsset\n    ? [NSURL URLWithString:uri]\n    : [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]];\n  NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init];\n  \n  if (isNetwork) {\n    NSDictionary *headers = [source objectForKey:@\"requestHeaders\"];\n    if ([headers count] > 0) {\n      [assetOptions setObject:headers forKey:@\"AVURLAssetHTTPHeaderFieldsKey\"];\n    }\n    NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];\n    [assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey];\n    \n#if __has_include(<react-native-cometchat-ui-kit/RCTVideoCache.h>)\n    if (shouldCache && (!_textTracks || !_textTracks.count)) {\n      /* The DVURLAsset created by cache doesn't have a tracksWithMediaType property, so trying\n       * to bring in the text track code will crash. I suspect this is because the asset hasn't fully loaded.\n       * Until this is fixed, we need to bypass caching when text tracks are specified.\n       */\n      DebugLog(@\"Caching is not supported for uri '%@' because text tracks are not compatible with the cache. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md\", uri);\n      [self playerItemForSourceUsingCache:uri assetOptions:assetOptions withCallback:handler];\n      return;\n    }\n#endif\n    \n    asset = [AVURLAsset URLAssetWithURL:url options:assetOptions];\n  } else if (isAsset) {\n    asset = [AVURLAsset URLAssetWithURL:url options:nil];\n  } else {\n    asset = [AVURLAsset URLAssetWithURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]] options:nil];\n  }\n  // Reset _loadingRequest\n  if (_loadingRequest != nil) {\n    [_loadingRequest finishLoading];\n  }\n  _requestingCertificate = NO;\n  _requestingCertificateErrored = NO;\n  // End Reset _loadingRequest\n  if (self->_drm != nil) {\n    dispatch_queue_t queue = dispatch_queue_create(\"assetQueue\", nil);\n    [asset.resourceLoader setDelegate:self queue:queue];\n  }\n  \n  [self playerItemPrepareText:asset assetOptions:assetOptions withCallback:handler];\n}\n\n#if __has_include(<react-native-cometchat-ui-kit/RCTVideoCache.h>)\n\n- (void)playerItemForSourceUsingCache:(NSString *)uri assetOptions:(NSDictionary *)options withCallback:(void(^)(AVPlayerItem *))handler {\n  NSURL *url = [NSURL URLWithString:uri];\n  [_videoCache getItemForUri:uri withCallback:^(RCTVideoCacheStatus videoCacheStatus, AVAsset * _Nullable cachedAsset) {\n    switch (videoCacheStatus) {\n      case RCTVideoCacheStatusMissingFileExtension: {\n        DebugLog(@\"Could not generate cache key for uri '%@'. It is currently not supported to cache urls that do not include a file extension. The video file will not be cached. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md\", uri);\n        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];\n        [self playerItemPrepareText:asset assetOptions:options withCallback:handler];\n        return;\n      }\n      case RCTVideoCacheStatusUnsupportedFileExtension: {\n        DebugLog(@\"Could not generate cache key for uri '%@'. The file extension of that uri is currently not supported. The video file will not be cached. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md\", uri);\n        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];\n        [self playerItemPrepareText:asset assetOptions:options withCallback:handler];\n        return;\n      }\n      default:\n        if (cachedAsset) {\n          DebugLog(@\"Playing back uri '%@' from cache\", uri);\n          // See note in playerItemForSource about not being able to support text tracks & caching\n          handler([AVPlayerItem playerItemWithAsset:cachedAsset]);\n          return;\n        }\n    }\n    \n    DVURLAsset *asset = [[DVURLAsset alloc] initWithURL:url options:options networkTimeout:10000];\n    asset.loaderDelegate = self;\n    \n    /* More granular code to have control over the DVURLAsset\n     DVAssetLoaderDelegate *resourceLoaderDelegate = [[DVAssetLoaderDelegate alloc] initWithURL:url];\n     resourceLoaderDelegate.delegate = self;\n     NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];\n     components.scheme = [DVAssetLoaderDelegate scheme];\n     AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[components URL] options:options];\n     [asset.resourceLoader setDelegate:resourceLoaderDelegate queue:dispatch_get_main_queue()];\n     */\n    \n    handler([AVPlayerItem playerItemWithAsset:asset]);\n  }];\n}\n\n#pragma mark - DVAssetLoaderDelegate\n\n- (void)dvAssetLoaderDelegate:(DVAssetLoaderDelegate *)loaderDelegate\n                  didLoadData:(NSData *)data\n                       forURL:(NSURL *)url {\n  [_videoCache storeItem:data forUri:[url absoluteString] withCallback:^(BOOL success) {\n    DebugLog(@\"Cache data stored successfully 🎉\");\n  }];\n}\n\n#endif\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context\n{\n\n  if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {\n    self.onReadyForDisplay(@{@\"target\": self.reactTag});\n    return;\n  }\n  if (object == _playerItem) {\n    // When timeMetadata is read the event onTimedMetadata is triggered\n    if ([keyPath isEqualToString:timedMetadata]) {\n      NSArray<AVMetadataItem *> *items = [change objectForKey:@\"new\"];\n      if (items && ![items isEqual:[NSNull null]] && items.count > 0) {\n        NSMutableArray *array = [NSMutableArray new];\n        for (AVMetadataItem *item in items) {\n          NSString *value = (NSString *)item.value;\n          NSString *identifier = item.identifier;\n          \n          if (![value isEqual: [NSNull null]]) {\n            NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@\"value\", @\"identifier\"]];\n            \n            [array addObject:dictionary];\n          }\n        }\n        \n        self.onTimedMetadata(@{\n                               @\"target\": self.reactTag,\n                               @\"metadata\": array\n                               });\n      }\n    }\n    \n    if ([keyPath isEqualToString:statusKeyPath]) {\n      // Handle player item status change.\n      if (_playerItem.status == AVPlayerItemStatusReadyToPlay) {\n        float duration = CMTimeGetSeconds(_playerItem.asset.duration);\n        \n        if (isnan(duration)) {\n          duration = 0.0;\n        }\n        \n        NSObject *width = @\"undefined\";\n        NSObject *height = @\"undefined\";\n        NSString *orientation = @\"undefined\";\n        \n        if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) {\n          AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];\n          width = [NSNumber numberWithFloat:videoTrack.naturalSize.width];\n          height = [NSNumber numberWithFloat:videoTrack.naturalSize.height];\n          CGAffineTransform preferredTransform = [videoTrack preferredTransform];\n          \n          if ((videoTrack.naturalSize.width == preferredTransform.tx\n               && videoTrack.naturalSize.height == preferredTransform.ty)\n              || (preferredTransform.tx == 0 && preferredTransform.ty == 0))\n          {\n            orientation = @\"landscape\";\n          } else {\n            orientation = @\"portrait\";\n          }\n        } else if (_playerItem.presentationSize.height) {\n          width = [NSNumber numberWithFloat:_playerItem.presentationSize.width];\n          height = [NSNumber numberWithFloat:_playerItem.presentationSize.height];\n          orientation = _playerItem.presentationSize.width > _playerItem.presentationSize.height ? @\"landscape\" : @\"portrait\";\n        }\n\n        if (_pendingSeek) {\n          [self setCurrentTime:_pendingSeekTime];\n          _pendingSeek = false;\n        }\n        \n        if (self.onVideoLoad && _videoLoadStarted) {\n          self.onVideoLoad(@{@\"duration\": [NSNumber numberWithFloat:duration],\n                             @\"currentTime\": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],\n                             @\"canPlayReverse\": [NSNumber numberWithBool:_playerItem.canPlayReverse],\n                             @\"canPlayFastForward\": [NSNumber numberWithBool:_playerItem.canPlayFastForward],\n                             @\"canPlaySlowForward\": [NSNumber numberWithBool:_playerItem.canPlaySlowForward],\n                             @\"canPlaySlowReverse\": [NSNumber numberWithBool:_playerItem.canPlaySlowReverse],\n                             @\"canStepBackward\": [NSNumber numberWithBool:_playerItem.canStepBackward],\n                             @\"canStepForward\": [NSNumber numberWithBool:_playerItem.canStepForward],\n                             @\"naturalSize\": @{\n                                 @\"width\": width,\n                                 @\"height\": height,\n                                 @\"orientation\": orientation\n                                 },\n                             @\"audioTracks\": [self getAudioTrackInfo],\n                             @\"textTracks\": [self getTextTrackInfo],\n                             @\"target\": self.reactTag});\n        }\n        _videoLoadStarted = NO;\n        \n        [self attachListeners];\n        [self applyModifiers];\n      } else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {\n        self.onVideoError(@{@\"error\": @{@\"code\": [NSNumber numberWithInteger: _playerItem.error.code],\n                                        @\"localizedDescription\": [_playerItem.error localizedDescription] == nil ? @\"\" : [_playerItem.error localizedDescription],\n                                        @\"localizedFailureReason\": [_playerItem.error localizedFailureReason] == nil ? @\"\" : [_playerItem.error localizedFailureReason],\n                                        @\"localizedRecoverySuggestion\": [_playerItem.error localizedRecoverySuggestion] == nil ? @\"\" : [_playerItem.error localizedRecoverySuggestion],\n                                        @\"domain\": _playerItem != nil && _playerItem.error != nil ? _playerItem.error.domain : @\"RTCVideo\"},\n                            @\"target\": self.reactTag});\n      }\n    } else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) {\n      _playerBufferEmpty = YES;\n      self.onVideoBuffer(@{@\"isBuffering\": @(YES), @\"target\": self.reactTag});\n    } else if ([keyPath isEqualToString:playbackLikelyToKeepUpKeyPath]) {\n      // Continue playing (or not if paused) after being paused due to hitting an unbuffered zone.\n      if ((!(_controls || _fullscreenPlayerPresented) || _playerBufferEmpty) && _playerItem.playbackLikelyToKeepUp) {\n        [self setPaused:_paused];\n      }\n      _playerBufferEmpty = NO;\n      self.onVideoBuffer(@{@\"isBuffering\": @(NO), @\"target\": self.reactTag});\n    }\n  } else if (object == _player) {\n    if([keyPath isEqualToString:playbackRate]) {\n      if (_player.rate > 0 && _rate > 0 && _player.rate != _rate) {\n        // Playback is resuming, apply rate modifer.\n        [_player setRate:_rate];\n      } else if(self.onPlaybackRateChange) {\n        self.onPlaybackRateChange(@{@\"playbackRate\": [NSNumber numberWithFloat:_player.rate],\n                                    @\"target\": self.reactTag});\n      }\n      if(_playbackStalled && _player.rate > 0) {\n        if(self.onPlaybackResume) {\n          self.onPlaybackResume(@{@\"playbackRate\": [NSNumber numberWithFloat:_player.rate],\n                                  @\"target\": self.reactTag});\n        }\n        _playbackStalled = NO;\n      }\n    }\n    else if([keyPath isEqualToString:externalPlaybackActive]) {\n      if(self.onVideoExternalPlaybackChange) {\n        self.onVideoExternalPlaybackChange(@{@\"isExternalPlaybackActive\": [NSNumber numberWithBool:_player.isExternalPlaybackActive],\n                                             @\"target\": self.reactTag});\n      }\n    }\n  } else if (object == _playerViewController.contentOverlayView) {\n      // when controls==true, this is a hack to reset the rootview when rotation happens in fullscreen\n      if ([keyPath isEqualToString:@\"frame\"]) {\n\n        CGRect oldRect = [change[NSKeyValueChangeOldKey] CGRectValue];\n        CGRect newRect = [change[NSKeyValueChangeNewKey] CGRectValue];\n\n        if (!CGRectEqualToRect(oldRect, newRect)) {\n          if (CGRectEqualToRect(newRect, [UIScreen mainScreen].bounds)) {\n            NSLog(@\"in fullscreen\");\n\n            [self.reactViewController.view setFrame:[UIScreen mainScreen].bounds];\n            [self.reactViewController.view setNeedsLayout];\n          } else NSLog(@\"not fullscreen\");\n        }\n\n        return;\n      }\n  }\n}\n\n- (void)attachListeners\n{\n  // listen for end of file\n  [[NSNotificationCenter defaultCenter] removeObserver:self\n                                                  name:AVPlayerItemDidPlayToEndTimeNotification\n                                                object:[_player currentItem]];\n  [[NSNotificationCenter defaultCenter] addObserver:self\n                                           selector:@selector(playerItemDidReachEnd:)\n                                               name:AVPlayerItemDidPlayToEndTimeNotification\n                                             object:[_player currentItem]];\n  \n  [[NSNotificationCenter defaultCenter] removeObserver:self\n                                                  name:AVPlayerItemPlaybackStalledNotification\n                                                object:nil];\n  [[NSNotificationCenter defaultCenter] addObserver:self\n                                           selector:@selector(playbackStalled:)\n                                               name:AVPlayerItemPlaybackStalledNotification\n                                             object:nil];\n  \n  [[NSNotificationCenter defaultCenter] removeObserver:self\n                                                  name:AVPlayerItemNewAccessLogEntryNotification\n                                                object:nil];\n  [[NSNotificationCenter defaultCenter] addObserver:self\n                                           selector:@selector(handleAVPlayerAccess:)\n                                               name:AVPlayerItemNewAccessLogEntryNotification\n                                             object:nil];\n  [[NSNotificationCenter defaultCenter] removeObserver:self\n                                                  name: AVPlayerItemFailedToPlayToEndTimeNotification\n                                                object:nil];\n  [[NSNotificationCenter defaultCenter] addObserver:self\n                                           selector:@selector(didFailToFinishPlaying:)\n                                               name: AVPlayerItemFailedToPlayToEndTimeNotification\n                                             object:nil];\n  \n}\n\n- (void)handleAVPlayerAccess:(NSNotification *)notification {\n  AVPlayerItemAccessLog *accessLog = [((AVPlayerItem *)notification.object) accessLog];\n  AVPlayerItemAccessLogEvent *lastEvent = accessLog.events.lastObject;\n  \n  /* TODO: get this working\n   if (self.onBandwidthUpdate) {\n   self.onBandwidthUpdate(@{@\"bitrate\": [NSNumber numberWithFloat:lastEvent.observedBitrate]});\n   }\n   */\n}\n\n- (void)didFailToFinishPlaying:(NSNotification *)notification {\n  NSError *error = notification.userInfo[AVPlayerItemFailedToPlayToEndTimeErrorKey];\n  self.onVideoError(@{@\"error\": @{@\"code\": [NSNumber numberWithInteger: error.code],\n                                  @\"localizedDescription\": [error localizedDescription] == nil ? @\"\" : [error localizedDescription],\n                                  @\"localizedFailureReason\": [error localizedFailureReason] == nil ? @\"\" : [error localizedFailureReason],\n                                  @\"localizedRecoverySuggestion\": [error localizedRecoverySuggestion] == nil ? @\"\" : [error localizedRecoverySuggestion],\n                                  @\"domain\": error.domain},\n                      @\"target\": self.reactTag});\n}\n\n- (void)playbackStalled:(NSNotification *)notification\n{\n  if(self.onPlaybackStalled) {\n    self.onPlaybackStalled(@{@\"target\": self.reactTag});\n  }\n  _playbackStalled = YES;\n}\n\n- (void)playerItemDidReachEnd:(NSNotification *)notification\n{\n  if(self.onVideoEnd) {\n    self.onVideoEnd(@{@\"target\": self.reactTag});\n  }\n  \n  if (_repeat) {\n    AVPlayerItem *item = [notification object];\n    [item seekToTime:kCMTimeZero];\n    [self applyModifiers];\n  } else {\n    [self removePlayerTimeObserver];\n  }\n}\n\n#pragma mark - Prop setters\n\n- (void)setResizeMode:(NSString*)mode\n{\n  if( _controls )\n  {\n    _playerViewController.videoGravity = mode;\n  }\n  else\n  {\n    _playerLayer.videoGravity = mode;\n  }\n  _resizeMode = mode;\n}\n\n- (void)setPlayInBackground:(BOOL)playInBackground\n{\n  _playInBackground = playInBackground;\n}\n\n- (void)setPreventsDisplaySleepDuringVideoPlayback:(BOOL)preventsDisplaySleepDuringVideoPlayback\n{\n    _preventsDisplaySleepDuringVideoPlayback = preventsDisplaySleepDuringVideoPlayback;\n    [self applyModifiers];\n}\n\n- (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback\n{\n  _allowsExternalPlayback = allowsExternalPlayback;\n  _player.allowsExternalPlayback = _allowsExternalPlayback;\n}\n\n- (void)setPlayWhenInactive:(BOOL)playWhenInactive\n{\n  _playWhenInactive = playWhenInactive;\n}\n\n- (void)setPictureInPicture:(BOOL)pictureInPicture\n{\n  #if TARGET_OS_IOS\n  if (_pictureInPicture == pictureInPicture) {\n    return;\n  }\n  \n  _pictureInPicture = pictureInPicture;\n  if (_pipController && _pictureInPicture && ![_pipController isPictureInPictureActive]) {\n    dispatch_async(dispatch_get_main_queue(), ^{\n      [_pipController startPictureInPicture];\n    });\n  } else if (_pipController && !_pictureInPicture && [_pipController isPictureInPictureActive]) {\n    dispatch_async(dispatch_get_main_queue(), ^{\n      [_pipController stopPictureInPicture];\n    });\n  }\n  #endif\n}\n\n#if TARGET_OS_IOS\n- (void)setRestoreUserInterfaceForPIPStopCompletionHandler:(BOOL)restore\n{\n  if (_restoreUserInterfaceForPIPStopCompletionHandler != NULL) {\n    _restoreUserInterfaceForPIPStopCompletionHandler(restore);\n    _restoreUserInterfaceForPIPStopCompletionHandler = NULL;\n  }\n}\n\n- (void)setupPipController {\n  if (!_pipController && _playerLayer && [AVPictureInPictureController isPictureInPictureSupported]) {\n    // Create new controller passing reference to the AVPlayerLayer\n    _pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:_playerLayer];\n    _pipController.delegate = self;\n  }\n}\n#endif\n\n- (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch\n{\n  _ignoreSilentSwitch = ignoreSilentSwitch;\n  [self configureAudio];\n  [self applyModifiers];\n}\n\n- (void)setMixWithOthers:(NSString *)mixWithOthers\n{\n  _mixWithOthers = mixWithOthers;\n  [self applyModifiers];\n}\n\n- (void)setPaused:(BOOL)paused\n{\n  if (paused) {\n    [_player pause];\n    [_player setRate:0.0];\n  } else {\n\n    [self configureAudio];\n\n    if (@available(iOS 10.0, *) && !_automaticallyWaitsToMinimizeStalling) {\n      [_player playImmediatelyAtRate:_rate];\n    } else {\n      [_player play];\n      [_player setRate:_rate];\n    }\n    [_player setRate:_rate];\n  }\n  \n  _paused = paused;\n}\n\n- (float)getCurrentTime\n{\n  return _playerItem != NULL ? CMTimeGetSeconds(_playerItem.currentTime) : 0;\n}\n\n- (void)setCurrentTime:(float)currentTime\n{\n  NSDictionary *info = @{\n                         @\"time\": [NSNumber numberWithFloat:currentTime],\n                         @\"tolerance\": [NSNumber numberWithInt:100]\n                         };\n  [self setSeek:info];\n}\n\n- (void)setSeek:(NSDictionary *)info\n{\n  NSNumber *seekTime = info[@\"time\"];\n  NSNumber *seekTolerance = info[@\"tolerance\"];\n  \n  int timeScale = 1000;\n  \n  AVPlayerItem *item = _player.currentItem;\n  if (item && item.status == AVPlayerItemStatusReadyToPlay) {\n    // TODO check loadedTimeRanges\n    \n    CMTime cmSeekTime = CMTimeMakeWithSeconds([seekTime floatValue], timeScale);\n    CMTime current = item.currentTime;\n    // TODO figure out a good tolerance level\n    CMTime tolerance = CMTimeMake([seekTolerance floatValue], timeScale);\n    BOOL wasPaused = _paused;\n    \n    if (CMTimeCompare(current, cmSeekTime) != 0) {\n      if (!wasPaused) [_player pause];\n      [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) {\n        if (!_timeObserver) {\n          [self addPlayerTimeObserver];\n        }\n        if (!wasPaused) {\n          [self setPaused:false];\n        }\n        if(self.onVideoSeek) {\n          self.onVideoSeek(@{@\"currentTime\": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)],\n                             @\"seekTime\": seekTime,\n                             @\"target\": self.reactTag});\n        }\n      }];\n      \n      _pendingSeek = false;\n    }\n    \n  } else {\n    _pendingSeek = true;\n    _pendingSeekTime = [seekTime floatValue];\n  }\n}\n\n- (void)setRate:(float)rate\n{\n  _rate = rate;\n  [self applyModifiers];\n}\n\n- (void)setMuted:(BOOL)muted\n{\n  _muted = muted;\n  [self applyModifiers];\n}\n\n- (void)setVolume:(float)volume\n{\n  _volume = volume;\n  [self applyModifiers];\n}\n\n- (void)setMaxBitRate:(float) maxBitRate {\n  _maxBitRate = maxBitRate;\n  _playerItem.preferredPeakBitRate = maxBitRate;\n}\n\n- (void)setPreferredForwardBufferDuration:(float) preferredForwardBufferDuration\n{\n  _preferredForwardBufferDuration = preferredForwardBufferDuration;\n  _playerItem.preferredForwardBufferDuration = preferredForwardBufferDuration;\n}\n\n- (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits\n{\n\t_automaticallyWaitsToMinimizeStalling = waits;\n\t_player.automaticallyWaitsToMinimizeStalling = waits;\n}\n\n\n- (void)applyModifiers\n{\n  if (_muted) {\n    if (!_controls) {\n      [_player setVolume:0];\n    }\n    [_player setMuted:YES];\n  } else {\n    [_player setVolume:_volume];\n    [_player setMuted:NO];\n  }\n\n  if (@available(iOS 12.0, *)) {\n      self->_player.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback;\n  } else {\n      // Fallback on earlier versions\n  }\n  \n  [self setMaxBitRate:_maxBitRate];\n  [self setSelectedAudioTrack:_selectedAudioTrack];\n  [self setSelectedTextTrack:_selectedTextTrack];\n  [self setResizeMode:_resizeMode];\n  [self setRepeat:_repeat];\n  [self setPaused:_paused];\n  [self setControls:_controls];\n  [self setAllowsExternalPlayback:_allowsExternalPlayback];\n}\n\n- (void)configureAudio\n{\n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    AVAudioSessionCategory category = nil;\n    AVAudioSessionCategoryOptions options = nil;\n\n    if([_ignoreSilentSwitch isEqualToString:@\"ignore\"]) {\n      category = AVAudioSessionCategoryPlayback;\n    } else if([_ignoreSilentSwitch isEqualToString:@\"obey\"]) {\n      category = AVAudioSessionCategoryAmbient;\n    }\n\n    if([_mixWithOthers isEqualToString:@\"mix\"]) {\n      options = AVAudioSessionCategoryOptionMixWithOthers;\n    } else if([_mixWithOthers isEqualToString:@\"duck\"]) {\n      options = AVAudioSessionCategoryOptionDuckOthers;\n    }\n\n    if (category != nil && options != nil) {\n      [session setCategory:category withOptions:options error:nil];\n    } else if (category != nil && options == nil) {\n      [session setCategory:category error:nil];\n    } else if (category == nil && options != nil) {\n      [session setCategory:session.category withOptions:options error:nil];\n    }\n}\n\n- (void)setRepeat:(BOOL)repeat {\n  _repeat = repeat;\n}\n\n- (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic\n                                   withCriteria:(NSDictionary *)criteria\n{\n  NSString *type = criteria[@\"type\"];\n  AVMediaSelectionGroup *group = [_player.currentItem.asset\n                                  mediaSelectionGroupForMediaCharacteristic:characteristic];\n  AVMediaSelectionOption *mediaOption;\n  \n  if ([type isEqualToString:@\"disabled\"]) {\n    // Do nothing. We want to ensure option is nil\n  } else if ([type isEqualToString:@\"language\"] || [type isEqualToString:@\"title\"]) {\n    NSString *value = criteria[@\"value\"];\n    for (int i = 0; i < group.options.count; ++i) {\n      AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];\n      NSString *optionValue;\n      if ([type isEqualToString:@\"language\"]) {\n        optionValue = [currentOption extendedLanguageTag];\n      } else {\n        optionValue = [[[currentOption commonMetadata]\n                        valueForKey:@\"value\"]\n                       objectAtIndex:0];\n      }\n      if ([value isEqualToString:optionValue]) {\n        mediaOption = currentOption;\n        break;\n      }\n    }\n    //} else if ([type isEqualToString:@\"default\"]) {\n    //  option = group.defaultOption; */\n  } else if ([type isEqualToString:@\"index\"]) {\n    if ([criteria[@\"value\"] isKindOfClass:[NSNumber class]]) {\n      int index = [criteria[@\"value\"] intValue];\n      if (group.options.count > index) {\n        mediaOption = [group.options objectAtIndex:index];\n      }\n    }\n  } else { // default. invalid type or \"system\"\n    #if TARGET_OS_TV\n    // Do noting. Fix for tvOS native audio menu language selector\n    #else\n      [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group];\n      return;\n    #endif\n  }\n\n    #if TARGET_OS_TV\n    // Do noting. Fix for tvOS native audio menu language selector\n    #else\n       // If a match isn't found, option will be nil and text tracks will be disabled\n       [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];\n    #endif\n}\n\n- (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack {\n  _selectedAudioTrack = selectedAudioTrack;\n  [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible\n                                   withCriteria:_selectedAudioTrack];\n}\n\n- (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack {\n  _selectedTextTrack = selectedTextTrack;\n  if (_textTracks) { // sideloaded text tracks\n    [self setSideloadedText];\n  } else { // text tracks included in the HLS playlist\n    [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicLegible\n                                     withCriteria:_selectedTextTrack];\n  }\n}\n\n- (void) setSideloadedText {\n  NSString *type = _selectedTextTrack[@\"type\"];\n  NSArray *textTracks = [self getTextTrackInfo];\n  \n  // The first few tracks will be audio & video track\n  int firstTextIndex = 0;\n  for (firstTextIndex = 0; firstTextIndex < _player.currentItem.tracks.count; ++firstTextIndex) {\n    if ([_player.currentItem.tracks[firstTextIndex].assetTrack hasMediaCharacteristic:AVMediaCharacteristicLegible]) {\n      break;\n    }\n  }\n  \n  int selectedTrackIndex = RCTVideoUnset;\n  \n  if ([type isEqualToString:@\"disabled\"]) {\n    // Do nothing. We want to ensure option is nil\n  } else if ([type isEqualToString:@\"language\"]) {\n    NSString *selectedValue = _selectedTextTrack[@\"value\"];\n    for (int i = 0; i < textTracks.count; ++i) {\n      NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];\n      if ([selectedValue isEqualToString:currentTextTrack[@\"language\"]]) {\n        selectedTrackIndex = i;\n        break;\n      }\n    }\n  } else if ([type isEqualToString:@\"title\"]) {\n    NSString *selectedValue = _selectedTextTrack[@\"value\"];\n    for (int i = 0; i < textTracks.count; ++i) {\n      NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];\n      if ([selectedValue isEqualToString:currentTextTrack[@\"title\"]]) {\n        selectedTrackIndex = i;\n        break;\n      }\n    }\n  } else if ([type isEqualToString:@\"index\"]) {\n    if ([_selectedTextTrack[@\"value\"] isKindOfClass:[NSNumber class]]) {\n      int index = [_selectedTextTrack[@\"value\"] intValue];\n      if (textTracks.count > index) {\n        selectedTrackIndex = index;\n      }\n    }\n  }\n  \n  // in the situation that a selected text track is not available (eg. specifies a textTrack not available)\n  if (![type isEqualToString:@\"disabled\"] && selectedTrackIndex == RCTVideoUnset) {\n    CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser);\n    NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics;\n    if ([captionSettings containsObject:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) {\n      selectedTrackIndex = 0; // If we can't find a match, use the first available track\n      NSString *systemLanguage = [[NSLocale preferredLanguages] firstObject];\n      for (int i = 0; i < textTracks.count; ++i) {\n        NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];\n        if ([systemLanguage isEqualToString:currentTextTrack[@\"language\"]]) {\n          selectedTrackIndex = i;\n          break;\n        }\n      }\n    }\n  }\n  \n  for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) {\n    BOOL isEnabled = NO;\n    if (selectedTrackIndex != RCTVideoUnset) {\n      isEnabled = i == selectedTrackIndex + firstTextIndex;\n    }\n    [_player.currentItem.tracks[i] setEnabled:isEnabled];\n  }\n}\n\n-(void) setStreamingText {\n  NSString *type = _selectedTextTrack[@\"type\"];\n  AVMediaSelectionGroup *group = [_player.currentItem.asset\n                                  mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];\n  AVMediaSelectionOption *mediaOption;\n  \n  if ([type isEqualToString:@\"disabled\"]) {\n    // Do nothing. We want to ensure option is nil\n  } else if ([type isEqualToString:@\"language\"] || [type isEqualToString:@\"title\"]) {\n    NSString *value = _selectedTextTrack[@\"value\"];\n    for (int i = 0; i < group.options.count; ++i) {\n      AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];\n      NSString *optionValue;\n      if ([type isEqualToString:@\"language\"]) {\n        optionValue = [currentOption extendedLanguageTag];\n      } else {\n        optionValue = [[[currentOption commonMetadata]\n                        valueForKey:@\"value\"]\n                       objectAtIndex:0];\n      }\n      if ([value isEqualToString:optionValue]) {\n        mediaOption = currentOption;\n        break;\n      }\n    }\n    //} else if ([type isEqualToString:@\"default\"]) {\n    //  option = group.defaultOption; */\n  } else if ([type isEqualToString:@\"index\"]) {\n    if ([_selectedTextTrack[@\"value\"] isKindOfClass:[NSNumber class]]) {\n      int index = [_selectedTextTrack[@\"value\"] intValue];\n      if (group.options.count > index) {\n        mediaOption = [group.options objectAtIndex:index];\n      }\n    }\n  } else { // default. invalid type or \"system\"\n    [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group];\n    return;\n  }\n  \n  // If a match isn't found, option will be nil and text tracks will be disabled\n  [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];\n}\n\n- (void)setTextTracks:(NSArray*) textTracks;\n{\n  _textTracks = textTracks;\n  \n  // in case textTracks was set after selectedTextTrack\n  if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack];\n}\n\n- (NSArray *)getAudioTrackInfo\n{\n  NSMutableArray *audioTracks = [[NSMutableArray alloc] init];\n  AVMediaSelectionGroup *group = [_player.currentItem.asset\n                                  mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];\n  for (int i = 0; i < group.options.count; ++i) {\n    AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];\n    NSString *title = @\"\";\n    NSArray *values = [[currentOption commonMetadata] valueForKey:@\"value\"];\n    if (values.count > 0) {\n      title = [values objectAtIndex:0];\n    }\n    NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @\"\";\n    NSDictionary *audioTrack = @{\n                                 @\"index\": [NSNumber numberWithInt:i],\n                                 @\"title\": title,\n                                 @\"language\": language\n                                 };\n    [audioTracks addObject:audioTrack];\n  }\n  return audioTracks;\n}\n\n- (NSArray *)getTextTrackInfo\n{\n  // if sideloaded, textTracks will already be set\n  if (_textTracks) return _textTracks;\n  \n  // if streaming video, we extract the text tracks\n  NSMutableArray *textTracks = [[NSMutableArray alloc] init];\n  AVMediaSelectionGroup *group = [_player.currentItem.asset\n                                  mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];\n  for (int i = 0; i < group.options.count; ++i) {\n    AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];\n    NSString *title = @\"\";\n    NSArray *values = [[currentOption commonMetadata] valueForKey:@\"value\"];\n    if (values.count > 0) {\n      title = [values objectAtIndex:0];\n    }\n    NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @\"\";\n    NSDictionary *textTrack = @{\n                                @\"index\": [NSNumber numberWithInt:i],\n                                @\"title\": title,\n                                @\"language\": language\n                                };\n    [textTracks addObject:textTrack];\n  }\n  return textTracks;\n}\n\n- (BOOL)getFullscreen\n{\n  return _fullscreenPlayerPresented;\n}\n\n- (void)setFullscreen:(BOOL) fullscreen {\n  if( fullscreen && !_fullscreenPlayerPresented && _player )\n  {\n    // Ensure player view controller is not null\n    if( !_playerViewController )\n    {\n      [self usePlayerViewController];\n    }\n    // Set presentation style to fullscreen\n    [_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen];\n    \n    // Find the nearest view controller\n    UIViewController *viewController = [self firstAvailableUIViewController];\n    if( !viewController )\n    {\n      UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];\n      viewController = keyWindow.rootViewController;\n      if( viewController.childViewControllers.count > 0 )\n      {\n        viewController = viewController.childViewControllers.lastObject;\n      }\n    }\n    if( viewController )\n    {\n      _presentingViewController = viewController;\n      if(self.onVideoFullscreenPlayerWillPresent) {\n        self.onVideoFullscreenPlayerWillPresent(@{@\"target\": self.reactTag});\n      }\n      [viewController presentViewController:_playerViewController animated:true completion:^{\n        _playerViewController.showsPlaybackControls = YES;\n        _fullscreenPlayerPresented = fullscreen;\n        _playerViewController.autorotate = _fullscreenAutorotate;\n        if(self.onVideoFullscreenPlayerDidPresent) {\n          self.onVideoFullscreenPlayerDidPresent(@{@\"target\": self.reactTag});\n        }\n      }];\n    }\n  }\n  else if ( !fullscreen && _fullscreenPlayerPresented )\n  {\n    [self videoPlayerViewControllerWillDismiss:_playerViewController];\n    [_presentingViewController dismissViewControllerAnimated:true completion:^{\n      [self videoPlayerViewControllerDidDismiss:_playerViewController];\n    }];\n  }\n}\n\n- (void)setFullscreenAutorotate:(BOOL)autorotate {\n  _fullscreenAutorotate = autorotate;\n  if (_fullscreenPlayerPresented) {\n    _playerViewController.autorotate = autorotate;\n  }\n}\n\n- (void)setFullscreenOrientation:(NSString *)orientation {\n  _fullscreenOrientation = orientation;\n  if (_fullscreenPlayerPresented) {\n    _playerViewController.preferredOrientation = orientation;\n  }\n}\n\n- (void)usePlayerViewController\n{\n  if( _player )\n  {\n    if (!_playerViewController) {\n      _playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];\n    }\n    // to prevent video from being animated when resizeMode is 'cover'\n    // resize mode must be set before subview is added\n    [self setResizeMode:_resizeMode];\n    \n    if (_controls) {\n      UIViewController *viewController = [self reactViewController];\n      [viewController addChildViewController:_playerViewController];\n      [self addSubview:_playerViewController.view];\n    }\n      \n    [_playerViewController addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];\n    \n    [_playerViewController.contentOverlayView addObserver:self forKeyPath:@\"frame\" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];\n  }\n}\n\n- (void)usePlayerLayer\n{\n  if( _player )\n  {\n    _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];\n    _playerLayer.frame = self.bounds;\n    _playerLayer.needsDisplayOnBoundsChange = YES;\n    \n    // to prevent video from being animated when resizeMode is 'cover'\n    // resize mode must be set before layer is added\n    [self setResizeMode:_resizeMode];\n    [_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];\n    _playerLayerObserverSet = YES;\n    \n    [self.layer addSublayer:_playerLayer];\n    self.layer.needsDisplayOnBoundsChange = YES;\n    #if TARGET_OS_IOS\n    [self setupPipController];\n    #endif\n  }\n}\n\n- (void)setControls:(BOOL)controls\n{\n  if( _controls != controls || (!_playerLayer && !_playerViewController) )\n  {\n    _controls = controls;\n    if( _controls )\n    {\n      [self removePlayerLayer];\n      [self usePlayerViewController];\n    }\n    else\n    {\n      [_playerViewController.view removeFromSuperview];\n      _playerViewController = nil;\n      [self usePlayerLayer];\n    }\n  }\n}\n\n- (void)setProgressUpdateInterval:(float)progressUpdateInterval\n{\n  _progressUpdateInterval = progressUpdateInterval;\n  \n  if (_timeObserver) {\n    [self removePlayerTimeObserver];\n    [self addPlayerTimeObserver];\n  }\n}\n\n- (void)removePlayerLayer\n{\n  if (_loadingRequest != nil) {\n    [_loadingRequest finishLoading];\n  }\n  _requestingCertificate = NO;\n  _requestingCertificateErrored = NO;\n  [_playerLayer removeFromSuperlayer];\n  if (_playerLayerObserverSet) {\n    [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];\n    _playerLayerObserverSet = NO;\n  }\n  _playerLayer = nil;\n}\n\n#pragma mark - RCTVideoPlayerViewControllerDelegate\n\n- (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController\n{\n  if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss)\n  {\n    @try{\n      [_playerViewController.contentOverlayView removeObserver:self forKeyPath:@\"frame\"];\n      [_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];\n    }@catch(id anException){\n    }\n    self.onVideoFullscreenPlayerWillDismiss(@{@\"target\": self.reactTag});\n  }\n}\n\n- (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController\n{\n  if (_playerViewController == playerViewController && _fullscreenPlayerPresented)\n  {\n    _fullscreenPlayerPresented = false;\n    _presentingViewController = nil;\n    _playerViewController = nil;\n    [self applyModifiers];\n    if(self.onVideoFullscreenPlayerDidDismiss) {\n      self.onVideoFullscreenPlayerDidDismiss(@{@\"target\": self.reactTag});\n    }\n  }\n}\n\n- (void)setFilter:(NSString *)filterName {\n  _filterName = filterName;\n  \n  if (!_filterEnabled) {\n    return;\n  } else if ([[_source objectForKey:@\"uri\"] rangeOfString:@\"m3u8\"].location != NSNotFound) {\n    return; // filters don't work for HLS... return\n  } else if (!_playerItem.asset) {\n    return;\n  }\n  \n  CIFilter *filter = [CIFilter filterWithName:filterName];\n  _playerItem.videoComposition = [AVVideoComposition\n                                  videoCompositionWithAsset:_playerItem.asset\n                                  applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) {\n                                    if (filter == nil) {\n                                      [request finishWithImage:request.sourceImage context:nil];\n                                    } else {\n                                      CIImage *image = request.sourceImage.imageByClampingToExtent;\n                                      [filter setValue:image forKey:kCIInputImageKey];\n                                      CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent];\n                                      [request finishWithImage:output context:nil];\n                                    }\n                                  }];\n}\n\n- (void)setFilterEnabled:(BOOL)filterEnabled {\n  _filterEnabled = filterEnabled;\n}\n\n#pragma mark - React View Management\n\n- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex\n{\n  // We are early in the game and somebody wants to set a subview.\n  // That can only be in the context of playerViewController.\n  if( !_controls && !_playerLayer && !_playerViewController )\n  {\n    [self setControls:true];\n  }\n  \n  if( _controls )\n  {\n    view.frame = self.bounds;\n    [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex];\n  }\n  else\n  {\n    RCTLogError(@\"video cannot have any subviews\");\n  }\n  return;\n}\n\n- (void)removeReactSubview:(UIView *)subview\n{\n  if( _controls )\n  {\n    [subview removeFromSuperview];\n  }\n  else\n  {\n    RCTLogError(@\"video cannot have any subviews\");\n  }\n  return;\n}\n\n- (void)layoutSubviews\n{\n  [super layoutSubviews];\n  if( _controls )\n  {\n    _playerViewController.view.frame = self.bounds;\n    \n    // also adjust all subviews of contentOverlayView\n    for (UIView* subview in _playerViewController.contentOverlayView.subviews) {\n      subview.frame = self.bounds;\n    }\n  }\n  else\n  {\n    [CATransaction begin];\n    [CATransaction setAnimationDuration:0];\n    _playerLayer.frame = self.bounds;\n    [CATransaction commit];\n  }\n}\n\n#pragma mark - Lifecycle\n\n- (void)removeFromSuperview\n{\n  [_player pause];\n  if (_playbackRateObserverRegistered) {\n    [_player removeObserver:self forKeyPath:playbackRate context:nil];\n    _playbackRateObserverRegistered = NO;\n  }\n  if (_isExternalPlaybackActiveObserverRegistered) {\n    [_player removeObserver:self forKeyPath:externalPlaybackActive context:nil];\n    _isExternalPlaybackActiveObserverRegistered = NO;\n  }\n  _player = nil;\n  \n  [self removePlayerLayer];\n  \n  [_playerViewController.contentOverlayView removeObserver:self forKeyPath:@\"frame\"];\n  [_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];\n  [_playerViewController.view removeFromSuperview];\n  _playerViewController.rctDelegate = nil;\n  _playerViewController.player = nil;\n  _playerViewController = nil;\n  \n  [self removePlayerTimeObserver];\n  [self removePlayerItemObservers];\n  \n  _eventDispatcher = nil;\n  [[NSNotificationCenter defaultCenter] removeObserver:self];\n  \n  [super removeFromSuperview];\n}\n\n#pragma mark - Export\n\n- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {\n  \n  AVAsset *asset = _playerItem.asset;\n  \n  if (asset != nil) {\n    \n    AVAssetExportSession *exportSession = [AVAssetExportSession\n                                           exportSessionWithAsset:asset presetName:AVAssetExportPresetHighestQuality];\n    \n    if (exportSession != nil) {\n      NSString *path = nil;\n      NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);\n      path = [self generatePathInDirectory:[[self cacheDirectoryPath] stringByAppendingPathComponent:@\"Videos\"]\n                             withExtension:@\".mp4\"];\n      NSURL *url = [NSURL fileURLWithPath:path];\n      exportSession.outputFileType = AVFileTypeMPEG4;\n      exportSession.outputURL = url;\n      exportSession.videoComposition = _playerItem.videoComposition;\n      exportSession.shouldOptimizeForNetworkUse = true;\n      [exportSession exportAsynchronouslyWithCompletionHandler:^{\n        \n        switch ([exportSession status]) {\n          case AVAssetExportSessionStatusFailed:\n            reject(@\"ERROR_COULD_NOT_EXPORT_VIDEO\", @\"Could not export video\", exportSession.error);\n            break;\n          case AVAssetExportSessionStatusCancelled:\n            reject(@\"ERROR_EXPORT_SESSION_CANCELLED\", @\"Export session was cancelled\", exportSession.error);\n            break;\n          default:\n            resolve(@{@\"uri\": url.absoluteString});\n            break;\n        }\n        \n      }];\n      \n    } else {\n      \n      reject(@\"ERROR_COULD_NOT_CREATE_EXPORT_SESSION\", @\"Could not create export session\", nil);\n      \n    }\n    \n  } else {\n    \n    reject(@\"ERROR_ASSET_NIL\", @\"Asset is nil\", nil);\n    \n  }\n}\n\n- (void)setLicenseResult:(NSString *)license {\n  NSData *respondData = [self base64DataFromBase64String:license];\n  if (_loadingRequest != nil && respondData != nil) {\n    AVAssetResourceLoadingDataRequest *dataRequest = [_loadingRequest dataRequest];\n    [dataRequest respondWithData:respondData];\n    [_loadingRequest finishLoading];\n  } else {\n    [self setLicenseResultError:@\"No data from JS license response\"];\n  }\n}\n\n- (BOOL)setLicenseResultError:(NSString *)error {\n  if (_loadingRequest != nil) {\n    NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                code: RCTVideoErrorFromJSPart\n                                            userInfo: @{\n                                                        NSLocalizedDescriptionKey: error,\n                                                        NSLocalizedFailureReasonErrorKey: error,\n                                                        NSLocalizedRecoverySuggestionErrorKey: error\n                                                        }\n                             ];\n    [self finishLoadingWithError:licenseError];\n  }\n  return NO;\n}\n\n- (BOOL)finishLoadingWithError:(NSError *)error {\n  if (_loadingRequest && error != nil) {\n    NSError *licenseError = error;\n    [_loadingRequest finishLoadingWithError:licenseError];\n    if (self.onVideoError) {\n      self.onVideoError(@{@\"error\": @{@\"code\": [NSNumber numberWithInteger: error.code],\n                                      @\"localizedDescription\": [error localizedDescription] == nil ? @\"\" : [error localizedDescription],\n                                      @\"localizedFailureReason\": [error localizedFailureReason] == nil ? @\"\" : [error localizedFailureReason],\n                                      @\"localizedRecoverySuggestion\": [error localizedRecoverySuggestion] == nil ? @\"\" : [error localizedRecoverySuggestion],\n                                      @\"domain\": _playerItem.error == nil ? @\"RCTVideo\" : _playerItem.error.domain},\n                          @\"target\": self.reactTag});\n    }\n  }\n  return NO;\n}\n\n- (BOOL)ensureDirExistsWithPath:(NSString *)path {\n  BOOL isDir = NO;\n  NSError *error;\n  BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];\n  if (!(exists && isDir)) {\n    [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];\n    if (error) {\n      return NO;\n    }\n  }\n  return YES;\n}\n\n- (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension {\n  NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingString:extension];\n  [self ensureDirExistsWithPath:directory];\n  return [directory stringByAppendingPathComponent:fileName];\n}\n\n- (NSString *)cacheDirectoryPath {\n  NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);\n  return array[0];\n}\n\n#pragma mark - AVAssetResourceLoaderDelegate\n\n- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForRenewalOfRequestedResource:(AVAssetResourceRenewalRequest *)renewalRequest {\n  return [self loadingRequestHandling:renewalRequest];\n}\n\n- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {\n  return [self loadingRequestHandling:loadingRequest];\n}\n\n- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader\ndidCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {\n  NSLog(@\"didCancelLoadingRequest\");\n}\n\n- (BOOL)loadingRequestHandling:(AVAssetResourceLoadingRequest *)loadingRequest {\n  if (self->_requestingCertificate) {\n    return YES;\n  } else if (self->_requestingCertificateErrored) {\n    return NO;\n  }\n  _loadingRequest = loadingRequest;\n  NSURL *url = loadingRequest.request.URL;\n  if (self->_drm != nil) {\n    NSString *contentId;\n    NSString *contentIdOverride = (NSString *)[self->_drm objectForKey:@\"contentId\"];\n    if (contentIdOverride != nil) {\n      contentId = contentIdOverride;\n    } else if (self.onGetLicense) {\n      contentId = url.host;\n    } else {\n      contentId = [url.absoluteString stringByReplacingOccurrencesOfString:@\"skd://\" withString:@\"\"];\n    }\n    NSString *drmType = (NSString *)[self->_drm objectForKey:@\"type\"];\n    if ([drmType isEqualToString:@\"fairplay\"]) {\n      NSString *certificateStringUrl = (NSString *)[self->_drm objectForKey:@\"certificateUrl\"];\n      if (certificateStringUrl != nil) {\n        NSURL *certificateURL = [NSURL URLWithString:[certificateStringUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];\n        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n          NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL];\n          if ([self->_drm objectForKey:@\"base64Certificate\"]) {\n            certificateData = [[NSData alloc] initWithBase64EncodedData:certificateData options:NSDataBase64DecodingIgnoreUnknownCharacters];\n          }\n          \n          if (certificateData != nil) {\n            NSData *contentIdData;\n            if(self.onGetLicense) {\n              contentIdData = [contentId dataUsingEncoding:NSUTF8StringEncoding];\n            } else {\n              contentIdData = [NSData dataWithBytes: [contentId cStringUsingEncoding:NSUTF8StringEncoding] length:[contentId lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];\n            }\n            AVAssetResourceLoadingDataRequest *dataRequest = [loadingRequest dataRequest];\n            if (dataRequest != nil) {\n              NSError *spcError = nil;\n              NSData *spcData = [loadingRequest streamingContentKeyRequestDataForApp:certificateData contentIdentifier:contentIdData options:nil error:&spcError];\n              // Request CKC to the server\n              NSString *licenseServer = (NSString *)[self->_drm objectForKey:@\"licenseServer\"];\n              if (spcError != nil) {\n                [self finishLoadingWithError:spcError];\n                self->_requestingCertificateErrored = YES;\n              }\n              if (spcData != nil) {\n                if(self.onGetLicense) {\n                  NSString *base64Encoded = [spcData base64EncodedStringWithOptions:0];\n                  self->_requestingCertificate = YES;\n                  if (licenseServer == nil) {\n                    licenseServer = @\"\";\n                  }\n                  self.onGetLicense(@{@\"licenseUrl\": licenseServer,\n                                      @\"contentId\": contentId,\n                                      @\"spcBase64\": base64Encoded,\n                                      @\"target\": self.reactTag}\n                                    );\n                } else if(licenseServer != nil) {\n                  NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];\n                  [request setHTTPMethod:@\"POST\"];\n                  [request setURL:[NSURL URLWithString:licenseServer]];\n                  // HEADERS\n                  NSDictionary *headers = (NSDictionary *)[self->_drm objectForKey:@\"headers\"];\n                  if (headers != nil) {\n                    for (NSString *key in headers) {\n                      NSString *value = headers[key];\n                      [request setValue:value forHTTPHeaderField:key];\n                    }\n                  }\n\n                  if(self.onGetLicense) {\n                    [request setHTTPBody: spcData];\n                  } else {\n                    NSString *spcEncoded = [spcData base64EncodedStringWithOptions:0];\n                    NSString *spcUrlEncoded = (NSString *) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)spcEncoded, NULL, CFSTR(\"?=&+\"), kCFStringEncodingUTF8));\n                    NSString *post = [NSString stringWithFormat:@\"spc=%@&%@\", spcUrlEncoded, contentId];\n                    NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];\n                    [request setHTTPBody: postData];\n                  }\n                  \n                  NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];\n                  NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];\n                  NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {\n                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;\n                    if (error != nil) {\n                      NSLog(@\"Error getting license from %@, HTTP status code %li\", url, (long)[httpResponse statusCode]);\n                      [self finishLoadingWithError:error];\n                      self->_requestingCertificateErrored = YES;\n                    } else {\n                      if([httpResponse statusCode] != 200){\n                        NSLog(@\"Error getting license from %@, HTTP status code %li\", url, (long)[httpResponse statusCode]);\n                        NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                                    code: RCTVideoErrorLicenseRequestNotOk\n                                                                userInfo: @{\n                                                                            NSLocalizedDescriptionKey: @\"Error obtaining license.\",\n                                                                            NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@\"License server responded with status code %li\", (long)[httpResponse statusCode]],\n                                                                            NSLocalizedRecoverySuggestionErrorKey: @\"Did you send the correct data to the license Server? Is the server ok?\"\n                                                                            }\n                                                 ];\n                        [self finishLoadingWithError:licenseError];\n                        self->_requestingCertificateErrored = YES;\n                      } else if (data != nil) {\n                        if(self.onGetLicense) {\n                          [dataRequest respondWithData:data];\n                        } else {\n                          NSData *decodedData = [[NSData alloc] initWithBase64EncodedData:data options:0];\n                          [dataRequest respondWithData:decodedData];\n                        }\n                        [loadingRequest finishLoading];\n                      } else {\n                        NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                                    code: RCTVideoErrorNoDataFromLicenseRequest\n                                                                userInfo: @{\n                                                                            NSLocalizedDescriptionKey: @\"Error obtaining DRM license.\",\n                                                                            NSLocalizedFailureReasonErrorKey: @\"No data received from the license server.\",\n                                                                            NSLocalizedRecoverySuggestionErrorKey: @\"Is the licenseServer ok?.\"\n                                                                            }\n                                                 ];\n                        [self finishLoadingWithError:licenseError];\n                        self->_requestingCertificateErrored = YES;\n                      }\n\n                    }\n                  }];\n                  [postDataTask resume];\n                }\n                \n              } else {\n                NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                            code: RCTVideoErrorNoSPC\n                                                        userInfo: @{\n                                                                    NSLocalizedDescriptionKey: @\"Error obtaining license.\",\n                                                                    NSLocalizedFailureReasonErrorKey: @\"No spc received.\",\n                                                                    NSLocalizedRecoverySuggestionErrorKey: @\"Check your DRM config.\"\n                                                                    }\n                                         ];\n                [self finishLoadingWithError:licenseError];\n                self->_requestingCertificateErrored = YES;\n              }\n              \n            } else {\n              NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                          code: RCTVideoErrorNoDataRequest\n                                                      userInfo: @{\n                                                                  NSLocalizedDescriptionKey: @\"Error obtaining DRM license.\",\n                                                                  NSLocalizedFailureReasonErrorKey: @\"No dataRequest found.\",\n                                                                  NSLocalizedRecoverySuggestionErrorKey: @\"Check your DRM configuration.\"\n                                                                  }\n                                       ];\n              [self finishLoadingWithError:licenseError];\n              self->_requestingCertificateErrored = YES;\n            }\n          } else {\n            NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                        code: RCTVideoErrorNoCertificateData\n                                                    userInfo: @{\n                                                                NSLocalizedDescriptionKey: @\"Error obtaining DRM license.\",\n                                                                NSLocalizedFailureReasonErrorKey: @\"No certificate data obtained from the specificied url.\",\n                                                                NSLocalizedRecoverySuggestionErrorKey: @\"Have you specified a valid 'certificateUrl'?\"\n                                                                }\n                                     ];\n            [self finishLoadingWithError:licenseError];\n            self->_requestingCertificateErrored = YES;\n          }\n        });\n        return YES;\n      } else {\n        NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                    code: RCTVideoErrorNoCertificateURL\n                                                userInfo: @{\n                                                            NSLocalizedDescriptionKey: @\"Error obtaining DRM License.\",\n                                                            NSLocalizedFailureReasonErrorKey: @\"No certificate URL has been found.\",\n                                                            NSLocalizedRecoverySuggestionErrorKey: @\"Did you specified the prop certificateUrl?\"\n                                                            }\n                                 ];\n        return [self finishLoadingWithError:licenseError];\n      }\n    } else {\n      NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                  code: RCTVideoErrorNoFairplayDRM\n                                              userInfo: @{\n                                                          NSLocalizedDescriptionKey: @\"Error obtaining DRM license.\",\n                                                          NSLocalizedFailureReasonErrorKey: @\"Not a valid DRM Scheme has found\",\n                                                          NSLocalizedRecoverySuggestionErrorKey: @\"Have you specified the 'drm' 'type' as fairplay?\"\n                                                          }\n                               ];\n      return [self finishLoadingWithError:licenseError];\n    }\n    \n  } else {\n    NSError *licenseError = [NSError errorWithDomain: @\"RCTVideo\"\n                                                code: RCTVideoErrorNoDRMData\n                                            userInfo: @{\n                                                        NSLocalizedDescriptionKey: @\"Error obtaining DRM license.\",\n                                                        NSLocalizedFailureReasonErrorKey: @\"No drm object found.\",\n                                                        NSLocalizedRecoverySuggestionErrorKey: @\"Have you specified the 'drm' prop?\"\n                                                        }\n                             ];\n    return [self finishLoadingWithError:licenseError];\n  }\n  \n  \n  return NO;\n}\n\n- (NSData *)base64DataFromBase64String: (NSString *)base64String {\n  if (base64String != nil) {\n    // NSData from the Base64 encoded str\n    NSData *base64Data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSASCIIStringEncoding];\n    return base64Data;\n  }\n  return nil;\n}\n#pragma mark - Picture in Picture\n\n#if TARGET_OS_IOS\n- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {\n  if (self.onPictureInPictureStatusChanged) {\n    self.onPictureInPictureStatusChanged(@{\n                                           @\"isActive\": [NSNumber numberWithBool:false]\n                                           });\n  }\n}\n\n- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {\n  if (self.onPictureInPictureStatusChanged) {\n    self.onPictureInPictureStatusChanged(@{\n                                           @\"isActive\": [NSNumber numberWithBool:true]\n                                           });\n  }\n}\n\n- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {\n  \n}\n\n- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {\n  \n}\n\n- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {\n  \n}\n\n- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler {\n  NSAssert(_restoreUserInterfaceForPIPStopCompletionHandler == NULL, @\"restoreUserInterfaceForPIPStopCompletionHandler was not called after picture in picture was exited.\");\n  if (self.onRestoreUserInterfaceForPictureInPictureStop) {\n    self.onRestoreUserInterfaceForPictureInPictureStop(@{});\n  }\n  _restoreUserInterfaceForPIPStopCompletionHandler = completionHandler;\n}\n#endif\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideoManager.h",
    "content": "#import <React/RCTViewManager.h>\n#import <React/RCTBridgeModule.h>\n\n@interface CCRCTVideoManager : RCTViewManager <RCTBridgeModule>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideoManager.m",
    "content": "#import \"CCRCTVideoManager.h\"\n#import \"CCRCTVideo.h\"\n#import <React/RCTBridge.h>\n#import <React/RCTUIManager.h>\n#import <AVFoundation/AVFoundation.h>\n\n@implementation CCRCTVideoManager\n\nRCT_EXPORT_MODULE();\n\n- (UIView *)view\n{\n  return [[CCRCTVideo alloc] initWithEventDispatcher:self.bridge.eventDispatcher];\n}\n\n- (dispatch_queue_t)methodQueue\n{\n    return self.bridge.uiManager.methodQueue;\n}\n\nRCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);\nRCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);\nRCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);\nRCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);\nRCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(automaticallyWaitsToMinimizeStalling, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);\nRCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);\nRCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary);\nRCT_EXPORT_VIEW_PROPERTY(paused, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(muted, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(controls, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(volume, float);\nRCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(preventsDisplaySleepDuringVideoPlayback, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(preferredForwardBufferDuration, float);\nRCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(pictureInPicture, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString);\nRCT_EXPORT_VIEW_PROPERTY(mixWithOthers, NSString);\nRCT_EXPORT_VIEW_PROPERTY(rate, float);\nRCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);\nRCT_EXPORT_VIEW_PROPERTY(currentTime, float);\nRCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString);\nRCT_EXPORT_VIEW_PROPERTY(filter, NSString);\nRCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);\nRCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);\nRCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);\n/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */\nRCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock);\nRCT_REMAP_METHOD(save,\n        options:(NSDictionary *)options\n        reactTag:(nonnull NSNumber *)reactTag\n        resolver:(RCTPromiseResolveBlock)resolve\n        rejecter:(RCTPromiseRejectBlock)reject)\n{\n    [self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, CCRCTVideo *> *viewRegistry) {\n        CCRCTVideo *view = viewRegistry[reactTag];\n        if (![view isKindOfClass:[CCRCTVideo class]]) {\n            RCTLogError(@\"Invalid view returned from registry, expecting RCTVideo, got: %@\", view);\n        } else {\n            [view save:options resolve:resolve reject:reject];\n        }\n    }];\n};\nRCT_REMAP_METHOD(setLicenseResult,\n         license:(NSString *)license\n         reactTag:(nonnull NSNumber *)reactTag)\n{\n    [self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, CCRCTVideo *> *viewRegistry) {\n        CCRCTVideo *view = viewRegistry[reactTag];\n        if (![view isKindOfClass:[CCRCTVideo class]]) {\n            RCTLogError(@\"Invalid view returned from registry, expecting RCTVideo, got: %@\", view);\n        } else {\n            [view setLicenseResult:license];\n        }\n    }];\n};\n\nRCT_REMAP_METHOD(setLicenseResultError,\n                 error:(NSString *)error\n                 reactTag:(nonnull NSNumber *)reactTag)\n{\n    [self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, CCRCTVideo *> *viewRegistry) {\n        CCRCTVideo *view = viewRegistry[reactTag];\n        if (![view isKindOfClass:[CCRCTVideo class]]) {\n            RCTLogError(@\"Invalid view returned from registry, expecting RCTVideo, got: %@\", view);\n        } else {\n            [view setLicenseResultError:error];\n        }\n    }];\n};\nRCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);\nRCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);\n\n- (NSDictionary *)constantsToExport\n{\n  return @{\n    @\"ScaleNone\": AVLayerVideoGravityResizeAspect,\n    @\"ScaleToFill\": AVLayerVideoGravityResize,\n    @\"ScaleAspectFit\": AVLayerVideoGravityResizeAspect,\n    @\"ScaleAspectFill\": AVLayerVideoGravityResizeAspectFill\n  };\n}\n\n+ (BOOL)requiresMainQueueSetup\n{\n    return YES;\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideoPlayerViewController.h",
    "content": "//\n//  RCTVideoPlayerViewController.h\n//  RCTVideo\n//\n//  Created by Stanisław Chmiela on 31.03.2016.\n//  Copyright © 2016 Facebook. All rights reserved.\n//\n\n#import <AVKit/AVKit.h>\n#import \"CCRCTVideo.h\"\n#import \"CCRCTVideoPlayerViewControllerDelegate.h\"\n\n@interface CCRCTVideoPlayerViewController : AVPlayerViewController\n@property (nonatomic, weak) id<CCRCTVideoPlayerViewControllerDelegate> rctDelegate;\n\n// Optional paramters\n@property (nonatomic, weak) NSString* preferredOrientation;\n@property (nonatomic) BOOL autorotate;\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideoPlayerViewController.m",
    "content": "#import \"CCRCTVideoPlayerViewController.h\"\n\n@interface CCRCTVideoPlayerViewController ()\n\n@end\n\n@implementation CCRCTVideoPlayerViewController\n\n- (BOOL)shouldAutorotate {\n\n  if (self.autorotate || self.preferredOrientation.lowercaseString == nil || [self.preferredOrientation.lowercaseString isEqualToString:@\"all\"])\n    return YES;\n  \n  return NO;\n}\n\n- (void)viewDidDisappear:(BOOL)animated\n{\n  [super viewDidDisappear:animated];\n  [_rctDelegate videoPlayerViewControllerWillDismiss:self];\n  [_rctDelegate videoPlayerViewControllerDidDismiss:self];\n}\n\n#if !TARGET_OS_TV\n- (UIInterfaceOrientationMask)supportedInterfaceOrientations {\n  return UIInterfaceOrientationMaskAll;\n}\n\n- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {\n  if ([self.preferredOrientation.lowercaseString isEqualToString:@\"landscape\"]) {\n    return UIInterfaceOrientationLandscapeRight;\n  }\n  else if ([self.preferredOrientation.lowercaseString isEqualToString:@\"portrait\"]) {\n    return UIInterfaceOrientationPortrait;\n  }\n  else { // default case\n    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;\n    return orientation;\n  }\n}\n#endif\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/CCRCTVideoPlayerViewControllerDelegate.h",
    "content": "#import <Foundation/Foundation.h>\n#import \"AVKit/AVKit.h\"\n\n@protocol CCRCTVideoPlayerViewControllerDelegate <NSObject>\n- (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController;\n- (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController;\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/UIView+FindUIViewController.h",
    "content": "//\n//  UIView+FindUIViewController.h\n//  RCTVideo\n//\n//  Created by Stanisław Chmiela on 31.03.2016.\n//  Copyright © 2016 Facebook. All rights reserved.\n//\n//  Source: http://stackoverflow.com/a/3732812/1123156\n\n#import <UIKit/UIKit.h>\n\n@interface UIView (FindUIViewController)\n- (UIViewController *) firstAvailableUIViewController;\n- (id) traverseResponderChainForUIViewController;\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/Video/UIView+FindUIViewController.m",
    "content": "//  Source: http://stackoverflow.com/a/3732812/1123156\n\n#import \"UIView+FindUIViewController.h\"\n\n@implementation UIView (FindUIViewController)\n- (UIViewController *) firstAvailableUIViewController {\n    // convenience function for casting and to \"mask\" the recursive function\n    return (UIViewController *)[self traverseResponderChainForUIViewController];\n}\n\n- (id) traverseResponderChainForUIViewController {\n    id nextResponder = [self nextResponder];\n    if ([nextResponder isKindOfClass:[UIViewController class]]) {\n        return nextResponder;\n    } else if ([nextResponder isKindOfClass:[UIView class]]) {\n        return [nextResponder traverseResponderChainForUIViewController];\n    } else {\n        return nil;\n    }\n}\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/VideoManager.h",
    "content": "#import <React/RCTBridgeModule.h>\n#import <QuickLook/QuickLook.h>\n\n@interface CometChatVideoManager : NSObject <RCTBridgeModule, QLPreviewControllerDataSource, QLPreviewControllerDelegate>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/VideoManager.m",
    "content": "\n#import <Foundation/Foundation.h>\n#import <React/RCTBridgeModule.h>\n#import \"VideoManager.h\"\n#import <QuickLook/QuickLook.h>\n#import <React/RCTUtils.h>\n\n@implementation CometChatVideoManager {\n    NSString *_videoUrl;\n}\n\nRCT_EXPORT_MODULE(VideoManager);\n\n-(void) openMedia:(NSString *) path {\n            dispatch_async(dispatch_get_main_queue(), ^{\n                QLPreviewController* previewCtrl = [[QLPreviewController alloc] init];\n                [previewCtrl setDataSource: self];\n                [[previewCtrl navigationController] setTitle:@\"\"];\n                [previewCtrl setModalPresentationStyle:UIModalPresentationPopover];\n                UIViewController *presentedViewController = RCTPresentedViewController();\n                [presentedViewController presentViewController:previewCtrl animated:YES completion:nil];\n            });\n}\n\nRCT_EXPORT_METHOD(play:(NSString *) urlToDownload name:(NSString *) name callback:(RCTResponseSenderBlock) callback) {\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n            NSURL  *url = [NSURL URLWithString:urlToDownload];\n            NSData *urlData = [NSData dataWithContentsOfURL:url];\n            if (urlData)\n            {\n                NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n                NSString *documentsDirectory = [paths objectAtIndex:0];\n\n                NSString *filePath = [NSString stringWithFormat:@\"file://%@/%@\", documentsDirectory,name];\n                NSURL *destinationFileURL = [NSURL URLWithString:filePath];\n\n                NSURL *url = [NSURL URLWithString:urlToDownload];\n                [[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {\n                    if (error == nil) {\n                        [[NSFileManager defaultManager] moveItemAtURL:location toURL:destinationFileURL error:nil];\n                        self->_videoUrl = filePath;\n                        callback(@[filePath]);\n                        return;\n                    }\n                    callback(@[@\"\"]);\n                }] resume];\n            }\n        });\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/VideoPickerModule.h",
    "content": "// VideoPickerModule.h\n\n#import <React/RCTBridgeModule.h>\n#import <UIKit/UIKit.h>\n\n@interface VideoPickerModule : NSObject <RCTBridgeModule, UINavigationControllerDelegate, UIImagePickerControllerDelegate>\n@end"
  },
  {
    "path": "packages/ChatUiKit/ios/VideoPickerModule.mm",
    "content": "// VideoPickerModule.m\n\n#import \"VideoPickerModule.h\"\n#import <React/RCTLog.h>\n#import <MobileCoreServices/MobileCoreServices.h>\n@interface VideoPickerModule () <UINavigationControllerDelegate, UIImagePickerControllerDelegate>\n\n@property (nonatomic, copy) RCTResponseSenderBlock callback;\n\n@end\n@implementation VideoPickerModule\n\nRCT_EXPORT_MODULE(VideoPickerModule);\n\nRCT_EXPORT_METHOD(pickVideo:(RCTResponseSenderBlock)callback)\n{\n    RCTLogInfo(@\"Picking video\");\n  \n    dispatch_async(dispatch_get_main_queue(), ^{\n        UIImagePickerController *picker = [[UIImagePickerController alloc] init];\n        picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;\n        picker.mediaTypes = @[(NSString*)kUTTypeMovie];\n        picker.delegate = self;\n      \n        // Get the application's root view controller\n        UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;\n        \n        // Check if the root view controller is presenting another view controller\n        while (rootViewController.presentedViewController) {\n            // If so, use it as the rootViewController to present the picker\n            rootViewController = rootViewController.presentedViewController;\n        }\n        \n        // Now present the picker\n        [rootViewController presentViewController:picker animated:YES completion:nil];\n        \n        // Store the callback for later use\n        self.callback = callback;\n    });\n}\n\n#pragma mark - UIImagePickerControllerDelegate\n- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {\n    NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];\n\n    // Remove the 'trim.' prefix and any trailing period '.' from the file name\n    NSString *fileName = [[videoURL lastPathComponent] stringByDeletingPathExtension];\n    if ([fileName hasPrefix:@\"trim.\"]) {\n        fileName = [fileName substringFromIndex:5]; // Remove the 'trim.' prefix\n    }\n    NSString *fileType = [videoURL pathExtension];\n    NSString *tempPath = [videoURL path];\n\n    // Copy the file to the application's document directory\n    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];\n    NSString *destinationFileName = [NSString stringWithFormat:@\"%@.%@\", fileName, fileType];\n    NSString *destinationPath = [documentsDirectory stringByAppendingPathComponent:destinationFileName];\n    NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath];\n\n    NSError *error;\n    NSFileManager *fileManager = [NSFileManager defaultManager];\n    \n    // Check if file exists at destination, remove it if it does\n    if ([fileManager fileExistsAtPath:destinationPath]) {\n        [fileManager removeItemAtPath:destinationPath error:&error];\n        if (error) {\n            RCTLogError(@\"Error removing existing file at destination: %@\", error.localizedDescription);\n            self.callback(@[@\"Could not remove existing file at destination\", [NSNull null]]);\n            [picker dismissViewControllerAnimated:YES completion:nil];\n            return;\n        }\n    }\n    \n    // Copy file from temporary location to documents directory\n    if ([fileManager copyItemAtPath:tempPath toPath:destinationPath error:&error]) {\n        // Constructing file URI with \"file://\" prefix\n        NSString *fileURI = destinationURL.absoluteString;\n\n        // Pass the video info to the callback\n        self.callback(@[@{@\"name\": destinationFileName, @\"type\": fileType, @\"uri\": fileURI}]);\n    } else {\n        RCTLogError(@\"Error copying temporary file to destination: %@\", error.localizedDescription);\n        self.callback(@[error.localizedDescription, [NSNull null]]);\n    }\n    \n    [picker dismissViewControllerAnimated:YES completion:nil];\n}\n\n\n- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker\n{\n    [picker dismissViewControllerAnimated:YES completion:nil];\n}\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/WebViewManager.h",
    "content": "#import <WebKit/WebKit.h>\n#import <React/RCTViewManager.h>\n\n\n@interface WebViewManager : NSObject <RCTBridgeModule>\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/ios/WebViewManager.m",
    "content": "#import <Foundation/Foundation.h>\n#import <WebKit/WebKit.h>\n#import <React/RCTViewManager.h>\n#import \"WebViewManager.h\"\n#import <UIKit/UIKit.h>\n\n@implementation WebViewManager\n\nRCT_EXPORT_MODULE(WebViewManager);\n\nRCT_EXPORT_METHOD(openUrl: (NSString *) url) {\n    dispatch_async(dispatch_get_main_queue(), ^{\n        \n        // Create a web view controller\n        UIViewController *webViewController = [[UIViewController alloc] init];\n        webViewController.view.backgroundColor = [UIColor whiteColor];\n        \n        // Create a web view with CGRectZero — Auto Layout will size it\n        WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero];\n        webView.translatesAutoresizingMaskIntoConstraints = NO;\n        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];\n        \n        // Add the web view to the view controller's view\n        [webViewController.view addSubview:webView];\n        \n        // Constrain to safe area layout guide so content respects notch and home indicator\n        UILayoutGuide *safeArea = webViewController.view.safeAreaLayoutGuide;\n        [NSLayoutConstraint activateConstraints:@[\n            [webView.topAnchor constraintEqualToAnchor:safeArea.topAnchor],\n            [webView.bottomAnchor constraintEqualToAnchor:safeArea.bottomAnchor],\n            [webView.leadingAnchor constraintEqualToAnchor:safeArea.leadingAnchor],\n            [webView.trailingAnchor constraintEqualToAnchor:safeArea.trailingAnchor]\n        ]];\n        \n        // Present the web view controller\n        UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;\n        [rootViewController presentViewController:webViewController animated:YES completion:^{\n            // code on completion\n        }];\n    });\n}\n\n\n@end\n"
  },
  {
    "path": "packages/ChatUiKit/package.json",
    "content": "{\n  \"name\": \"@cometchat/chat-uikit-react-native\",\n  \"version\": \"5.3.4\",\n  \"description\": \"Ready-to-use Chat UI Components for React Native\",\n  \"main\": \"src/index\",\n  \"module\": \"src/index\",\n  \"react-native\": \"src/index\",\n  \"source\": \"src/index\",\n  \"files\": [\n    \"lib\",\n    \"screenshots\",\n    \"android\",\n    \"ios\",\n    \"cpp\",\n    \"src\",\n    \"react-native-cometchat-ui-kit.podspec\",\n    \"!lib/typescript/example\",\n    \"!android/build\",\n    \"!ios/build\",\n    \"!**/__tests__\",\n    \"!**/__fixtures__\",\n    \"!**/__mocks__\"\n  ],\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"typescript\": \"tsc --noEmit\",\n    \"lint\": \"eslint \\\"**/*.{js,ts,tsx}\\\"\",\n    \"release\": \"release-it\",\n    \"format\": \"prettier --write ./src/**/*.{ts,tsx}\"\n  },\n  \"keywords\": [\n    \"cometchat\",\n    \"chat\",\n    \"messaging\",\n    \"audio\",\n    \"video\",\n    \"react-native\",\n    \"webRTC\",\n    \"Uikit\",\n    \"SDK\"\n  ],\n  \"repository\": \"https://github.com/cometchat/cometchat-uikit-react-native\",\n  \"author\": \"Inscripts\",\n  \"license\": \"https://www.cometchat.com/legal-terms-of-service\",\n  \"bugs\": {\n    \"url\": \"https://github.com/cometchat/cometchat-uikit-react-native/issues\"\n  },\n  \"homepage\": \"https://www.cometchat.com\",\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\"\n  },\n  \"devDependencies\": {\n    \"@babel/eslint-parser\": \"^7.27.0\",\n    \"@react-native/eslint-config\": \"0.79.2\",\n    \"@types/jest\": \"^29.5.13\",\n    \"@types/react\": \"*\",\n    \"commitlint\": \"^17.0.2\",\n    \"eslint\": \"^8.57.1\",\n    \"eslint-config-prettier\": \"^10.1.2\",\n    \"eslint-plugin-prettier\": \"^5.2.6\",\n    \"jest\": \"^29.6.3\",\n    \"prettier\": \"^3.5.3\",\n    \"react\": \"*\",\n    \"react-native\": \"*\",\n    \"typescript\": \"^5.0.4\"\n  },\n  \"dependencies\": {\n    \"@types/prop-types\": \"^15.7.14\",\n    \"prop-types\": \"^15.8.1\",\n    \"react-native-markdown-display\": \"^7.0.2\",\n    \"rxjs\": \"^7.8.2\",\n    \"dayjs\": \"^1.11.18\",\n    \"react-native-localize\": \"^3.5.2\"\n  },\n  \"peerDependencies\": {\n    \"@cometchat/chat-sdk-react-native\": \"*\",\n    \"@react-native-clipboard/clipboard\": \"*\",\n    \"react\": \"*\",\n    \"react-native\": \"*\",\n    \"react-native-gesture-handler\": \"*\",\n    \"react-native-svg\": \"*\",\n    \"react-native-video\": \"*\",\n    \"react-native-localize\": \"*\"\n  },\n  \"jest\": {\n    \"preset\": \"react-native\",\n    \"modulePathIgnorePatterns\": [\n      \"<rootDir>/lib/\"\n    ]\n  },\n  \"commitlint\": {\n    \"extends\": [\n      \"@commitlint/config-conventional\"\n    ]\n  },\n  \"release-it\": {\n    \"git\": {\n      \"commitMessage\": \"chore: release ${version}\",\n      \"tagName\": \"v${version}\"\n    },\n    \"npm\": {\n      \"publish\": true\n    },\n    \"github\": {\n      \"release\": true\n    }\n  },\n  \"eslintConfig\": {\n    \"root\": true,\n    \"parser\": \"@babel/eslint-parser\",\n    \"extends\": [\n      \"@react-native-community\",\n      \"prettier\"\n    ],\n    \"rules\": {\n      \"prettier/prettier\": \"error\"\n    }\n  },\n  \"eslintIgnore\": [\n    \"node_modules/\",\n    \"lib/\"\n  ]\n}\n"
  },
  {
    "path": "packages/ChatUiKit/packager.js",
    "content": "const { log } = require('console');\nconst fs = require('fs');\nconst path = require('path');\n\nfunction getFiles(dir, files_) {\n    files_ = files_ || [];\n    const files = fs.readdirSync(dir);\n    for (let i in files) {\n        const name = dir + '/' + files[i];\n        if (fs.statSync(name).isDirectory()) {\n            getFiles(name, files_);\n        } else {\n          const extName = path.extname(files[i]);\n           if (extName == \".png\" || extName == \".wav\" || extName == \".jpg\" || extName == \"jpeg\")\n            files_.push({name, dist: name.replace(\"src/\",\"lib/\")});\n        }\n    }\n    return files_;\n}\n\nfunction copyFiles() {\n  const files = getFiles('src');\n\n  for (let i in files) {\n    fs.copyFile(files[i].name, files[i].dist, (e) => {\n      if (e) {\n        console.log(e);\n      }\n    })\n}\n}\ncopyFiles()"
  },
  {
    "path": "packages/ChatUiKit/react-native-cometchat-ui-kit.podspec",
    "content": "require \"json\"\n\npackage = JSON.parse(File.read(File.join(__dir__, \"package.json\")))\n\nfolly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'\n\nPod::Spec.new do |s|\n  s.name         = \"react-native-cometchat-ui-kit\"\n  s.version      = package[\"version\"]\n  s.summary      = package[\"description\"][0...140]  # Shorten summary to max 140 characters\n  s.homepage     = package[\"homepage\"]\n  s.license      = package[\"license\"]\n  s.authors      = package[\"author\"].is_a?(Hash) ? [package[\"author\"]] : package[\"author\"]\n\n  s.platforms    = { :ios => \"11.0\" }\n  s.source       = { :git => \"https://github.com/cometchat/cometchat-uikit-react-native.git\", :tag => \"#{s.version}\" }\n\n  s.source_files = \"ios/**/*.{h,m,mm,swift}\"\n\n  s.static_framework = true  # Set static_framework for the main spec if needed\n  s.dependency \"React-Core\"\n\n  s.subspec \"Video\" do |ss|\n    ss.source_files  = \"ios/Video/*.{h,m}\"\n  end\n  \n  s.subspec \"VideoCaching\" do |ss|\n    ss.dependency \"react-native-cometchat-ui-kit/Video\"\n    ss.dependency \"SPTPersistentCache\", \"~> 1.1.0\"\n    ss.dependency \"DVAssetLoaderDelegate\", \"~> 0.3.1\"\n\n    ss.source_files = \"ios/VideoCaching/**/*.{h,m}\"\n  end\n\n  # Conditional compiler flags and dependencies for the new architecture\n  if ENV['RCT_NEW_ARCH_ENABLED'] == '1'\n    s.compiler_flags = \"#{folly_compiler_flags} -DRCT_NEW_ARCH_ENABLED=1\"\n    s.pod_target_xcconfig = {\n      \"HEADER_SEARCH_PATHS\" => \"\\\"$(PODS_ROOT)/Header/Public/**\\\"\",\n      \"CLANG_CXX_LANGUAGE_STANDARD\" => \"c++17\"\n    }\n    \n    # s.dependency \"React-Codegen\"\n    # s.dependency \"RCT-Folly\"\n    # s.dependency \"RCTRequired\"\n    # s.dependency \"RCTTypeSafety\"\n    # s.dependency \"ReactCommon/turbomodule/core\"\n    # s.dependency \"RCTSlider\"\n  end\nend"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatAIAssistantChatHistory/CometChatAIAssistantChatHistory.tsx",
    "content": "import React, { useState, useRef, useCallback, useEffect, useMemo, JSX } from 'react';\nimport {\n  View,\n  Text,\n  SectionList,\n  TouchableOpacity,\n  ActivityIndicator,\n  GestureResponderEvent,\n} from 'react-native';\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { useTheme } from \"../theme\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { ChatHistoryStyle, getChatHistoryStyleLight } from \"./style\";\nimport { Skeleton } from \"./Skeleton\";\nimport { CometChatTooltipMenu } from \"../shared/views/CometChatTooltipMenu\";\nimport { CometChatConfirmDialog } from \"../shared/views/CometChatConfirmDialog\";\nimport Delete from \"../shared/icons/components/delete\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\n\ninterface CometChatAIAssistantChatHistoryProps {\n  /**\n   * A `CometChat.User` object representing the participant of the chat whose message history is displayed.\n   */\n  user?: CometChat.User;\n\n  /**\n   * A `CometChat.Group` object representing the group whose message history is displayed.\n   */\n  group?: CometChat.Group;\n\n  /**\n   * Callback function triggered when an error occurs during message fetching.\n   */\n  onError?: ((error: CometChat.CometChatException) => void) | null;\n\n  /**\n   * Callback function triggered when clicked on closeIcon button\n   */\n  onClose?: (() => void) | undefined;\n\n  /**\n   * Callback function triggered when clicked on a message\n   */\n  onMessageClicked?: ((message: CometChat.BaseMessage) => void) | undefined;\n\n  /**\n   * Callback when agent new chat button is clicked \n   */\n  onNewChatButtonClick?: () => void;\n\n  /**\n   * Custom styling for the chat history component.\n   */\n  style?: DeepPartial<ChatHistoryStyle>;\n  /**\n   * Custom loading state view component\n   */\n  LoadingStateView?: () => JSX.Element;\n}\n\nenum States {\n  loading = \"loading\",\n  loaded = \"loaded\", \n  empty = \"empty\",\n  error = \"error\"\n}\n\ninterface GroupedMessage {\n  title: string;\n  data: CometChat.TextMessage[];\n}\n\nconst CometChatAIAssistantChatHistory: React.FC<CometChatAIAssistantChatHistoryProps> = (props) => {\n  const {\n    user,\n    group,\n    onError,\n    onClose,\n    onMessageClicked,\n    onNewChatButtonClick,\n    style = {},\n    LoadingStateView\n  } = props;\n\n  const theme = useTheme();\n  const {t}=useCometChatTranslation();\n  \n  // Create fallback styles if theme.chatHistoryStyles doesn't exist\n  const defaultStyles = useMemo(() => {\n    return (theme as any).chatHistoryStyles || getChatHistoryStyleLight(theme.color, theme.spacing, theme.typography);\n  }, [theme]);\n  \n  const mergedStyle = deepMerge(defaultStyles, style);\n\n  // State variables\n  // Only show text messages in history\n  const [messageList, setMessageList] = useState<CometChat.TextMessage[]>([]);\n  const [listState, setListState] = useState<States>(States.loading);\n  const [isLoadingMore, setIsLoadingMore] = useState(false);\n  \n  // Delete functionality states\n  const [confirmDelete, setConfirmDelete] = useState<number | undefined>(undefined);\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const longPressedMessage = useRef<CometChat.BaseMessage | undefined>(undefined);\n  const tooltipPosition = useRef({\n    pageX: 0,\n    pageY: 0,\n  });\n\n  // Refs\n  const messageListBuilderRef = useRef<CometChat.MessagesRequest | null>(null);\n  const loggedInUserRef = useRef<CometChat.User | null>(null);\n  const isFetchingRef = useRef<boolean>(false);\n  const messagesCountRef = useRef<number>(0);\n\n  // Error handler\n  const errorHandler = useCallback((error: any, context: string) => {\n    console.log(`Error in ${context}:`, error);\n    if (onError) {\n      onError(error);\n    }\n  }, [onError]);\n\n  /**\n   * Function to delete a message\n   */\n  const deleteMessage = useCallback((messageId: number) => {\n    const messageToDelete = messageList.find(msg => msg.getId() === messageId);\n    if (!messageToDelete) return;\n\n    CometChat.deleteMessage(messageId.toString())\n      .then((deletedMessage) => {\n        setMessageList(prevList => {\n          const newList = prevList.filter(msg => msg.getId() !== messageId);\n          messagesCountRef.current = newList.length;\n          if (newList.length === 0) {\n            setListState(States.empty);\n            // Call parent to reset agentic/parent state\n            if (onNewChatButtonClick) {\n              onNewChatButtonClick();\n            }\n          }\n          return newList;\n        });\n      })\n      .catch((error) => {\n        errorHandler(error, \"deleteMessage\");\n      });\n  }, [messageList, errorHandler]);\n\n  /**\n   * Function to get formatted date label\n   */\n  const getDateLabel = useCallback((timestamp: number): string => {\n    const messageDate = new Date(timestamp * 1000);\n    const today = new Date();\n    const yesterday = new Date(today);\n    yesterday.setDate(yesterday.getDate() - 1);\n    \n    if (messageDate.toDateString() === today.toDateString()) {\n      return 'Today';\n    } else if (messageDate.toDateString() === yesterday.toDateString()) {\n      return 'Yesterday';\n    } else {\n      // Check if it's within the last week\n      const oneWeekAgo = new Date(today);\n      oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);\n      \n      if (messageDate >= oneWeekAgo) {\n        return messageDate.toLocaleDateString('en-US', { weekday: 'long' });\n      } else {\n        return messageDate.toLocaleDateString('en-US', { \n          month: 'long', \n          day: 'numeric',\n          year: messageDate.getFullYear() !== today.getFullYear() ? 'numeric' : undefined\n        });\n      }\n    }\n  }, []);\n\n  /**\n   * Function to group messages by date\n   */\n  const groupedMessages = useMemo((): GroupedMessage[] => {\n    const groups: { [key: string]: CometChat.TextMessage[] } = {};\n    \n    messageList.forEach(message => {\n      const label = getDateLabel(message.getSentAt());\n      if (!groups[label]) {\n        groups[label] = [];\n      }\n      groups[label].push(message);\n    });\n    \n    // Sort groups by date priority\n    const sortOrder = ['Today', 'Yesterday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];\n    \n    return Object.entries(groups)\n      .sort(([a], [b]) => {\n        const aIndex = sortOrder.indexOf(a);\n        const bIndex = sortOrder.indexOf(b);\n        \n        if (aIndex !== -1 && bIndex !== -1) {\n          return aIndex - bIndex;\n        } else if (aIndex !== -1) {\n          return -1;\n        } else if (bIndex !== -1) {\n          return 1;\n        } else {\n          // Both are dates, sort by most recent first\n          return b.localeCompare(a);\n        }\n      })\n      .map(([title, data]) => ({ title, data }));\n  }, [messageList, getDateLabel]);\n\n  /**\n   * Function to extract text content from a message\n   */\n  const getMessageText = useCallback((message: CometChat.BaseMessage): string => {\n    try {\n      if (message instanceof CometChat.TextMessage) {\n        return message.getText();\n      } else if (message instanceof CometChat.MediaMessage) {\n        return `${message.getType()} message`;\n      } else if (message instanceof CometChat.CustomMessage) {\n        // Handle assistant messages\n        if (message.getType() === 'assistant' && message.getData()) {\n          const data = message.getData();\n          if (data.assistantMessageData?.getText) {\n            return data.assistantMessageData.getText();\n          } else if (data.text) {\n            return data.text;\n          }\n        }\n        return 'Custom message';\n      } else if (message.getType() === 'groupMember') {\n        return 'Group action message';\n      } else {\n        return 'Message';\n      }\n    } catch (error) {\n      errorHandler(error, \"getMessageText\");\n      return 'Message';\n    }\n  }, [errorHandler]);\n\n  /**\n   * Function to check if message can be deleted\n   */\n  const canDeleteMessage = useCallback((message: CometChat.BaseMessage): boolean => {\n    // Only allow deletion if user is the sender, message is not deleted, and not system/group\n    const isSender = message.getSender().getUid() === loggedInUserRef.current?.getUid();\n    const isDeletableCategory = message.getCategory && message.getCategory() === 'message';\n    const isNotDeleted = !message.getDeletedAt || !message.getDeletedAt();\n    return isSender && isDeletableCategory && isNotDeleted;\n  }, []);\n\n  /**\n   * Function to append messages to the end of the current message list\n   */\n  const appendMessages = useCallback(\n    (messages: CometChat.BaseMessage[]) => {\n      return new Promise<boolean>((resolve) => {\n        try {\n          // Only keep text messages with non-empty text, safely check getText\n          const textMessages = messages.filter(\n            m =>\n              m instanceof CometChat.TextMessage &&\n              typeof m.getText === \"function\" &&\n              typeof m.getText() === \"string\" &&\n              m.getText().trim() !== \"\"\n          ) as CometChat.TextMessage[];\n          setMessageList((prevMessageList: CometChat.TextMessage[]) => {\n            const newList = [...prevMessageList, ...textMessages];\n            messagesCountRef.current = newList.length;\n            return newList;\n          });\n          resolve(true);\n        } catch (error: any) {\n          errorHandler(error, \"appendMessages\");\n          resolve(false);\n        }\n      });\n    },\n    [errorHandler]\n  );\n\n  /**\n   * Function to fetch previous messages\n   */\n  const fetchPreviousMessages = useCallback(() => {\n    return new Promise<boolean>(async (resolve, reject) => {\n      try {\n        if (isFetchingRef.current) {\n          resolve(true);\n          return;\n        }\n\n        isFetchingRef.current = true;\n        setIsLoadingMore(messagesCountRef.current > 0);\n\n        if (messageListBuilderRef.current) {\n          const messages = await messageListBuilderRef.current.fetchPrevious();\n\n          if (messages.length > 0) {\n            await appendMessages(messages.reverse());\n            setListState(States.loaded);\n          } else {\n            if (messages.length == 0 && messagesCountRef.current === 0) {\n              setListState(States.empty);\n            } else {\n              setListState(States.loaded);\n            }\n          }\n        }\n\n        isFetchingRef.current = false;\n        setIsLoadingMore(false);\n        resolve(true);\n      } catch (error: any) {\n        isFetchingRef.current = false;\n        setIsLoadingMore(false);\n        if (messagesCountRef.current <= 0) {\n          setListState(States.error);\n        }\n        errorHandler(error, \"fetchPreviousMessages\");\n        reject(error);\n      }\n    });\n  }, [appendMessages, errorHandler]);\n\n  /**\n   * Callback to be executed when the list is scrolled to the end\n   */\n  const onEndReached = useCallback(() => {\n    if (listState === States.loaded && !isFetchingRef.current) {\n      fetchPreviousMessages();\n    }\n  }, [fetchPreviousMessages, listState]);\n\n  /**\n   * Function to render section header\n   */\n  const renderSectionHeader = useCallback(({ section }: { section: GroupedMessage }) => {\n    return (\n      <View style={mergedStyle.sectionHeaderStyle}>\n        <Text style={mergedStyle.sectionHeaderTextStyle}>\n          {section.title}\n        </Text>\n      </View>\n    );\n  }, [mergedStyle]);\n\n  /**\n   * Function to render message item\n   */\n  const renderMessageItem = useCallback(({ item: message }: { item: CometChat.TextMessage }) => {\n    // Only render if message is a text message with non-empty text\n    if (!(message instanceof CometChat.TextMessage)) return null;\n    const messageText = getMessageText(message);\n    if (\n      !messageText ||\n      typeof messageText !== \"string\" ||\n      messageText.trim() === \"\"\n    )\n      return null;\n    const deletable = canDeleteMessage(message);\n    return (\n      <TouchableOpacity \n        style={mergedStyle.messageItemStyle}\n        onPress={() => {\n          if (onMessageClicked) {\n            onMessageClicked(message);\n          }\n        }}\n        onLongPress={(e: GestureResponderEvent) => {\n          if (deletable) {\n            longPressedMessage.current = message;\n            tooltipPosition.current = {\n              pageX: e.nativeEvent.pageX,\n              pageY: e.nativeEvent.pageY,\n            };\n            setTooltipVisible(true);\n          }\n        }}\n      >\n        <Text \n          style={mergedStyle.messageTextStyle}\n          numberOfLines={1}\n          ellipsizeMode=\"tail\"\n        >\n          {messageText}\n        </Text>\n      </TouchableOpacity>\n    );\n  }, [getMessageText, mergedStyle, onMessageClicked, canDeleteMessage]);\n\n\n  /**\n   * Initialize component and fetch logged-in user\n   */\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((userObject: CometChat.User | null) => {\n        if (userObject) {\n          loggedInUserRef.current = userObject;\n        }\n      })\n      .catch((error: CometChat.CometChatException) => {\n        errorHandler(error, \"getLoggedinUser\");\n      });\n  }, [user, group, errorHandler]);\n\n  /**\n   * Initialize message list manager when user or group changes\n   */\n  useEffect(() => {\n    const initializeChat = async () => {\n      try {\n        if (user || group) {\n          // Create messages request builder with hideReplies set to true to only show parent messages\n          const builder = new CometChat.MessagesRequestBuilder()\n            .hideReplies(true)\n            .setLimit(30)\n          if (user) {\n            builder.setUID(user.getUid());\n          } else if (group) {\n            builder.setGUID(group.getGuid());\n          }\n\n          messageListBuilderRef.current = builder.build();\n          messagesCountRef.current = 0;\n          setMessageList([]);\n          setListState(States.loading);\n\n          // Fetch initial messages\n          if (!isFetchingRef.current) {\n            isFetchingRef.current = true;\n            try {\n              const messages = await messageListBuilderRef.current.fetchPrevious();\n              if (messages.length > 0) {\n                const textMessages = messages\n                  .reverse()\n                  .filter(\n                    m =>\n                      m instanceof CometChat.TextMessage &&\n                      typeof m.getText === \"function\" &&\n                      typeof m.getText() === \"string\" &&\n                      m.getText().trim() !== \"\"\n                  ) as CometChat.TextMessage[];\n                setMessageList(textMessages);\n                messagesCountRef.current = textMessages.length;\n                setListState(textMessages.length > 0 ? States.loaded : States.empty);\n              } else {\n                setListState(States.empty);\n              }\n            } catch (error) {\n              setListState(States.error);\n              errorHandler(error, \"initial fetch\");\n            } finally {\n              isFetchingRef.current = false;\n            }\n          }\n        }\n      } catch (error) {\n        errorHandler(error, \"useEffect - initialization\");\n      }\n    };\n\n    initializeChat();\n  }, [user, group, errorHandler]);\n\n  const getLoadingView = () => {\n    if (LoadingStateView) return <LoadingStateView />;\n    return (\n      <Skeleton \n        style={{\n          containerBackgroundColor: mergedStyle.containerStyle?.backgroundColor || theme.color.background3,\n        }}\n      />\n    );\n  };\n\n  const getEmptyView = () => {\n    return (\n      <View style={mergedStyle.centerContainerStyle}>\n        <Text style={mergedStyle.stateTitleStyle}>\n          {t(\"NO_CONVERSATION_HISTORY_FOUND\")}\n        </Text>\n        <Text style={mergedStyle.stateTextStyle}>\n          {t(\"START_A_CHAT\")}\n        </Text>\n      </View>\n    );\n  };\n\n  const getErrorView = () => {\n    return (\n      <View style={mergedStyle.centerContainerStyle}>\n        <Text style={[mergedStyle.stateTitleStyle, { color: theme.color.error }]}>\n          {t(\"OOPS!\")}\n        </Text>\n        <Text style={mergedStyle.stateTextStyle}>\n          {t(\"LOOKS_LIKE_SOMETHING_WENT_WRONG\")}\n        </Text>\n      </View>\n    );\n  };\n\n  // const renderFooter = () => {\n  //   if (isLoadingMore) {\n  //     return (\n  //       <View style={mergedStyle.footerLoaderStyle}>\n  //         <ActivityIndicator size=\"small\" color={theme.color.primary} />\n  //       </View>\n  //     );\n  //   }\n  //   return null;\n  // };\n\n  const keyExtractor = useCallback((item: CometChat.TextMessage, index: number) => {\n    return `${item.getId()}_${item.getMuid()}_${index}`;\n  }, []);\n\n  return (\n    <View style={mergedStyle.containerStyle}>\n      {/* Tooltip Menu for Delete Option */}\n      {/* Only show tooltip menu if the message is deletable */}\n      {longPressedMessage.current && canDeleteMessage(longPressedMessage.current) && (\n        <CometChatTooltipMenu\n          visible={tooltipVisible}\n          onClose={() => {\n            setTooltipVisible(false);\n          }}\n          event={{\n            nativeEvent: tooltipPosition.current,\n          }}\n          menuItems={[{\n            text: \"Delete\",\n            onPress: () => {\n              setConfirmDelete(longPressedMessage.current?.getId());\n              setTooltipVisible(false);\n            },\n            icon: (\n              <Delete\n                color={theme.color.error}\n                height={theme.spacing.spacing.s6}\n                width={theme.spacing.spacing.s6}\n              />\n            ),\n            textStyle: { color: theme.color.error },\n          }]}\n        />\n      )}\n\n      {/* Confirm Delete Dialog */}\n      <CometChatConfirmDialog\n        titleText=\"Delete Message\"\n        icon={<Icon name='delete' size={theme.spacing.spacing.s12} color={theme.color.error} />}\n        cancelButtonText=\"Cancel\"\n        confirmButtonText=\"Delete\"\n        messageText=\"Are you sure you want to delete this message?\"\n        isOpen={confirmDelete !== undefined}\n        onCancel={() => setConfirmDelete(undefined)}\n        onConfirm={() => {\n          if (confirmDelete) {\n            deleteMessage(confirmDelete);\n          }\n          setConfirmDelete(undefined);\n        }}\n      />\n\n      {/* Header */}\n      <View style={mergedStyle.headerStyle}>\n        <View style={mergedStyle.headerContentStyle}>\n          <TouchableOpacity\n            style={mergedStyle.closeButtonStyle}\n            onPress={onClose}\n          >\n            <Icon name=\"close-fill\" width={24} height={24} color={theme.color.textSecondary} />\n          </TouchableOpacity>\n          <Text style={mergedStyle.headerTitleStyle}>\n            {t(\"CHAT_HISTORY\")}\n          </Text>\n        </View>\n      </View>\n\n      {/* New Chat Button */}\n      <TouchableOpacity\n        style={mergedStyle.newChatButtonStyle}\n        onPress={onNewChatButtonClick}\n      >\n        <Icon name=\"ai-new-chat\" width={24} height={24} color={theme.color.textSecondary} />\n        <Text style={mergedStyle.newChatTextStyle}>\n          {t(\"NEW_CHAT\")}\n        </Text>\n      </TouchableOpacity>\n\n      {/* Content */}\n      {listState === States.loading ? (\n        getLoadingView()\n      ) : listState === States.error ? (\n        getErrorView()\n      ) : listState === States.empty ? (\n        getEmptyView()\n      ) : (\n        <SectionList\n          sections={groupedMessages}\n          renderItem={renderMessageItem}\n          renderSectionHeader={renderSectionHeader}\n          keyExtractor={keyExtractor}\n          onEndReached={onEndReached}\n          onEndReachedThreshold={0.1}\n          showsVerticalScrollIndicator={false}\n          contentContainerStyle={mergedStyle.listContainerStyle}\n          stickySectionHeadersEnabled={true}\n        />\n      )}\n    </View>\n  );\n};\n\nexport { CometChatAIAssistantChatHistory };"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatAIAssistantChatHistory/Skeleton.tsx",
    "content": "import React, { useEffect, useMemo, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../theme\";\nimport type { ColorValue } from \"react-native\";\nimport { CometChatTheme } from \"../theme/type\";\n\n/**\n * Animated skeleton placeholder mimicking chat history in CometChat UI‑Kit.\n *\n * The component respects the defaults provided by `theme.chatHistoryStyles.skeletonStyle`,\n * but every visual property can be **overridden per instance** using the\n * `style` prop.\n */\nexport interface SkeletonProps {\n  /** Partial style overrides (theme fallback for omitted keys). */\n  style?: Partial<SkeletonStyle>;\n}\n\n/** Alias for the skeleton style slice inside the theme. */\ntype SkeletonStyle = {\n  backgroundColor?: ColorValue;\n  linearGradientColors?: [string, string];\n  shimmerBackgroundColor?: ColorValue;\n  shimmerOpacity?: number;\n  speed?: number;\n  containerBackgroundColor?: ColorValue;\n};\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Utility helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\nfunction getStyleValue<K extends keyof SkeletonStyle>(\n  key: K,\n  overrides: Partial<SkeletonStyle> | undefined,\n  theme: CometChatTheme\n): NonNullable<SkeletonStyle[K]> {\n  // Use userStyles as fallback since it's guaranteed to exist\n  const fallbackSkeletonStyle = theme.userStyles.skeletonStyle;\n  const themeValue = (theme as any).chatHistoryStyles?.skeletonStyle?.[key];\n  return ((overrides?.[key] as SkeletonStyle[K]) ??\n    themeValue ??\n    fallbackSkeletonStyle[key]) as NonNullable<SkeletonStyle[K]>;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Layout constants – adapted for chat history structure\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst { width: SCREEN_WIDTH } = Dimensions.get(\"window\");\n\nconst PADDING = 20;\nconst SECTION_HEADER_HEIGHT = 16;\nconst SECTION_SPACING = 25;\nconst MESSAGE_HEIGHT = 18;\nconst MESSAGE_SPACING = 32;\nconst SECTIONS_COUNT = 3;\nconst MESSAGES_PER_SECTION = 4;\n\n/** Total height required for the SVG canvas */\nconst TOTAL_HEIGHT =\n  SECTIONS_COUNT * (SECTION_HEADER_HEIGHT + SECTION_SPACING + MESSAGES_PER_SECTION * (MESSAGE_HEIGHT + MESSAGE_SPACING)) + \n  PADDING * 2;\n\n// ──────────────────────────────────────────────────────────────────────────────\n// SVG elements factory (memoized for perf)\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst useHistoryElements = (fill: ColorValue) =>\n  useMemo(() => {\n    const elements: React.ReactElement[] = [];\n    let currentY = PADDING;\n\n    // Chat history sections\n    for (let sectionIndex = 0; sectionIndex < SECTIONS_COUNT; sectionIndex++) {\n      // Section header (Today, Yesterday, etc.)\n      elements.push(\n        <Rect\n          key={`section-${sectionIndex}`}\n          x={PADDING}\n          y={currentY}\n          width={60 + sectionIndex * 20} // Varying widths for different date labels\n          height={SECTION_HEADER_HEIGHT}\n          rx={SECTION_HEADER_HEIGHT / 2}\n          fill={fill as string}\n        />\n      );\n      currentY += SECTION_HEADER_HEIGHT + SECTION_SPACING;\n\n      // Messages in this section\n      for (let messageIndex = 0; messageIndex < MESSAGES_PER_SECTION; messageIndex++) {\n        const messageWidth = SCREEN_WIDTH - PADDING * 2 - (messageIndex * 30); // Varying message widths\n        elements.push(\n          <Rect\n            key={`message-${sectionIndex}-${messageIndex}`}\n            x={PADDING}\n            y={currentY}\n            width={Math.max(messageWidth, SCREEN_WIDTH * 0.4)} // Minimum width\n            height={MESSAGE_HEIGHT}\n            rx={MESSAGE_HEIGHT / 2}\n            fill={fill as string}\n          />\n        );\n        currentY += MESSAGE_HEIGHT + MESSAGE_SPACING;\n      }\n      currentY += 10; // Extra spacing between sections\n    }\n\n    return elements;\n  }, [fill]);\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Component Implementation\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport const Skeleton: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n  const get = <K extends keyof SkeletonStyle>(key: K) => getStyleValue(key, style, theme);\n\n  // Shimmer animation ------------------------------------------\n  const translate = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const speed = get(\"speed\");\n    const duration = 1000 / speed;\n    const loop = Animated.loop(\n      Animated.timing(translate, {\n        toValue: 1,\n        duration,\n        easing: Easing.linear,\n        useNativeDriver: false, // SVG not yet compatible with native driver\n      })\n    );\n\n    loop.start();\n    return () => loop.stop();\n  }, [get, translate]);\n\n  const translateX = translate.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-SCREEN_WIDTH * 2, SCREEN_WIDTH],\n  });\n\n  // Pre‑build elements (bottom gradient + top mask)\n  const elementsGradient = useHistoryElements(\"url(#gradient)\");\n  const elementsSolid = useHistoryElements(String(get(\"backgroundColor\")));\n\n  return (\n    <ScrollView\n      scrollEnabled={false}\n      showsVerticalScrollIndicator={false}\n      style={{ backgroundColor: get(\"containerBackgroundColor\") }}\n    >\n      {/* Bottom layer (gradient fill) */}\n      <Svg\n        height={TOTAL_HEIGHT}\n        width={SCREEN_WIDTH}\n        fill='none'\n        preserveAspectRatio='xMidYMid meet'\n      >\n        <Defs>\n          <LinearGradient\n            id='gradient'\n            x1='0'\n            y1='0'\n            x2={SCREEN_WIDTH}\n            y2='0'\n            gradientUnits='userSpaceOnUse'\n          >\n            {(() => {\n              const colors = get(\"linearGradientColors\");\n              return [\n                <Stop key={0} stopColor={colors[0]} />,\n                <Stop key={1} offset='1' stopColor={colors[1]} />,\n              ];\n            })()}\n          </LinearGradient>\n        </Defs>\n        {elementsGradient}\n      </Svg>\n\n      {/* Moving shimmer highlight (rendered twice for coverage) */}\n      {[0, SCREEN_WIDTH / 2].map((offset) => (\n        <Animated.View\n          key={offset}\n          style={[\n            styles.shimmer,\n            {\n              transform: [\n                { translateX: Animated.add(translateX, offset) },\n                { translateY: -20 },\n                { rotate: \"15deg\" },\n              ],\n              backgroundColor: get(\"shimmerBackgroundColor\"),\n              opacity: get(\"shimmerOpacity\"),\n            },\n          ]}\n        />\n      ))}\n\n      {/* Top mask – solid shapes clip the shimmer to elements */}\n      <View style={StyleSheet.absoluteFill} pointerEvents='none'>\n        <Svg\n          height={TOTAL_HEIGHT}\n          width={SCREEN_WIDTH}\n          fill='none'\n          preserveAspectRatio='xMidYMid meet'\n        >\n          {elementsSolid}\n        </Svg>\n      </View>\n    </ScrollView>\n  );\n};\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Styles\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst styles = StyleSheet.create({\n  shimmer: {\n    position: \"absolute\",\n    width: \"25%\", // thin bar for highlight\n    top: 0,\n    bottom: 0,\n  },\n});"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatAIAssistantChatHistory/index.ts",
    "content": "export { CometChatAIAssistantChatHistory } from \"./CometChatAIAssistantChatHistory\";\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatAIAssistantChatHistory/style.ts",
    "content": "import { ColorValue, ViewStyle, TextStyle } from \"react-native\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\n\nexport type ChatHistoryStyle = {\n  containerStyle: ViewStyle;\n  headerStyle: ViewStyle;\n  headerContentStyle: ViewStyle;\n  headerTitleStyle: TextStyle;\n  closeButtonStyle: ViewStyle;\n  closeButtonTextStyle: TextStyle;\n  newChatButtonStyle: ViewStyle;\n  newChatTextStyle: TextStyle;\n  listContainerStyle: ViewStyle;\n  sectionHeaderStyle: ViewStyle;\n  sectionHeaderTextStyle: TextStyle;\n  messageItemStyle: ViewStyle;\n  messageTextStyle: TextStyle;\n  centerContainerStyle: ViewStyle;\n  stateTitleStyle: TextStyle;\n  stateTextStyle: TextStyle;\n  footerLoaderStyle: ViewStyle;\n  skeletonStyle: {\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n  };\n};\n\nexport const getChatHistoryStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<ChatHistoryStyle> => {\n  return {\n    containerStyle: {\n      flex: 1,\n      backgroundColor: color.background3,\n    },\n    headerStyle: {\n      paddingTop: spacing.padding.p4,\n      paddingBottom: spacing.padding.p4,\n      backgroundColor: color.background3,\n    },\n    headerContentStyle: {\n      flexDirection: 'row',\n      alignItems: 'center',\n      paddingHorizontal: spacing.padding.p3,\n      borderBottomColor: color.borderDefault,\n      borderBottomWidth: 1,\n      paddingBottom: spacing.padding.p4,\n      width: '100%',\n    },\n    headerTitleStyle: {\n      fontSize: typography.heading4.medium.fontSize,\n      fontWeight: typography.heading4.medium.fontWeight,\n      color: color.textPrimary,\n      marginLeft: spacing.margin.m2,\n      ...typography.heading4.medium,\n      fontFamily: typography.heading4.medium.fontFamily,\n    },\n    closeButtonStyle: {\n      width: 32,\n      height: 32,\n      alignItems: 'center',\n      justifyContent: 'center',\n    },\n    closeButtonTextStyle: {\n      fontSize: 18,\n      color: color.textSecondary,\n    },\n    newChatButtonStyle: {\n      flexDirection: 'row',\n      alignItems: 'center',\n      paddingHorizontal: spacing.padding.p4,\n      paddingVertical: spacing.padding.p2,\n      marginBottom: spacing.margin.m2,\n      backgroundColor: color.background3,\n    },\n    newChatTextStyle: {\n      fontSize: typography.body.regular.fontSize,\n      marginLeft: spacing.margin.m3,\n      fontWeight: typography.body.regular.fontWeight,\n      color: color.textPrimary,\n      ...typography.body.regular,\n    },\n    listContainerStyle: {\n      paddingHorizontal: spacing.padding.p5,\n    },\n    sectionHeaderStyle: {\n      paddingVertical: spacing.padding.p2,\n      backgroundColor: color.background3,\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n    },\n    sectionHeaderTextStyle: {\n      fontSize: typography.caption1.medium.fontSize,\n      fontWeight: typography.caption1.medium.fontWeight,\n      textTransform: 'capitalize',\n      color: color.textTertiary,\n      ...typography.caption1.medium,\n    },\n    messageItemStyle: {\n      paddingVertical: spacing.padding.p3,\n    },\n    messageTextStyle: {\n      fontSize: typography.body.regular.fontSize,\n      lineHeight: 20,\n      color: color.textPrimary,\n      ...typography.body.regular,\n    },\n    centerContainerStyle: {\n      flex: 1,\n      justifyContent: 'center',\n      alignItems: 'center',\n      padding: spacing.padding.p5,\n      backgroundColor: color.background3,\n    },\n    stateTitleStyle: {\n      fontSize: 18,\n      fontWeight: 'bold',\n      marginBottom: spacing.margin.m2,\n      color: color.textPrimary,\n      ...typography.heading3.bold,\n    },\n    stateTextStyle: {\n      fontSize: 16,\n      textAlign: 'center',\n      color: color.textSecondary,\n      ...typography.body.regular,\n    },\n    footerLoaderStyle: {\n      paddingVertical: spacing.padding.p4,\n      alignItems: 'center',\n    },\n    skeletonStyle: {\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n      shimmerBackgroundColor: color.background3,\n      shimmerOpacity: 0.01,\n      speed: 0.1,\n    },\n  };\n};\n\nexport const getChatHistoryStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<ChatHistoryStyle> => {\n  return deepMerge(getChatHistoryStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      linearGradientColors: [\"#383838\", \"#272727\"],\n      shimmerBackgroundColor: color.background3,\n      shimmerOpacity: 0.01,\n      speed: 0.1,\n    },\n  });\n};"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatCompactMessageComposer/CometChatCompactMessageComposer.tsx",
    "content": "import React, { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo } from 'react';\nimport {\n  Animated,\n  View,\n  TextInput,\n  TouchableOpacity,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  KeyboardAvoidingViewProps,\n  KeyboardAvoidingView,\n  Modal,\n  Platform,\n  NativeModules,\n  Text,\n  Image,\n  Dimensions,\n  ImageSourcePropType,\n  ImageStyle,\n  InteractionManager,\n  Keyboard,\n  ColorValue,\n  TouchableWithoutFeedbackProps,\n  ScrollView,\n  StyleSheet,\n} from 'react-native';\nimport { JSX } from 'react';\nimport RichTextEditor, {\n  type RichTextEditorRef,\n  type ContentChangeEvent,\n  type ActiveStylesState,\n} from '../CometChatRichTextEditor';\n\n/**\n * Link tap event data emitted by the native editor when a user taps an existing link.\n * Matches the LinkTapEventData interface from the rich text editor bridge source.\n */\ninterface LinkTapEventData {\n  url: string;\n  text: string;\n  location: number;\n  length: number;\n}\nimport { Style } from './styles';\nimport {\n  calculateInputHeight,\n  resolveEffectiveMaxHeight,\n  getIconAlignment,\n  DEFAULT_MIN_HEIGHT,\n  DEFAULT_LINE_HEIGHT,\n  DEFAULT_PADDING_VERTICAL,\n} from './heightUtils';\n//@ts-ignore\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { startStreamingForRunId, stopStreamingForRunId, streamingState$ } from '../shared/services/stream-message.service';\nimport {\n  CometChatSoundManager,\n  CometChatMentionsFormatter,\n  CometChatUrlsFormatter,\n  CometChatTextFormatter,\n  ChatConfigurator,\n  CometChatUIKit,\n  CometChatBottomSheet,\n  CometChatActionSheet,\n  CometChatMediaRecorder,\n  CometChatMessagePreview,\n  CometChatSuggestionList,\n  SuggestionItem,\n  useCometChatTranslation,\n  CometChatUIEvents,\n  CometChatInlineAudioRecorder,\n} from '../shared';\nimport { useTheme } from '../theme';\nimport { CometChatTheme } from '../theme/type';\nimport { deepMerge } from '../shared/helper/helperFunctions';\nimport { CometChatMessageComposerAction, DeepPartial } from '../shared/helper/types';\nimport {\n  isAgenticUser as checkIsAgenticUser,\n  setQuotedMessageSafe,\n  deriveHideButton,\n  deriveDisableFeature,\n  ReplyMessageState,\n  parseMentionKey,\n  calcDeletionRange,\n  collectOverlappingMentions,\n  shiftRemainingMentionKeys,\n  MentionOverlap,\n} from '../shared/helper/composerHelpers';\nimport { Icon } from '../shared/icons/Icon';\nimport { CometChatSendButtonView } from '../shared/views/CometChatSendButtonView/CometChatSendButtonView';\nimport { CometChatLinkConfirmPopup } from '../shared/views/CometChatLinkConfirmPopup';\nimport {\n  getUnixTimestampInMilliseconds,\n  messageStatus,\n} from '../shared/utils/CometChatMessageHelper';\nimport { MessageTypeConstants, ReceiverTypeConstants, ConversationOptionConstants, MentionsVisibility, MentionsTargetElement, ViewAlignment, EnterKeyBehavior } from '../shared/constants/UIKitConstants';\nimport { MessageEvents } from '../shared/events';\nimport { CometChatUIEventHandler } from '../shared/events/CometChatUIEventHandler/CometChatUIEventHandler';\nimport { CometChatMessageEvents } from '../shared/events/CometChatMessageEvents';\nimport { ICONS } from './resources';\nimport { CommonUtils } from '../shared/utils/CommonUtils';\nimport { isCursorWithinMentionRange, getMentionRangeAtCursor } from '../shared/utils/MentionUtils';\nimport { stripMarkdown } from '../shared/utils/MarkdownUtils';\nimport { commonVars } from '../shared/base/vars';\nimport { permissionUtil } from '../shared/utils/PermissionUtil';\nimport { CheckPropertyExists } from '../shared/helper/functions';\nimport { CometChatStickerKeyboard } from '../extensions/Stickers/CometChatStickerKeyboard';\nimport { ExtensionTypeConstants } from '../extensions/ExtensionConstants';\n\nconst { FileManager, CommonUtil } = NativeModules;\n\n// Listener IDs at module scope (v5 pattern)\nconst editMessageListenerID = \"editMessageListener_\" + new Date().getTime();\nconst replyMessageListenerID = \"replyMessageListener_\" + new Date().getTime();\nconst uiEventListenerID = \"uiEventListener_\" + new Date().getTime();\nconst uiEventListenerShowID = \"uiEventListenerShow_\" + new Date().getTime();\nconst uiEventListenerHideID = \"uiEventListenerHide_\" + new Date().getTime();\n\n// Mic/sticker animation constants (hoisted to module level)\nconst MIC_ANIM_DURATION = 150;\nconst MIC_SLIDE_DISTANCE = 40;\n\n// URL detection regex for paste-over-selection link creation (hoisted to module level)\n// Note: paste-URL-over-selection is handled natively in iOS/Android editors\n\n/**\n * Styles for the custom rich text formatting toolbar.\n * Note: theme-dependent values (colors) are applied inline at render time.\n * Layout values use static constants since they don't vary by theme.\n */\nconst richTextToolbarStyles = StyleSheet.create({\n  btn: {\n    width: 36,\n    height: 36,\n    borderRadius: 6,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  separator: {\n    width: 1,\n    height: 20,\n    marginHorizontal: 4,\n  },\n});\n\n/**\n * Toolbar item configuration for the rich text formatting toolbar.\n * Items with type 'separator' render a vertical divider.\n * Items with type 'button' render a tappable icon.\n */\ntype ToolbarSeparator = { type: 'separator' };\ntype ToolbarButton = {\n  type: 'button';\n  key: string;\n  icon: any;\n  iconName?: string;\n  isActive?: (styles: ActiveStylesState) => boolean;\n  onPress: (\n    inputRef: React.RefObject<any>,\n    selectionPosition: { start: number; end: number },\n    inputTextRef: React.RefObject<string>,\n    setLinkText: (t: string) => void,\n    setLinkUrl: (u: string) => void,\n    setShowLinkModal: (v: boolean) => void,\n  ) => void;\n};\ntype ToolbarItem = ToolbarSeparator | ToolbarButton;\n\nconst richTextToolbarItems: ToolbarItem[] = [\n  { type: 'button', key: 'bold', icon: null, iconName: 'format-bold', isActive: (s) => s.bold, onPress: (ref) => ref.current?.toggleBold() },\n  { type: 'button', key: 'italic', icon: null, iconName: 'format-italic', isActive: (s) => s.italic, onPress: (ref) => ref.current?.toggleItalic() },\n  { type: 'button', key: 'underline', icon: null, iconName: 'format-underlined', isActive: (s) => s.underline, onPress: (ref) => ref.current?.toggleUnderline() },\n  { type: 'button', key: 'strikethrough', icon: null, iconName: 'format-strikethrough', isActive: (s) => s.strikethrough, onPress: (ref) => ref.current?.toggleStrikethrough() },\n  { type: 'separator' },\n  {\n    type: 'button', key: 'link', icon: null, iconName: 'link',\n    onPress: (_ref, selectionPosition, inputTextRef, setLinkText, setLinkUrl, setShowLinkModal) => {\n      const { start, end } = selectionPosition;\n      const currentText = inputTextRef.current || '';\n      const selectedText = start !== end ? currentText.substring(start, end) : '';\n      setLinkText(selectedText);\n      setLinkUrl('');\n      setShowLinkModal(true);\n    },\n  },\n  { type: 'button', key: 'ordered-list', icon: null, iconName: 'format-list-numbered', isActive: (s) => s.blockType === 'numbered' || s.blockType === 'quoteNumbered', onPress: (ref) => ref.current?.setNumberedList() },\n  { type: 'button', key: 'unordered-list', icon: null, iconName: 'format-list-bulleted', isActive: (s) => s.blockType === 'bullet' || s.blockType === 'quoteBullet', onPress: (ref) => ref.current?.setBulletList() },\n  { type: 'separator' },\n  { type: 'button', key: 'blockquote', icon: null, iconName: 'format-quote', isActive: (s) => s.blockType === 'quote' || s.blockType === 'quoteBullet' || s.blockType === 'quoteNumbered', onPress: (ref) => ref.current?.setQuote() },\n  { type: 'button', key: 'inline-code', icon: null, iconName: 'code', isActive: (s) => s.code, onPress: (ref) => ref.current?.toggleCode() },\n  { type: 'button', key: 'code-block', icon: null, iconName: 'code-blocks', onPress: (ref) => ref.current?.toggleCodeBlock() },\n];\n\n/**\n * ActionSheetBoard component for displaying attachment options (v5 pattern)\n */\nconst ActionSheetBoard = (props: any) => {\n  const { shouldShow = false, onClose = () => {}, options = [], sheetRef, style } = props;\n  return (\n    <CometChatBottomSheet\n      style={{ maxHeight: Dimensions.get(\"window\").height * 0.49 }}\n      ref={sheetRef}\n      onClose={onClose}\n      isOpen={shouldShow}\n      doNotOccupyEntireHeight={true}\n    >\n      <CometChatActionSheet actions={options} style={style} />\n    </CometChatBottomSheet>\n  );\n};\n\n/**\n * RecordAudio component for voice recording (v5 pattern)\n */\nconst RecordAudio = (props: any) => {\n  const {\n    shouldShow = false,\n    onClose = () => {},\n    cometChatBottomSheetStyle = {},\n    sheetRef,\n    onPause = () => {},\n    onPlay = () => {},\n    onSend = (_recordedFile: string) => {},\n    onStop = (_recordedFile: string) => {},\n    onStart = () => {},\n    mediaRecorderStyle,\n  } = props;\n  return (\n    <CometChatBottomSheet\n      ref={sheetRef}\n      onClose={onClose}\n      style={cometChatBottomSheetStyle}\n      isOpen={shouldShow}\n    >\n      <CometChatMediaRecorder\n        onClose={onClose}\n        onPause={onPause}\n        onPlay={onPlay}\n        onSend={onSend}\n        onStop={onStop}\n        onStart={onStart}\n        style={mediaRecorderStyle}\n      />\n    </CometChatBottomSheet>\n  );\n};\n\n/**\n * MessagePreviewTray component for displaying edit/reply message preview (v5 pattern)\n */\nconst MessagePreviewTray = (props: any) => {\n  const { shouldShow = false, message = null, onClose = () => {}, title = '' } = props;\n  if (!shouldShow) return null;\n  return (\n    <CometChatMessagePreview\n      messagePreviewTitle={title}\n      message={message}\n      onCloseClick={onClose}\n    />\n  );\n};\n\n/**\n * AttachIconButton component for attachment button (v5 pattern)\n */\nconst AttachIconButton = (props: {\n  onPress: TouchableWithoutFeedbackProps[\"onPress\"];\n  icon: ImageSourcePropType | JSX.Element;\n  iconStyle: ImageStyle;\n}) => {\n  return (\n    <TouchableOpacity onPress={props.onPress}>\n      <Icon\n        name='add-circle'\n        icon={props.icon}\n        color={props.iconStyle.tintColor}\n        height={props.iconStyle.height}\n        width={props.iconStyle.width}\n        imageStyle={props.iconStyle}\n      />\n    </TouchableOpacity>\n  );\n};\n\n/**\n * EmojiButton component for SingleLineTextComposer.\n * A custom emoji/sticker button with proper styling (no extra padding).\n * Opens the sticker keyboard panel when pressed.\n */\ninterface EmojiButtonProps {\n  user?: CometChat.User;\n  group?: CometChat.Group;\n  composerIdMap: Map<string, any>;\n  replyToMessage?: CometChat.BaseMessage;\n  closeReplyPreview?: () => void;\n  editorRef?: React.RefObject<RichTextEditorRef | null>;\n}\n\nconst EmojiButton = ({ user, group, composerIdMap, replyToMessage, closeReplyPreview, editorRef }: EmojiButtonProps) => {\n  const [isPanelOpen, setIsPanelOpen] = React.useState(false);\n  const [keyboardOpen, setKeyboardOpen] = React.useState(false);\n  const theme = useTheme();\n  const loggedInUser = React.useRef<CometChat.User | null>(null);\n  const uiListenerIdRef = React.useRef<string>(`emoji_button_${Date.now()}`);\n  \n  // Use refs to store reply message info to avoid stale closures\n  const replyToMessageRef = React.useRef(replyToMessage);\n  const closeReplyPreviewRef = React.useRef(closeReplyPreview);\n\n  // Update refs when props change\n  React.useEffect(() => {\n    replyToMessageRef.current = replyToMessage;\n    closeReplyPreviewRef.current = closeReplyPreview;\n  }, [replyToMessage, closeReplyPreview]);\n\n  // Fetch logged-in user\n  React.useEffect(() => {\n    CometChat.getLoggedinUser().then((u: CometChat.User | null) => {\n      if (u) loggedInUser.current = u;\n    });\n  }, []);\n\n  // Keyboard event handling\n  const keyboardShowEvent = Platform.select({\n    ios: \"keyboardWillShow\",\n    android: \"keyboardDidShow\",\n  }) as any;\n\n  const keyboardHideEvent = Platform.select({\n    ios: \"keyboardWillHide\",\n    android: \"keyboardDidHide\",\n  }) as any;\n\n  React.useEffect(() => {\n    const keyboardDidShowListener = Keyboard.addListener(keyboardShowEvent, () => {\n      setKeyboardOpen(true);\n      if (isPanelOpen) {\n        closePanel();\n      }\n    });\n\n    const keyboardDidHideListener = Keyboard.addListener(keyboardHideEvent, () => {\n      setKeyboardOpen(false);\n    });\n\n    return () => {\n      keyboardDidShowListener.remove();\n      keyboardDidHideListener.remove();\n    };\n  }, [isPanelOpen, keyboardShowEvent, keyboardHideEvent]);\n\n  // Send sticker message\n  const sendCustomMessage = React.useCallback((sticker: any) => {\n    let receiverId = user?.getUid() || group?.getGuid();\n    let receiverType = user\n      ? CometChat.RECEIVER_TYPE.USER\n      : group\n        ? CometChat.RECEIVER_TYPE.GROUP\n        : undefined;\n\n    if (!receiverType) return;\n\n    let customType = ExtensionTypeConstants.sticker;\n    let parentId = composerIdMap?.get(\"parentMessageId\") || undefined;\n    let customMessage = new CometChat.CustomMessage(\n      receiverId,\n      receiverType,\n      customType,\n      sticker\n    );\n\n    customMessage.setCategory(CometChat.CATEGORY_CUSTOM as CometChat.MessageCategory);\n    customMessage.setParentMessageId(parentId);\n    customMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n    customMessage.setSender(loggedInUser.current!);\n    if (user || group) {\n      customMessage.setReceiver((user || group)!);\n    }\n    customMessage.shouldUpdateConversation(true);\n    customMessage.setMetadata({ incrementUnreadCount: true });\n\n    // Set quoted message if replying\n    const currentReplyMessage = replyToMessageRef.current;\n    if (currentReplyMessage) {\n      customMessage.setQuotedMessage(currentReplyMessage);\n      customMessage.setQuotedMessageId(currentReplyMessage.getId());\n    }\n\n    // Close reply preview\n    const currentClosePreview = closeReplyPreviewRef.current;\n    if (currentClosePreview) {\n      currentClosePreview();\n    }\n\n    CometChatUIKit.sendCustomMessage(customMessage)\n      .then((res: CometChat.BaseMessage) => {\n        if (currentReplyMessage) {\n          CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n            message: res,\n            status: messageStatus.success,\n          });\n        }\n      })\n      .catch((err: any) => {\n        console.error(\"Failed to send sticker:\", err);\n      });\n  }, [user, group, composerIdMap]);\n\n  const openPanel = React.useCallback(() => {\n    Keyboard.dismiss();\n    editorRef?.current?.blur();\n    setIsPanelOpen(true);\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.showPanel, {\n      alignment: ViewAlignment.composerBottom,\n      child: () => <CometChatStickerKeyboard onPress={sendCustomMessage} />,\n      panelId: \"sticker\",\n    });\n  }, [sendCustomMessage, editorRef]);\n\n  const closePanel = React.useCallback(() => {\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.hidePanel, {\n      alignment: ViewAlignment.composerBottom,\n      child: () => null,\n      panelId: \"sticker\",\n    });\n    setIsPanelOpen(false);\n  }, []);\n\n  const togglePanel = React.useCallback(() => {\n    if (isPanelOpen) {\n      closePanel();\n    } else {\n      if (keyboardOpen) {\n        Keyboard.dismiss();\n        setTimeout(() => {\n          openPanel();\n        }, 200);\n      } else {\n        openPanel();\n      }\n    }\n  }, [isPanelOpen, keyboardOpen, openPanel, closePanel]);\n\n  // Listen to global UI events\n  React.useEffect(() => {\n    const id = uiListenerIdRef.current;\n    CometChatUIEventHandler.addUIListener(id, {\n      hidePanel: (payload: any) => {\n        if (isPanelOpen) {\n          if (!payload || payload?.panelId === \"sticker\" || payload?.alignment === ViewAlignment.composerBottom) {\n            setIsPanelOpen(false);\n          }\n        }\n      },\n      showPanel: (payload: any) => {\n        if (payload?.panelId === \"sticker\") {\n          setIsPanelOpen(true);\n        }\n      },\n    });\n    return () => {\n      CometChatUIEventHandler.removeUIListener(id);\n    };\n  }, [isPanelOpen]);\n\n  return (\n    <TouchableOpacity\n      onPress={togglePanel}\n      style={Style.iconButton}\n      accessibilityHint='Opens the sticker panel'\n    >\n      <Icon\n        name={isPanelOpen ? \"sticker-fill\" : \"sticker\"}\n        width={24}\n        height={24}\n        color={isPanelOpen ? theme.color.primary : theme.color.iconSecondary}\n      />\n    </TouchableOpacity>\n  );\n};\n\n/**\n * Style interface for CometChatSingleLineMessageComposer.\n * This is a type alias for backward compatibility with v4 patterns.\n * In v5, styles are defined using DeepPartial<CometChatTheme[\"messageComposerStyles\"]>.\n */\nexport type SingleLineMessageComposerStyleInterface = DeepPartial<CometChatTheme[\"messageComposerStyles\"]>;\n\n/**\n * Props interface for the CometChatSingleLineMessageComposer component (v5 pattern)\n * \n * This interface matches CometChatMessageComposerInterface for full API compatibility.\n */\nexport interface CometChatCompactMessageComposerInterface {\n  /**\n   * Message composer identifier.\n   * @type {string | number}\n   */\n  id?: string | number;\n\n  /**\n   * CometChat SDK's user object.\n   * @type {CometChat.User}\n   */\n  user?: CometChat.User;\n\n  /**\n   * CometChat SDK's group object.\n   * @type {CometChat.Group}\n   */\n  group?: CometChat.Group;\n\n  /**\n   * Initial text value for the input.\n   * @type {string}\n   */\n  text?: string;\n\n  /**\n   * Placeholder text for the input.\n   * @type {string}\n   */\n  placeHolderText?: string;\n\n  /**\n   * Callback triggered when the input text changes.\n   * @param {string} text - The updated text.\n   */\n  onChangeText?: (text: string) => void;\n\n  /**\n   * Flag to turn off sound for outgoing messages.\n   * @type {boolean}\n   */\n  disableSoundForOutgoingMessages?: boolean;\n\n  /**\n   * Flag to turn off sound for messages (alias for disableSoundForOutgoingMessages).\n   * @type {boolean}\n   */\n  disableSoundForMessages?: boolean;\n\n  /**\n   * Custom audio sound to be played while sending messages.\n   * @type {*}\n   */\n  customSoundForOutgoingMessage?: any;\n\n  /**\n   * Custom audio sound for messages (alias for customSoundForOutgoingMessage).\n   * @type {*}\n   */\n  customSoundForMessage?: any;\n\n  /**\n   * Flag to disable typing events.\n   * @type {boolean}\n   */\n  disableTypingEvents?: boolean;\n\n  /**\n   * Initial text to be displayed in the composer.\n   * @type {string}\n   */\n  initialComposertext?: string;\n\n  /**\n   * Renders a preview section at the top of the composer.\n   */\n  HeaderView?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => JSX.Element;\n\n  /**\n   * Renders a footer section at the bottom of the composer.\n   */\n  FooterView?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => JSX.Element;\n\n  /**\n   * Callback triggered when the input text changes (alias for onChangeText).\n   * @param {string} text - The updated text.\n   */\n  onTextChange?: (text: string) => void;\n\n  /**\n   * Returns the attachment options for the composer.\n   */\n  attachmentOptions?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: Map<any, any>;\n  }) => CometChatMessageComposerAction[];\n\n  /**\n   * Replaces the default Auxiliary Button.\n   */\n  AuxiliaryButtonView?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: string | number;\n  }) => JSX.Element;\n\n  /**\n   * Replaces the default Secondary Button (attachment button).\n   */\n  SecondaryButtonView?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: string | number;\n  }) => JSX.Element;\n\n  /**\n   * Replaces the default Send Button.\n   */\n  SendButtonView?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: string | number;\n  }) => JSX.Element;\n\n  /**\n   * Message id required for threaded messages.\n   * @type {string | number}\n   */\n  parentMessageId?: string | number;\n\n  /**\n   * Custom styles for the message composer component.\n   */\n  style?: DeepPartial<CometChatTheme[\"messageComposerStyles\"]>;\n\n  /**\n   * Custom styles for the message composer (alias for style).\n   */\n  messageComposerStyle?: DeepPartial<CometChatTheme[\"messageComposerStyles\"]>;\n\n  /**\n   * Custom styles for the input field.\n   */\n  inputStyle?: StyleProp<TextStyle>;\n\n  /**\n   * Custom styles for the send button.\n   */\n  sendButtonStyle?: StyleProp<ViewStyle>;\n\n  /**\n   * Custom icon for the send button.\n   */\n  sendButtonIcon?: ImageSourcePropType;\n\n  /**\n   * Flag to hide the voice recording button.\n   * @type {boolean}\n   */\n  hideVoiceRecordingButton?: boolean;\n\n  /**\n   * Flag to hide voice recording (alias for hideVoiceRecordingButton).\n   * @type {boolean}\n   */\n  hideVoiceRecording?: boolean;\n\n  /**\n   * Custom icon URL for voice recording.\n   */\n  voiceRecordingIconURL?: ImageSourcePropType;\n\n  /**\n   * Custom styles for the media recorder.\n   */\n  mediaRecorderStyle?: any;\n\n  /**\n   * Custom pause icon URL for media recorder.\n   */\n  pauseIconUrl?: ImageSourcePropType;\n\n  /**\n   * Custom play icon URL for media recorder.\n   */\n  playIconUrl?: ImageSourcePropType;\n\n  /**\n   * Custom record icon URL for media recorder.\n   */\n  recordIconUrl?: ImageSourcePropType;\n\n  /**\n   * Custom delete icon URL for media recorder.\n   */\n  deleteIconUrl?: ImageSourcePropType;\n\n  /**\n   * Custom stop icon URL for media recorder.\n   */\n  stopIconUrl?: ImageSourcePropType;\n\n  /**\n   * Custom submit icon URL for media recorder.\n   */\n  submitIconUrl?: ImageSourcePropType;\n\n  /**\n   * Callback triggered when voice recording starts.\n   */\n  onVoiceRecordingStart?: () => void;\n\n  /**\n   * Callback triggered when voice recording ends.\n   * @param {string} recordedFile - The path to the recorded file.\n   */\n  onVoiceRecordingEnd?: (recordedFile: string) => void;\n\n  /**\n   * Callback triggered when the send button is pressed.\n   * @param {CometChat.BaseMessage} message - The base message object.\n   */\n  onSendButtonPress?: (message: CometChat.BaseMessage) => void;\n\n  /**\n   * Callback triggered when an error occurs.\n   * @param {CometChat.CometChatException} error - The error object.\n   */\n  onError?: (error: CometChat.CometChatException) => void;\n\n  /**\n   * Override properties for the KeyboardAvoidingView.\n   */\n  keyboardAvoidingViewProps?: KeyboardAvoidingViewProps;\n\n  /**\n   * Collection of text formatter classes to apply custom formatting.\n   */\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n\n  /**\n   * Flag to disable mention functionality.\n   */\n  disableMentions?: boolean;\n\n  /**\n   * Flag to disable the special group mention (@all / @channel etc.).\n   * @default false\n   */\n  disableMentionAll?: boolean;\n\n  /**\n   * Custom alias label for the group-wide mention.\n   * @default \"all\"\n   */\n  mentionAllLabel?: string;\n\n  /**\n   * Controls image quality when taking pictures from the camera.\n   * @default 20\n   */\n  imageQuality?: number;\n\n  /**\n   * If true, hides the camera option from the attachment options.\n   */\n  hideCameraOption?: boolean;\n\n  /**\n   * If true, hides the image attachment option from the attachment options.\n   */\n  hideImageAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the video attachment option from the attachment options.\n   */\n  hideVideoAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the audio attachment option from the attachment options.\n   */\n  hideAudioAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the file/document attachment option from the attachment options.\n   */\n  hideFileAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the polls option from the attachment options.\n   */\n  hidePollsAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the collaborative document option.\n   */\n  hideCollaborativeDocumentOption?: boolean;\n\n  /**\n   * If true, hides the collaborative whiteboard option.\n   */\n  hideCollaborativeWhiteboardOption?: boolean;\n\n  /**\n   * If true, hides the entire attachment button from the composer.\n   */\n  hideAttachmentButton?: boolean;\n\n  /**\n   * Custom icon for the attachment button.\n   */\n  attachmentIcon?: ImageSourcePropType;\n\n  /**\n   * Callback triggered when the attachment button is clicked.\n   */\n  onAttachmentClick?: () => void;\n\n  /**\n   * If true, hides the stickers button from the composer.\n   */\n  hideStickersButton?: boolean;\n\n  /**\n   * If true, hides the send button from the composer.\n   */\n  hideSendButton?: boolean;\n\n  /**\n   * If true, hides all auxiliary buttons.\n   */\n  hideAuxiliaryButtons?: boolean;\n\n  /**\n   * If true, hides the auxiliary button (alias for hideAuxiliaryButtons).\n   */\n  hideAuxiliaryButton?: boolean;\n\n  /**\n   * Additional attachment options to append to defaults.\n   */\n  addAttachmentOptions?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: Map<any, any>;\n  }) => CometChatMessageComposerAction[];\n\n  /**\n   * Determines the alignment of auxiliary buttons.\n   * @default \"left\"\n   */\n  auxiliaryButtonsAlignment?: \"left\" | \"right\";\n\n  /**\n   * Determines the alignment of auxiliary buttons (deprecated alias).\n   * @default \"right\"\n   */\n  auxiliaryButtonAlignment?: \"left\" | \"right\";\n\n  /**\n   * Custom send button view for AI agents (only applies to @agentic users)\n   */\n  AgentSendButtonView?: React.ComponentType<{\n    isButtonDisabled: boolean;\n    composerRef: any;\n  }>;\n\n  // ============================================\n  // Auto-Expand Configuration Props (SingleLineTextComposer specific)\n  // ============================================\n\n  /**\n   * Maximum number of lines before scrolling is enabled.\n   * @default 5\n   */\n  maxLines?: number;\n\n  /**\n   * Enable rich text formatting toolbar (bold, italic, underline, strikethrough, code).\n   * When true, an inline toolbar is shown above the text input.\n   * When false, the editor behaves like a plain TextInput.\n   * @default true\n   */\n  enableRichTextEditor?: boolean;\n\n  /**\n   * Hides all rich text formatting options.\n   * When true, the toolbar is always visible by default (no toggle needed).\n   * @default false\n   */\n  hideRichTextFormattingOptions?: boolean;\n\n  /**\n   * Behavior when Enter key is pressed (Android only — iOS always inserts new line).\n   * @default EnterKeyBehavior.NewLine\n   * @platform Android\n   */\n  enterKeyBehavior?: EnterKeyBehavior;\n\n  /**\n   * Show Bold/Italic/Underline/Strikethrough in the text selection context menu.\n   * When true, formatting options appear in the native text selection popup.\n   * @default true\n   */\n  showTextSelectionMenuItems?: boolean;\n\n  /**\n   * Maximum number of mentions allowed per message.\n   * @default 10\n   */\n  maxMentionLimit?: number;\n\n  /**\n   * Callback triggered when mention limit is reached.\n   */\n  onMentionLimitReached?: () => void;\n\n  /**\n   * Minimum height for the input in pixels.\n   * @default 40\n   */\n  minInputHeight?: number;\n\n  /**\n   * Maximum height for the input in pixels.\n   */\n  maxInputHeight?: number;\n}\n\n/**\n * Helper component for icon buttons (v5 pattern)\n */\nconst IconButton = (props: {\n  icon?: ImageSourcePropType | JSX.Element;\n  name?: string;\n  onClick: () => void;\n  buttonStyle?: StyleProp<ViewStyle>;\n  iconStyle?: ImageStyle;\n  disable?: boolean;\n  tintColor?: ColorValue;\n  testID?: string;\n}) => {\n  const { icon, name, onClick, buttonStyle, iconStyle, disable, tintColor, testID } = props;\n  return (\n    <TouchableOpacity\n      testID={testID}\n      activeOpacity={disable ? 1 : 0.7}\n      onPress={disable ? undefined : onClick}\n      style={[Style.imageButtonBase, buttonStyle]}\n    >\n      <Icon\n        name={name as any}\n        icon={icon}\n        color={tintColor}\n        height={iconStyle?.height || 24}\n        width={iconStyle?.width || 24}\n        imageStyle={iconStyle}\n      />\n    </TouchableOpacity>\n  );\n};\n\n/**\n * CometChatSingleLineMessageComposer - A compact, single-line text input component\n * for composing and sending text messages.\n * \n * This component is API-compatible with CometChatMessageComposer, allowing\n * developers to easily swap between the two based on their UI requirements.\n * \n * Key differences from CometChatMessageComposer:\n * - Single-line input (no multiline support)\n * - Text-only messaging (no attachments, voice recording, etc.)\n * - Compact UI suitable for inline messaging scenarios\n * \n * @example\n * // Basic usage\n * <CometChatSingleLineMessageComposer user={user} />\n * \n * @example\n * // With custom auxiliary buttons\n * <CometChatSingleLineMessageComposer\n *   user={user}\n *   AuxiliaryButtonView={() => <MyCustomButtons />}\n *   auxiliaryButtonsAlignment=\"right\"\n * />\n */\nexport const CometChatCompactMessageComposer = React.forwardRef(\n  (props: CometChatCompactMessageComposerInterface, ref) => {\n    const theme = useTheme();\n    const { t } = useCometChatTranslation();\n\n    const {\n      id,\n      user,\n      group,\n      text,\n      placeHolderText,\n      onChangeText,\n      style,\n      messageComposerStyle, // API compatibility\n      inputStyle,\n      sendButtonStyle,\n      sendButtonIcon,\n      SendButtonView,\n      onSendButtonPress,\n      AuxiliaryButtonView,\n      SecondaryButtonView,\n      auxiliaryButtonsAlignment,\n      auxiliaryButtonAlignment, // deprecated alias\n      hideAuxiliaryButton,\n      hideAuxiliaryButtons,\n      hideAttachmentButton: propHideAttachmentButton,\n      hideStickersButton: propHideStickersButton,\n      hideSendButton,\n      HeaderView,\n      FooterView,\n      parentMessageId,\n      disableTypingEvents: propDisableTypingEvents,\n      disableMentions: propDisableMentions,\n      disableSoundForMessages = true,\n      customSoundForMessage,\n      textFormatters,\n      onError,\n      keyboardAvoidingViewProps,\n      // Attachment props\n      attachmentIcon,\n      attachmentOptions,\n      onAttachmentClick,\n      imageQuality = 20,\n      // Additional attachment options\n      addAttachmentOptions,\n      // Individual hide options for attachments\n      hideCameraOption,\n      hideImageAttachmentOption,\n      hideVideoAttachmentOption,\n      hideAudioAttachmentOption,\n      hideFileAttachmentOption,\n      hidePollsAttachmentOption,\n      hideCollaborativeDocumentOption,\n      hideCollaborativeWhiteboardOption,\n      // Voice recording props\n      hideVoiceRecording: propHideVoiceRecording,\n      voiceRecordingIconURL,\n      mediaRecorderStyle,\n      pauseIconUrl,\n      playIconUrl,\n      recordIconUrl,\n      deleteIconUrl,\n      stopIconUrl,\n      submitIconUrl,\n      onVoiceRecordingStart,\n      onVoiceRecordingEnd,\n      // Auto-expand configuration props\n      maxLines,\n      minInputHeight,\n      maxInputHeight,\n      // Rich text formatting prop\n      enableRichTextEditor = true,\n      hideRichTextFormattingOptions = false,\n      enterKeyBehavior = EnterKeyBehavior.NewLine,\n      showTextSelectionMenuItems = true,\n      maxMentionLimit = 10,\n      onMentionLimitReached,\n      // Agentic user props\n      AgentSendButtonView,\n      // Initial text prop\n      initialComposertext,\n      // Mention configuration props\n      disableMentionAll = false,\n      mentionAllLabel = \"all\",\n    } = props;\n\n    // Auto-expand is always enabled (internal behavior)\n    const autoExpand = true;\n\n    // ============================================\n    // Agentic User Support\n    // ============================================\n\n    /**\n     * Helper function to check if the current user is an agentic user.\n     * Agentic users have the role '@agentic' and require special handling:\n     * - Auto-hide certain buttons (attachment, stickers, voice recording)\n     * - Disable typing events and mentions\n     * - Apply send button delay\n     * - Track parent message ID for threaded conversations\n     * \n     * @returns {boolean} True if user has role '@agentic'\n     */\n    const isAgenticUser = useCallback((): boolean => {\n      return checkIsAgenticUser(user);\n    }, [user]);\n\n    /**\n     * Derived state variables for automatic button hiding for agentic users.\n     * When user is agentic, these buttons are automatically hidden regardless of prop values.\n     * Uses shared helper function for consistency with CometChatMessageComposer.\n     */\n    const isAgentic = isAgenticUser();\n    const hideAttachmentButton = deriveHideButton(isAgentic, propHideAttachmentButton);\n    const hideStickersButton = deriveHideButton(isAgentic, propHideStickersButton);\n    const hideVoiceRecordingButton = deriveHideButton(isAgentic, propHideVoiceRecording);\n\n    /**\n     * Derived state variables for disabling typing events and mentions for agentic users.\n     * When user is agentic, these features are automatically disabled.\n     * Uses shared helper function for consistency with CometChatMessageComposer.\n     */\n    const disableTypingEvents = deriveDisableFeature(isAgentic, propDisableTypingEvents);\n    const disableMentions = deriveDisableFeature(isAgentic, propDisableMentions);\n\n    // Resolve alignment (support both prop names)\n    const resolvedAlignment = auxiliaryButtonsAlignment || auxiliaryButtonAlignment || 'right';\n\n    // Merge styles using deepMerge (support both style and messageComposerStyle)\n    // For agentic users, hide the divider line\n    const mergedComposerStyle = useMemo(() => {\n      const propStyle = style || messageComposerStyle || {};\n      const mergedStyle = deepMerge(theme.messageComposerStyles || {}, propStyle);\n      \n      // Hide divider for agentic users (matching CometChatMessageComposer behavior)\n      if (isAgentic) {\n        return {\n          ...mergedStyle,\n          messageInputStyles: {\n            ...mergedStyle.messageInputStyles,\n            dividerStyle: {\n              display: 'none' as const\n            }\n          }\n        };\n      }\n      return mergedStyle;\n    }, [theme.messageComposerStyles, style, messageComposerStyle, isAgentic]);\n\n    // Use merged style throughout the component\n    const resolvedStyle = mergedComposerStyle;\n\n    /**\n     * inputTextRef: holds the current text value without triggering re-renders.\n     * Updated on every keystroke from onContentChange.\n     * inputText state is only updated for programmatic changes (mentions, clear, edit)\n     * to push text to native via the text prop.\n     */\n    const inputTextRef = React.useRef(initialComposertext ?? text ?? '');\n    const [inputText, setInputText] = React.useState(initialComposertext ?? text ?? '');\n\n    /**\n     * Flag to skip the next onContentChange after a programmatic text update.\n     * Prevents handleTextChange from processing the echo of our own setText.\n     */\n    const skipNextContentChange = React.useRef(false);\n\n    /**\n     * Timestamp until which onSelectionChange events should be ignored.\n     * After programmatic setText (e.g. mention insertion), the native side\n     * fires multiple selection change events that reset cursor to 0.\n     * We ignore all selection events for a short window after setText.\n     */\n    const ignoreSelectionUntil = React.useRef(0);\n\n    /**\n     * Stores the latest blocks from the RichTextEditor's onContentChange.\n     * Used to convert structured formatting data to markdown when sending.\n     */\n    const blocksRef = React.useRef<any[]>([]);\n\n    /**\n     * Structured blocks to load into the native editor when entering edit mode.\n     * Parsed from the original message's markdown via markdownToBlocks.\n     * Passed as the `initialContent` prop so the editor displays WYSIWYG formatting.\n     * Reset to undefined when edit mode is exited or input is cleared.\n     */\n    const [editContentBlocks, setEditContentBlocks] = React.useState<any[] | undefined>(undefined);\n\n    const [isVisible, setIsVisible] = React.useState(false);\n    const [CustomView, setCustomView] = React.useState<React.ReactNode>(null);\n    const [CustomViewHeader, setCustomViewHeader] = React.useState<React.FC | React.ReactNode>(null);\n    const [CustomViewFooter, setCustomViewFooter] = React.useState<React.FC | React.ReactNode>(null);\n    const [kbOffset, setKbOffset] = React.useState(59);\n    const [showActionSheet, setShowActionSheet] = React.useState(false);\n    const [actionSheetItems, setActionSheetItems] = React.useState<CometChatMessageComposerAction[]>([]);\n    const [showInlineRecorder, setShowInlineRecorder] = React.useState(false);\n\n    // Mic/sticker animation: single Animated.Value drives mic slide + fade\n    // 0 = idle (mic visible), 1 = typing (mic hidden)\n    const micAnimValue = React.useRef(new Animated.Value(0)).current;\n    // Separate Animated.Value for mic layout width collapse (JS driver, supports layout props)\n    const micWidthAnim = React.useRef(new Animated.Value(1)).current;\n    const [messagePreview, setMessagePreview] = React.useState<{ message: any; mode: string } | null>(null);\n    const [showMentionList, setShowMentionList] = React.useState(false);\n    const [mentionsSearchData, setMentionsSearchData] = React.useState<Array<SuggestionItem>>([]);\n    const [suggestionListLoader, setSuggestionListLoader] = React.useState(false);\n    const [selectionPosition, setSelectionPosition] = React.useState<{ start: number; end: number }>({ start: 0, end: 0 });\n\n    // Active formatting styles state — tracks which styles are active at the cursor position\n    const [activeStyles, setActiveStyles] = React.useState<ActiveStylesState>({\n      bold: false,\n      italic: false,\n      underline: false,\n      strikethrough: false,\n      code: false,\n      codeBlock: false,\n      highlight: false,\n      blockType: 'paragraph',\n      alignment: 'left',\n    });\n    // Ref mirror of activeStyles — always current (no render-cycle delay).\n    // Used by shouldOpenList so the mention popup isn't blocked by stale state\n    // after exiting a code block.\n    const activeStylesRef = React.useRef<ActiveStylesState>(activeStyles);\n\n    // Link modal state (Slack-style: prefills selected text)\n    const [showLinkModal, setShowLinkModal] = React.useState(false);\n    const [linkText, setLinkText] = React.useState('');\n    const [linkUrl, setLinkUrl] = React.useState('');\n\n    // Link tap dialog state (edit/remove existing links)\n    const [linkTapData, setLinkTapData] = React.useState<{\n      url: string; text: string; location: number; length: number;\n    } | null>(null);\n    const [linkEditMode, setLinkEditMode] = React.useState(false);\n    const [editLinkUrl, setEditLinkUrl] = React.useState('');\n    const [editLinkText, setEditLinkText] = React.useState('');\n\n    // Track which code button is active (inline code vs code block)\n    const [codeBlockActive, setCodeBlockActive] = React.useState(false);\n\n    // Focus tracking for floating toolbar visibility\n    const [isFocused, setIsFocused] = React.useState(false);\n\n\n    /**\n     * Tracks links inserted via the JS modal.\n     * Maps link display text to URL. Used at send time to wrap\n     * matching text with [text](url) markdown as a fallback\n     * in case the native editor's blocks data is stale.\n     */\n    const pendingLinksRef = React.useRef<Map<string, string>>(new Map());\n\n    /**\n     * Handles link tap events from the native editor.\n     * Opens the link tap dialog with the tapped link's metadata.\n     */\n    const onLinkTap = useCallback((data: LinkTapEventData) => {\n      setLinkTapData(data);\n      setLinkEditMode(false);\n      setEditLinkUrl(data.url);\n      setEditLinkText(data.text);\n    }, []);\n\n    /**\n     * Sets input text programmatically — used for mentions, clear, edit message.\n     * Updates React state (for UI like send button) and pushes to native\n     * via imperative setText command (force=true bypasses typing guard).\n     * The text prop is NOT used (removed from JSX to prevent cursor jumping).\n     */\n    const setInputTextProgrammatic = React.useCallback((newText: string) => {\n      inputTextRef.current = newText;\n      skipNextContentChange.current = true;\n      setInputText(newText);\n      inputRef.current?.setText?.(newText);\n    }, []);\n\n    // ============================================\n    // Warning Message Support\n    // ============================================\n\n    /**\n     * State for tracking warning message (e.g., mention limit reached).\n     * When set, displays a warning in the CustomViewHeader area.\n     */\n    const [warningMessage, setWarningMessage] = React.useState<string>('');\n\n    // ============================================\n    // Reply Message Support\n    // ============================================\n\n    /**\n     * State for tracking reply message.\n     * When set, displays a reply preview and attaches the quoted message to sent messages.\n     */\n    const [replyMessage, setReplyMessage] = React.useState<ReplyMessageState | null>(null);\n\n    /**\n     * Ref to hold current replyMessage value to avoid stale closures in callbacks.\n     * This is necessary because attachment handlers and other callbacks may capture\n     * stale state values.\n     */\n    const replyMessageRef = React.useRef(replyMessage);\n\n    /**\n     * Callback to close the reply preview.\n     * Clears the reply message state and hides the preview.\n     */\n    const closeReplyPreview = useCallback(() => {\n      setReplyMessage(null);\n    }, []);\n\n    // ============================================\n    // Agentic User State Management\n    // ============================================\n\n    /**\n     * State for tracking send button delay for agentic users.\n     * When true, the send button is disabled for 1 second after sending.\n     */\n    const [isSendButtonDisabledForDelay, setIsSendButtonDisabledForDelay] = React.useState(false);\n\n    /**\n     * State for tracking streaming status for agentic users.\n     * Used to show/hide the stop button during AI response streaming.\n     */\n    const [isStreaming, setIsStreaming] = React.useState(false);\n    const [showStopButton, setShowStopButton] = React.useState(false);\n\n    // ============================================\n    // Auto-Expand Height State Management\n    // ============================================\n\n    /**\n     * Resolve the effective min and max heights for auto-expand mode.\n     * Uses props, style overrides, or defaults in that order of precedence.\n     */\n    const resolvedMinHeight = minInputHeight ?? DEFAULT_MIN_HEIGHT;\n    const resolvedLineHeight = DEFAULT_LINE_HEIGHT;\n    const resolvedMaxHeight = resolveEffectiveMaxHeight({\n      maxLines,\n      maxHeight: maxInputHeight,\n      lineHeight: resolvedLineHeight,\n      paddingVertical: DEFAULT_PADDING_VERTICAL,\n    });\n\n    /**\n     * State for tracking the current input height.\n     * Initialized to minHeight (single-line height).\n     * Only used when autoExpand is true.\n     *\n     */\n    const [inputHeight, setInputHeight] = React.useState<number>(resolvedMinHeight);\n\n    /**\n     * State for tracking whether the input is expanded beyond single-line height.\n     * Used for icon alignment and other UI adjustments.\n     *\n     */\n    const [isExpanded, setIsExpanded] = React.useState<boolean>(false);\n\n    // ============================================\n    // Debouncing for Height Updates\n    // ============================================\n\n    /**\n     * Debounce delay in milliseconds.\n     * 16ms corresponds to one frame at 60fps, ensuring smooth updates\n     * while preventing excessive re-renders during rapid typing.\n     *\n     */\n    const DEBOUNCE_DELAY_MS = 16;\n\n    /**\n     * Ref to track pending height update timeout.\n     * Used to debounce rapid content size changes and prevent flickering.\n     *\n     */\n    const pendingHeightUpdateRef = React.useRef<NodeJS.Timeout | null>(null);\n\n    /**\n     * Ref to store the latest pending height value.\n     * This ensures that only the final height value within the debounce\n     * window is applied to state (debounce idempotence).\n     *\n     */\n    const pendingHeightValueRef = React.useRef<number | null>(null);\n\n    /**\n     * Cleanup pending height update timeout on unmount.\n     * Prevents memory leaks and state updates on unmounted component.\n     *\n     */\n    useEffect(() => {\n      return () => {\n        if (pendingHeightUpdateRef.current) {\n          clearTimeout(pendingHeightUpdateRef.current);\n          pendingHeightUpdateRef.current = null;\n        }\n      };\n    }, []);\n\n    /**\n     * Apply the debounced height update to state.\n     * Only updates if the new height differs from current height.\n     *\n     * @param newHeight - The new height value to apply\n     *\n     */\n    const applyHeightUpdate = useCallback((newHeight: number) => {\n      // Only update state if height has changed\n      if (newHeight !== inputHeight) {\n        setInputHeight(newHeight);\n\n        // Update expanded state based on whether height exceeds minHeight\n        const newIsExpanded = newHeight > resolvedMinHeight;\n        if (newIsExpanded !== isExpanded) {\n          setIsExpanded(newIsExpanded);\n        }\n      }\n    }, [inputHeight, isExpanded, resolvedMinHeight]);\n\n    /**\n     * Handle content size changes from the TextInput.\n     * Uses debouncing to prevent flickering during rapid content changes.\n     * Calculates the new height using utility functions and updates state\n     * only when the height actually changes.\n     *\n     * This handler supports both expansion (when text is added) and contraction\n     * (when text is deleted). When text is deleted and content height decreases,\n     * the input height shrinks accordingly, returning to minHeight when the\n     * content fits within a single line.\n     *\n     * @param event - The content size change event from TextInput\n     *\n     */\n    const handleContentSizeChange = useCallback((event: { nativeEvent: { contentSize: { width: number; height: number } } }) => {\n      if (!autoExpand) {\n        return;\n      }\n\n      const { height: contentHeight } = event.nativeEvent.contentSize;\n\n      // Calculate the new height using the utility function\n      const newHeight = calculateInputHeight(contentHeight, resolvedMinHeight, resolvedMaxHeight);\n\n      // Store the latest pending height value\n      pendingHeightValueRef.current = newHeight;\n\n      // Clear any existing pending update\n      if (pendingHeightUpdateRef.current) {\n        clearTimeout(pendingHeightUpdateRef.current);\n      }\n\n      // Schedule debounced height update\n      pendingHeightUpdateRef.current = setTimeout(() => {\n        // Apply the latest pending height value\n        if (pendingHeightValueRef.current !== null) {\n          applyHeightUpdate(pendingHeightValueRef.current);\n          pendingHeightValueRef.current = null;\n        }\n        pendingHeightUpdateRef.current = null;\n      }, DEBOUNCE_DELAY_MS);\n    }, [autoExpand, resolvedMinHeight, resolvedMaxHeight, applyHeightUpdate]);\n\n    // Refs (v5 pattern: use React.useRef)\n    const loggedInUser = React.useRef<CometChat.User | null>(null);\n    const chatWith = React.useRef<string>('');\n    const chatWithId = React.useRef<string>('');\n    const inputRef = React.useRef<RichTextEditorRef>(null);\n    const isTyping = React.useRef<NodeJS.Timeout | null>(null);\n    const allFormatters = React.useRef<Map<string, CometChatTextFormatter | CometChatMentionsFormatter>>(new Map());\n    const bottomSheetRef = React.useRef<any>(null);\n    const mentionMap = React.useRef<Map<string, SuggestionItem>>(new Map());\n    const trackingCharacters = React.useRef<string[]>([]);\n    const activeCharacter = React.useRef<string>('');\n    const searchStringRef = React.useRef<string>('');\n    // Initialize plainTextInputRef with initialComposertext if provided\n    const plainTextInputRef = React.useRef<string>(initialComposertext ?? '');\n\n    /**\n     * Syncs mention ranges from mentionMap to the native editor for visual styling.\n     * Converts mentionMap keys (\"start_end\") into [{start, end}] and dispatches\n     * the setMentionRanges command so the native side can apply bold purple + background.\n     */\n    const syncMentionRanges = React.useCallback(() => {\n      const ranges: Array<{ start: number; end: number }> = [];\n      mentionMap.current.forEach((_value, key) => {\n        const parts = key.split('_');\n        const start = parseInt(parts[0], 10);\n        const end = parseInt(parts[1], 10);\n        if (!isNaN(start) && !isNaN(end)) {\n          ranges.push({ start, end });\n        }\n      });\n      inputRef.current?.setMentionRanges?.(ranges);\n    }, []);\n\n    /**\n     * Ref for tracking send button delay timer for agentic users.\n     * Used to clear the timer on unmount to prevent memory leaks.\n     */\n    const sendButtonDelayTimer = React.useRef<NodeJS.Timeout | null>(null);\n\n    /**\n     * Ref for tracking parent message ID for agentic users.\n     * After first message sent to agentic user without parentMessageId prop,\n     * stores the message ID for subsequent messages.\n     */\n    const parentMessageIdRef = React.useRef<number | null>(null);\n\n    // Handle platform-specific keyboard offset (iOS safe area insets)\n    useLayoutEffect(() => {\n      if (Platform.OS === 'ios') {\n        if (Number.isInteger(commonVars.safeAreaInsets.top)) {\n          setKbOffset(commonVars.safeAreaInsets.top ?? 59);\n          return;\n        }\n        CommonUtil?.getSafeAreaInsets?.().then((res: { top: number; bottom: number }) => {\n          if (Number.isInteger(res.top)) {\n            commonVars.safeAreaInsets.top = res.top;\n            commonVars.safeAreaInsets.bottom = res.bottom;\n            setKbOffset(res.top);\n          }\n        }).catch(() => {\n          // Fallback to default offset if native module fails\n        });\n      }\n    }, []);\n\n    // Get logged in user\n    useEffect(() => {\n      CometChat.getLoggedinUser()\n        .then((u: CometChat.User | null) => {\n          if (u) {\n            loggedInUser.current = u;\n          }\n        })\n        .catch((error: CometChat.CometChatException) => {\n          onError?.(error);\n        });\n    }, []);\n\n    /**\n     * Subscribe to streaming state observable for agentic users.\n     * Updates isStreaming state when streaming status changes.\n     * Hides stop button when streaming ends.\n     */\n    useEffect(() => {\n      const sub = streamingState$.subscribe((streaming) => {\n        setIsStreaming(streaming);\n        if (!streaming) setShowStopButton(false);\n      });\n      return () => sub.unsubscribe();\n    }, []);\n\n    /**\n     * Cleanup effect for send button delay timer.\n     * Clears the timer on unmount to prevent memory leaks.\n     */\n    useEffect(() => {\n      return () => {\n        if (sendButtonDelayTimer.current) {\n          clearTimeout(sendButtonDelayTimer.current);\n          sendButtonDelayTimer.current = null;\n        }\n      };\n    }, []);\n\n    /**\n     * Keep replyMessageRef in sync with replyMessage state.\n     * This ensures callbacks always have access to the current reply message value.\n     */\n    useEffect(() => {\n      replyMessageRef.current = replyMessage;\n    }, [replyMessage]);\n\n    // Configure receiver from user prop\n    useEffect(() => {\n      if (user) {\n        chatWith.current = ReceiverTypeConstants.user;\n        chatWithId.current = user.getUid();\n      }\n    }, [user]);\n\n    // Configure receiver from group prop\n    useEffect(() => {\n      if (group) {\n        chatWith.current = ReceiverTypeConstants.group;\n        chatWithId.current = group.getGuid();\n      }\n    }, [group]);\n\n    // Initialize text formatters\n    useEffect(() => {\n      let _formatter = [...(textFormatters || [])];\n\n      // Add default mentions formatter if not disabled\n      if (!disableMentions) {\n        const mentionsFormatter = ChatConfigurator.getDataSource().getMentionsFormatter();\n        if (CometChatUIKit.loggedInUser) {\n          mentionsFormatter.setLoggedInUser(CometChatUIKit.loggedInUser);\n        }\n        mentionsFormatter.setContext?.(\"composer\");\n        if (resolvedStyle?.mentionsStyle) {\n          mentionsFormatter.setMentionsStyle(\n            resolvedStyle.mentionsStyle as CometChatTheme[\"mentionsStyle\"]\n          );\n        }\n        // Set target element for mentions (matching MessageComposer pattern)\n        mentionsFormatter.setTargetElement?.(MentionsTargetElement.textinput);\n        \n        // Configure mention all settings\n        if (mentionAllLabel) {\n          mentionsFormatter.setMentionAllLabel(mentionAllLabel);\n        }\n        mentionsFormatter.setDisableMentionAll(disableMentionAll);\n        \n        if (user) {\n          mentionsFormatter.setUser(user);\n        }\n        if (group) {\n          mentionsFormatter.setGroup(group);\n        }\n        _formatter.unshift(mentionsFormatter);\n      }\n\n      _formatter.forEach((formatter) => {\n        if (id !== undefined) {\n          formatter.setComposerId(id);\n        }\n        if (user) {\n          formatter.setUser(user);\n        }\n        if (group) {\n          formatter.setGroup(group);\n        }\n        \n        // Get tracking character for this formatter\n        const trackingChar = formatter.getTrackingCharacter?.();\n        \n        // Clone and store formatter using tracking character as key (matching MessageComposer pattern)\n        const newFormatter = CommonUtils.clone(formatter);\n        if (trackingChar) {\n          allFormatters.current.set(trackingChar, newFormatter);\n          \n          // Set up tracking characters for mention detection\n          if (!trackingCharacters.current.includes(trackingChar)) {\n            trackingCharacters.current.push(trackingChar);\n          }\n        }\n      });\n    }, []);\n\n    // Initialize input with text prop\n    useEffect(() => {\n      if (text !== undefined) {\n        setInputTextProgrammatic(text);\n      }\n    }, [text]);\n\n    // Create composerId map for attachment options\n    const composerIdMap = new Map().set('parentMessageId', parentMessageId);\n\n    /**\n     * Handle custom view click for attachment options\n     */\n    const handleOnClick = (CustomViewFn: any) => {\n      const view = CustomViewFn(\n        user,\n        group,\n        {\n          uid: user?.getUid(),\n          guid: group?.getGuid(),\n          parentMessageId: parentMessageId,\n        },\n        {\n          onClose: () => setIsVisible(false),\n        }\n      );\n      bottomSheetRef.current?.togglePanel();\n      setTimeout(() => {\n        setCustomView(() => view);\n        setIsVisible(true);\n      }, 200);\n    };\n\n    // Initialize attachment options\n    useEffect(() => {\n      const defaultAttachmentOptions =\n        ChatConfigurator.dataSource.getAttachmentOptions(\n          theme,\n          user,\n          group,\n          composerIdMap,\n          {\n            // Individual hide options\n            hideCameraOption,\n            hideImageAttachmentOption,\n            hideVideoAttachmentOption,\n            hideAudioAttachmentOption,\n            hideFileAttachmentOption,\n            hidePollsAttachmentOption,\n            hideCollaborativeDocumentOption,\n            hideCollaborativeWhiteboardOption,\n            // Reply context\n            replyToMessage: replyMessage?.message,\n            closeReplyPreview,\n          }\n        );\n      \n      setActionSheetItems(() =>\n        attachmentOptions && typeof attachmentOptions === 'function'\n          ? attachmentOptions({ user, group, composerId: composerIdMap })?.map((item) => {\n              if (typeof item.CustomView === 'function')\n                return {\n                  ...item,\n                  onPress: () => handleOnClick(item.CustomView),\n                };\n              if (typeof item.onPress === 'function')\n                return {\n                  ...item,\n                  onPress: () => {\n                    setShowActionSheet(false);\n                    item.onPress?.(user, group);\n                  },\n                };\n              return {\n                ...item,\n                onPress: () => fileInputHandler(item.id ?? ''),\n              };\n            })\n          : [\n              // Default attachment options\n              ...defaultAttachmentOptions.map((item) => {\n                if (typeof item.CustomView === 'function')\n                  return {\n                    ...item,\n                    onPress: () => handleOnClick(item.CustomView),\n                  };\n                if (typeof item.onPress === 'function')\n                  return {\n                    ...item,\n                    onPress: () => {\n                      setShowActionSheet(false);\n                      item.onPress(user, group);\n                    },\n                  };\n                return {\n                  ...item,\n                  onPress: () => fileInputHandler(item.id),\n                };\n              }),\n              // Additional attachment options\n              ...(addAttachmentOptions && typeof addAttachmentOptions === 'function'\n                ? addAttachmentOptions({ user, group, composerId: composerIdMap })?.map((item) => {\n                    if (typeof item.CustomView === 'function')\n                      return {\n                        ...item,\n                        onPress: () => handleOnClick(item.CustomView),\n                      };\n                    if (typeof item.onPress === 'function')\n                      return {\n                        ...item,\n                        onPress: () => {\n                          setShowActionSheet(false);\n                          item.onPress?.(user, group);\n                        },\n                      };\n                    return {\n                      ...item,\n                      onPress: () => fileInputHandler(item.id ?? ''),\n                    };\n                  })\n                : []),\n            ]\n      );\n    }, [\n      user,\n      group,\n      id,\n      parentMessageId,\n      hideCameraOption,\n      hideImageAttachmentOption,\n      hideVideoAttachmentOption,\n      hideAudioAttachmentOption,\n      hideFileAttachmentOption,\n      hidePollsAttachmentOption,\n      hideCollaborativeDocumentOption,\n      hideCollaborativeWhiteboardOption,\n      addAttachmentOptions,\n      replyMessage,\n      closeReplyPreview,\n    ]);\n\n    /**\n     * Handle panel show/hide events\n     * Updates CustomViewHeader and CustomViewFooter based on alignment\n     *\n     */\n    const handlePanel = useCallback((item: { child?: React.FC | React.ReactNode; alignment?: string }) => {\n      if (item.child) {\n        if (item.alignment === ViewAlignment.composerTop) {\n          // Handle both FC and ReactNode types\n          const childContent = typeof item.child === 'function' \n            ? React.createElement(item.child as React.FC) \n            : item.child;\n          setCustomViewHeader(childContent);\n        } else if (item.alignment === ViewAlignment.composerBottom) {\n          const childContent = typeof item.child === 'function' \n            ? React.createElement(item.child as React.FC) \n            : item.child;\n          setCustomViewFooter(childContent);\n        }\n      } else {\n        if (item.alignment === ViewAlignment.composerTop) {\n          setCustomViewHeader(null);\n        } else if (item.alignment === ViewAlignment.composerBottom) {\n          setCustomViewFooter(null);\n        }\n      }\n    }, []);\n\n    // Add edit message event listener\n    useEffect(() => {\n      CometChatUIEventHandler.addMessageListener(editMessageListenerID, {\n        ccMessageEdited: (item: { message: any; status: string }) => previewMessage(item),\n      });\n\n      return () => {\n        CometChatUIEventHandler.removeMessageListener(editMessageListenerID);\n      };\n    }, []);\n\n    /**\n     * Add reply message event listener.\n     * Subscribes to ccReplyToMessage events to display reply preview.\n     */\n    useEffect(() => {\n      CometChatMessageEvents.addListener(\n        CometChatMessageEvents.ccReplyToMessage,\n        replyMessageListenerID,\n        (data: any) => {\n          if (data.status === messageStatus.inprogress) {\n            previewReplyMessage(data.message);\n          }\n        }\n      );\n\n      return () => {\n        CometChatMessageEvents.removeListener(\n          CometChatMessageEvents.ccReplyToMessage,\n          replyMessageListenerID\n        );\n      };\n    }, []);\n\n    // Add UI event listener for suggestion data, bottom sheet toggle, and compose message\n    useEffect(() => {\n      CometChatUIEventHandler.addUIListener(uiEventListenerID, {\n        // Handle suggestion data for mentions\n        ccSuggestionData(item: { id: string | number; data: Array<SuggestionItem> }) {\n          if (activeCharacter.current && id === item?.id) {\n            // Check if mention limit warning should be shown (matching MessageComposer pattern)\n            const warningView = getMentionLimitView();\n            if (warningView) {\n              return;\n            }\n            setMentionsSearchData(item?.data);\n            setSuggestionListLoader(false);\n          }\n        },\n        // Handle bottom sheet toggle events (for custom views)\n        ccToggleBottomSheet: (item: { botView?: boolean; child?: React.FC | React.ReactNode }) => {\n          if (item?.botView) {\n            // Handle botView for custom views - convert FC to ReactNode if needed\n            const childContent = typeof item.child === 'function' \n              ? React.createElement(item.child as React.FC) \n              : (item.child || null);\n            setCustomView(childContent);\n            return;\n          }\n          // Toggle bottom sheet visibility\n          setIsVisible(false);\n          bottomSheetRef.current?.togglePanel();\n        },\n        // Handle compose message events (for AI smart replies, etc.)\n        ccComposeMessage: (textData: { text?: string }) => {\n          setIsVisible(false);\n          bottomSheetRef.current?.togglePanel();\n          \n          // Update input with received text\n          if (textData?.text !== undefined) {\n            plainTextInputRef.current = textData.text;\n            setInputTextProgrammatic(textData.text);\n            onChangeText?.(textData.text);\n          }\n        },\n      });\n\n      return () => {\n        CometChatUIEventHandler.removeUIListener(uiEventListenerID);\n      };\n    }, [id]);\n\n    // Handle panel hide events — always clear the content to avoid\n    // a stale separator line when hidePanel passes child: () => null\n    // (a truthy function that renders nothing).\n    const handleHidePanel = useCallback((item: { child?: React.FC | React.ReactNode; alignment?: string }) => {\n      if (item.alignment === ViewAlignment.composerTop) {\n        setCustomViewHeader(null);\n      } else if (item.alignment === ViewAlignment.composerBottom) {\n        setCustomViewFooter(null);\n      }\n    }, []);\n\n    // Add panel show/hide listeners\n    useEffect(() => {\n      CometChatUIEventHandler.addUIListener(uiEventListenerShowID, {\n        showPanel: (item: { child?: React.FC; alignment?: string }) => handlePanel(item),\n      });\n      CometChatUIEventHandler.addUIListener(uiEventListenerHideID, {\n        hidePanel: (item: { child?: React.FC; alignment?: string }) => handleHidePanel(item),\n      });\n\n      return () => {\n        CometChatUIEventHandler.removeUIListener(uiEventListenerShowID);\n        CometChatUIEventHandler.removeUIListener(uiEventListenerHideID);\n      };\n    }, []);\n\n    /**\n     * Camera callback handler for processing captured images\n     */\n    const cameraCallback = async (cameraImage: any) => {\n      if (CheckPropertyExists(cameraImage, 'error')) {\n        return;\n      }\n      const { name, uri, type } = cameraImage;\n      const file = {\n        name,\n        type,\n        uri,\n      };\n      sendMediaMessage(\n        chatWithId.current,\n        file,\n        MessageTypeConstants.image,\n        chatWith.current\n      );\n    };\n\n    /**\n     * File input handler for different file types\n     * Handles image, video, audio, file, and takePhoto options\n     */\n    const fileInputHandler = async (fileType: string) => {\n      if (fileType === MessageTypeConstants.takePhoto) {\n        if (!(await permissionUtil.startResourceBasedTask(['camera']))) {\n          return;\n        }\n        let quality = imageQuality;\n        if (isNaN(imageQuality) || imageQuality < 1 || imageQuality > 100) {\n          quality = 20;\n        }\n        if (Platform.OS === 'android') {\n          FileManager.openCamera(\n            fileType,\n            Math.round(quality),\n            cameraCallback\n          );\n        } else {\n          FileManager.openCamera(\n            fileType,\n            cameraCallback\n          );\n        }\n      } else if (Platform.OS === 'ios' && fileType === MessageTypeConstants.video) {\n        NativeModules.VideoPickerModule.pickVideo((file: any) => {\n          if (file.uri) {\n            sendMediaMessage(\n              chatWithId.current,\n              file,\n              MessageTypeConstants.video,\n              chatWith.current\n            );\n          }\n        });\n      } else {\n        FileManager.openFileChooser(fileType, async (fileInfo: any) => {\n          if (CheckPropertyExists(fileInfo, 'error')) {\n            return;\n          }\n          const { name, uri, type } = fileInfo;\n          const file = {\n            name,\n            type,\n            uri,\n          };\n          sendMediaMessage(\n            chatWithId.current,\n            file,\n            fileType,\n            chatWith.current\n          );\n        });\n      }\n    };\n\n    /**\n     * Send media message (image, video, audio, file)\n     * Includes reply message support for quoted messages.\n     */\n    const sendMediaMessage = (\n      receiverId: string,\n      messageInput: { name: string; type: string; uri: string },\n      messageType: string,\n      receiverType: string\n    ) => {\n      setShowActionSheet(false);\n\n      // Capture current reply message using ref to avoid stale closures\n      const currentReplyMessage = replyMessageRef.current;\n      const replyMessageId = currentReplyMessage?.message?.getId?.() ?? null;\n      \n      const mediaMessage = new CometChat.MediaMessage(\n        receiverId,\n        messageInput,\n        messageType,\n        receiverType\n      );\n\n      mediaMessage.setSender(loggedInUser.current!);\n      mediaMessage.setReceiver((user || group)!);\n      mediaMessage.setType(messageType);\n      mediaMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n      mediaMessage.setData({\n        type: messageType,\n        category: CometChat.CATEGORY_MESSAGE,\n        name: messageInput.name,\n        file: messageInput,\n        url: messageInput.uri,\n        sender: loggedInUser.current,\n      });\n      \n      if (parentMessageId) {\n        mediaMessage.setParentMessageId(parentMessageId as number);\n      }\n\n      /**\n       * Handle reply message - set quoted message for media replies.\n       * Uses shared helper for consistency with CometChatMessageComposer.\n       */\n      if (replyMessageId && currentReplyMessage?.message) {\n        setQuotedMessageSafe(mediaMessage, currentReplyMessage.message, replyMessageId);\n      }\n\n      // Create local message for immediate UI feedback\n      const localMessage = new CometChat.MediaMessage(\n        receiverId,\n        messageInput,\n        messageType,\n        receiverType\n      );\n\n      localMessage.setSender(loggedInUser.current!);\n      localMessage.setReceiver((user || group)!);\n      localMessage.setType(messageType);\n      localMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n      localMessage.setData({\n        type: messageType,\n        category: CometChat.CATEGORY_MESSAGE,\n        name: messageInput.name,\n        file: messageInput,\n        url: messageInput.uri,\n        sender: loggedInUser.current,\n        attachments: [messageInput],\n      });\n      \n      if (parentMessageId) {\n        localMessage.setParentMessageId(parentMessageId as number);\n      }\n\n      // Set quoted message on local message for UI feedback\n      if (replyMessageId && currentReplyMessage?.message) {\n        setQuotedMessageSafe(localMessage, currentReplyMessage.message, replyMessageId);\n        (localMessage as any).quotedMessage = currentReplyMessage.message;\n      }\n\n      // Clear reply preview after preparing message\n      setReplyMessage(null);\n\n      // Emit in-progress event\n      CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n        message: localMessage,\n        status: messageStatus.inprogress,\n      });\n\n      // Play sound if enabled\n      if (!disableSoundForMessages) {\n        playAudio();\n      }\n\n      // Send message via SDK\n      CometChat.sendMediaMessage(mediaMessage)\n        .then((message: CometChat.BaseMessage) => {\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: message,\n            status: messageStatus.success,\n          });\n        })\n        .catch((error: CometChat.CometChatException) => {\n          onError?.(error);\n          (localMessage as any).data.metaData = { error: true };\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: localMessage,\n            status: messageStatus.error,\n          });\n        });\n    };\n\n    /**\n     * Play outgoing message sound\n     */\n    const playAudio = () => {\n      if (customSoundForMessage) {\n        CometChatSoundManager.play(\n          CometChatSoundManager.SoundOutput.outgoingMessage,\n          customSoundForMessage\n        );\n      } else {\n        CometChatSoundManager.play(\n          CometChatSoundManager.SoundOutput.outgoingMessage\n        );\n      }\n    };\n\n    /**\n     * Send recorded audio as a media message\n     * Called when voice recording is completed and submitted\n     */\n    const _sendRecordedAudio = (recordedFile: string) => {\n      // Invoke the onVoiceRecordingEnd callback if provided\n      onVoiceRecordingEnd?.(recordedFile);\n      \n      const fileObj = {\n        name: 'audio-recording' + recordedFile.split('/audio-recording')[1],\n        type: 'audio/mp4',\n        uri: recordedFile,\n      };\n      \n      sendMediaMessage(\n        chatWithId.current,\n        fileObj,\n        MessageTypeConstants.audio,\n        chatWith.current\n      );\n    };\n\n    /**\n     * Handle inline audio recorder submit.\n     * Sends the recorded audio file and hides the inline recorder.\n     */\n    const handleInlineRecorderSubmit = useCallback((recordedFile: string) => {\n      const fileObj = {\n        name: 'audio-recording' + recordedFile.split('/audio-recording')[1],\n        type: 'audio/mp4',\n        uri: recordedFile,\n      };\n      sendMediaMessage(chatWithId.current, fileObj, MessageTypeConstants.audio, chatWith.current);\n      setShowInlineRecorder(false);\n    }, []);\n\n    /**\n     * Handle inline audio recorder cancel.\n     * Hides the inline recorder without sending.\n     */\n    const handleInlineRecorderCancel = useCallback(() => {\n      setShowInlineRecorder(false);\n    }, []);\n\n    /**\n     * Handle voice recording start\n     * Called when recording begins\n     */\n    const handleVoiceRecordingStart = () => {\n      onVoiceRecordingStart?.();\n    };\n\n    /**\n     * Clear the input field.\n     * When autoExpand is enabled, also resets the height to minHeight.\n     * This ensures the input returns to its collapsed state when cleared.\n     *\n     */\n    const clearInputBox = useCallback(() => {\n      setInputTextProgrammatic('');\n      plainTextInputRef.current = '';\n      blocksRef.current = [];\n      pendingLinksRef.current.clear();\n      setEditContentBlocks(undefined);\n      // Clear the RichTextEditor content\n      inputRef.current?.clear();\n      \n      // When autoExpand is enabled, reset height to minHeight\n      // This handles the edge case of clearing all text\n      if (autoExpand) {\n        setInputHeight(resolvedMinHeight);\n        setIsExpanded(false);\n      }\n    }, [autoExpand, resolvedMinHeight]);\n\n    /**\n     * Start typing indicator\n     */\n    const startTyping = useCallback(() => {\n      if (disableTypingEvents) {\n        return;\n      }\n\n      // Check if user is blocked\n      if (user && (user.getBlockedByMe() || user.getHasBlockedMe())) {\n        return;\n      }\n\n      if (isTyping.current) {\n        clearTimeout(isTyping.current);\n        isTyping.current = null;\n      } else {\n        const typingNotification = new CometChat.TypingIndicator(\n          chatWithId.current,\n          chatWith.current\n        );\n        CometChat.startTyping(typingNotification);\n      }\n\n      isTyping.current = setTimeout(() => {\n        // End typing inline to avoid circular dependency\n        if (disableTypingEvents) {\n          return;\n        }\n        if (isTyping.current) {\n          clearTimeout(isTyping.current);\n          isTyping.current = null;\n        }\n        const typingNotification = new CometChat.TypingIndicator(\n          chatWithId.current,\n          chatWith.current\n        );\n        CometChat.endTyping(typingNotification);\n      }, 500);\n    }, [disableTypingEvents, user]);\n\n    /**\n     * End typing indicator\n     */\n    const endTyping = useCallback(() => {\n      if (disableTypingEvents) {\n        return;\n      }\n\n      if (isTyping.current) {\n        clearTimeout(isTyping.current);\n        isTyping.current = null;\n      }\n\n      const typingNotification = new CometChat.TypingIndicator(\n        chatWithId.current,\n        chatWith.current\n      );\n      CometChat.endTyping(typingNotification);\n    }, [disableTypingEvents]);\n\n    /**\n     * Check if cursor is within a mention range\n     */\n    /**\n     * Escape special regex characters\n     */\n    const escapeRegExp = (string: string): string => {\n      return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n    };\n\n    /**\n     * Extract text from cursor position to find search string\n     */\n    const extractTextFromCursor = (inputText: string, cursorPosition: number): string => {\n      const leftText = inputText.substring(0, cursorPosition);\n      const escapedPrefixes = trackingCharacters.current.map(escapeRegExp).join('|');\n      const pattern = new RegExp(`(${escapedPrefixes})([\\\\w\\\\s]*)$`);\n      const match = leftText.match(pattern);\n      \n      if (match) {\n        return match[2] || '';\n      }\n      return '';\n    };\n\n    /**\n     * Check if mention list should be opened.\n     * Suppressed when cursor is inside a code block or inline code span\n     * (Req 12.1, 12.2, 12.3).\n     */\n    const shouldOpenList = (\n      selection: { start: number; end: number },\n      searchString: string,\n      tracker: string\n    ): boolean => {\n      if (disableMentions) return false;\n      // Suppress mention picker inside code or code block contexts.\n      // Read from ref to avoid stale closure after code block exit.\n      const styles = activeStylesRef.current;\n      if (styles.code || styles.codeBlock) return false;\n      \n      return (\n        selection.start === selection.end &&\n        !isCursorWithinMentionRange(mentionMap.current, selection.start - searchString.length) &&\n        trackingCharacters.current.includes(tracker) &&\n        (searchString === ''\n          ? (plainTextInputRef.current[selection.start - 2]?.length === 1 &&\n              plainTextInputRef.current[selection.start - 2]?.trim()?.length === 0) ||\n            plainTextInputRef.current[selection.start - 2] === undefined\n          : true) &&\n        (plainTextInputRef.current[selection.start - 1]?.length === 1 &&\n        plainTextInputRef.current[selection.start - 1]?.trim()?.length === 0\n          ? searchString.length > 0\n          : true)\n      );\n    };\n\n    /**\n     * Open mention list based on cursor position\n     */\n    let openListTimeoutId: NodeJS.Timeout | null = null;\n    const openList = (selection: { start: number; end: number }) => {\n      if (disableMentions) return;\n      \n      if (openListTimeoutId) {\n        clearTimeout(openListTimeoutId);\n      }\n      \n      openListTimeoutId = setTimeout(() => {\n        const searchString = extractTextFromCursor(plainTextInputRef.current, selection.start);\n        const tracker = searchString\n          ? plainTextInputRef.current[selection.start - (searchString.length + 1)]\n          : plainTextInputRef.current[selection.start - 1];\n\n        if (shouldOpenList(selection, searchString, tracker)) {\n          activeCharacter.current = tracker;\n          searchStringRef.current = searchString;\n          setShowMentionList(true);\n          setSuggestionListLoader(true);\n\n          const formatter = allFormatters.current.get(tracker);\n          if (formatter instanceof CometChatMentionsFormatter) {\n            const shouldShowMentionList =\n              formatter.getVisibleIn() === MentionsVisibility.both ||\n              (formatter.getVisibleIn() === MentionsVisibility.usersConversationOnly && user) ||\n              (formatter.getVisibleIn() === MentionsVisibility.groupsConversationOnly && group);\n            if (shouldShowMentionList) {\n              formatter.search(searchString);\n            }\n          } else if (formatter) {\n            formatter.search(searchString);\n          }\n        } else {\n          activeCharacter.current = '';\n          searchStringRef.current = '';\n          setShowMentionList(false);\n          setMentionsSearchData([]);\n        }\n      }, 100);\n    };\n\n    /**\n     * Remove overlapping mentions from text and map.\n     * Tracks total shift for position adjustment and updates formatter suggestion items.\n     * Clears warning message if below mention limit.\n     * \n     * @param text - The original text\n     * @param overlaps - Array of overlapping mentions to remove\n     * @param map - The mention map to update\n     * @returns Object with newText and totalShift\n     */\n    const removeMentionsFromTextAndMap = (\n      text: string,\n      overlaps: MentionOverlap[],\n      map: Map<string, SuggestionItem>\n    ): { newText: string; totalShift: number } => {\n      let adjustment = 0;\n      let newText = text;\n\n      overlaps.forEach(({ key, value, start, end }) => {\n        const adjStart = start + adjustment;\n        const adjEnd = end + adjustment;\n\n        newText = newText.slice(0, adjStart) + newText.slice(adjEnd);\n        map.delete(key);\n        adjustment -= adjEnd - adjStart;\n\n        // Keep formatter in sync\n        if (value.id && !ifIdExists(value.id, map)) {\n          const trackingChar = value.trackingCharacter;\n          if (trackingChar) {\n            const fmt = allFormatters.current.get(trackingChar);\n            if (fmt instanceof CometChatMentionsFormatter) {\n              const users = fmt.getSuggestionItems().filter((u: SuggestionItem) => u.id !== value.id);\n              fmt.setSuggestionItems(users);\n              if (!getMentionLimitView(fmt)) setWarningMessage('');\n            }\n          }\n        }\n      });\n\n      return { newText, totalShift: adjustment };\n    };\n\n    /**\n     * Main helper to handle mention deletion.\n     * Orchestrates mention deletion using helper functions.\n     * Returns true if mentions were deleted, false otherwise.\n     * Updates cursor position after deletion.\n     * \n     * @param oldText - The text before deletion\n     * @param newText - The text after deletion (from TextInput)\n     * @returns True if mentions were deleted and handled, false otherwise\n     */\n    const deleteMentionHelper = (oldText: string, newText: string): boolean => {\n      const deletionLen = oldText.length - newText.length;\n      const range = calcDeletionRange(selectionPosition, deletionLen);\n\n      // Verify the deletion actually happened at the calculated range.\n      // Block-type toggles (list/blockquote) remove prefixes at line starts,\n      // not at the cursor position. If the calculated range doesn't match\n      // the actual deletion, skip mention deletion to avoid false positives.\n      const expectedAfterDeletion = oldText.substring(0, range.start) + oldText.substring(range.end);\n      if (expectedAfterDeletion !== newText) return false;\n\n      const deletionMentionMap = new Map(mentionMap.current);\n      const overlaps = collectOverlappingMentions(range, deletionMentionMap);\n\n      if (overlaps.length === 0) return false;\n\n      const { newText: finalText, totalShift } = removeMentionsFromTextAndMap(\n        oldText,\n        overlaps,\n        deletionMentionMap\n      );\n\n      shiftRemainingMentionKeys(deletionMentionMap, range.start, totalShift);\n\n      plainTextInputRef.current = finalText;\n      setInputTextProgrammatic(finalText);\n      onChangeText?.(finalText);\n      mentionMap.current = deletionMentionMap;\n\n      // Update cursor position to the start of the first deleted mention\n      const firstStart = overlaps[0].start;\n      ignoreSelectionUntil.current = Date.now() + 500;\n      InteractionManager.runAfterInteractions(() => {\n        inputRef.current?.setSelection?.(firstStart);\n        setSelectionPosition({ start: firstStart, end: firstStart });\n        syncMentionRanges();\n      });\n\n      return true;\n    };\n\n    /**\n     * Handle text input change\n     * Tracks cursor position and updates mention map positions.\n     * Checks for deletion and calls deleteMentionHelper before normal processing.\n     */\n    const handleTextChange = (newText: string) => {\n      const oldText = plainTextInputRef.current;\n\n      // Check if this is a deletion - call deleteMentionHelper before normal processing\n      if (oldText.length > newText.length) {\n        const handled = deleteMentionHelper(oldText, newText);\n        if (handled) {\n          // Mention deletion was handled, skip normal processing\n          startTyping();\n          return;\n        }\n      }\n\n      const removing = plainTextInputRef.current.length > newText.length;\n      const adding = plainTextInputRef.current.length < newText.length;\n      const textDiff = newText.length - plainTextInputRef.current.length;\n      const notAtLast = (selectionPosition.start + textDiff) < newText.length;\n\n      // Update plain text input ref\n      plainTextInputRef.current = newText;\n      \n      inputTextRef.current = newText;\n      setInputText(newText);\n      onChangeText?.(newText);\n      startTyping();\n\n      // Update mention map positions based on text changes\n      let decr = 0;\n      const newMentionMap = new Map(mentionMap.current);\n\n      mentionMap.current.forEach((value, key) => {\n        const position = {\n          start: parseInt(key.split('_')[0]),\n          end: parseInt(key.split('_')[1]),\n        };\n\n        // Runs when cursor before the mention and before the last position\n        if (\n          notAtLast &&\n          (selectionPosition.start - 1 <= position.start ||\n            selectionPosition.start - textDiff <= position.start)\n        ) {\n          if (removing) {\n            decr = selectionPosition.end - selectionPosition.start - textDiff;\n            position.start = position.start - decr;\n            position.end = position.end - decr;\n          } else if (adding) {\n            decr = selectionPosition.end - selectionPosition.start + textDiff;\n            position.start = position.start + decr;\n            position.end = position.end + decr;\n          }\n          if (removing || adding) {\n            const newKey = `${position.start}_${position.end}`;\n            if (position.start >= 0) {\n              newMentionMap.set(newKey, value);\n            }\n            newMentionMap.delete(key);\n          }\n        }\n\n        // Code to delete mention from hashmap if it's been modified\n        const expectedMentionPos = plainTextInputRef.current.substring(position.start, position.end);\n\n        if (expectedMentionPos !== value.promptText) {\n          const newKey = `${position.start}_${position.end}`;\n          newMentionMap.delete(newKey);\n\n          // Try to recover the mention by searching for its promptText in the new text.\n          // This handles block-type toggles (list/blockquote) where prefixes\n          // shift text positions but the mention text is still present.\n          const promptText = value.promptText || '';\n          let recovered = false;\n          if (promptText) {\n            // Search near the expected position first, then fall back to full text\n            const searchFrom = Math.max(0, position.start - 20);\n            let localIdx = plainTextInputRef.current.indexOf(promptText, searchFrom);\n            // Fallback: if not found near expected position, search from beginning\n            if (localIdx === -1 && searchFrom > 0) {\n              localIdx = plainTextInputRef.current.indexOf(promptText, 0);\n            }\n            while (localIdx !== -1) {\n              const recoveredKey = `${localIdx}_${localIdx + promptText.length}`;\n              if (!newMentionMap.has(recoveredKey)) {\n                newMentionMap.set(recoveredKey, value);\n                recovered = true;\n                break;\n              }\n              localIdx = plainTextInputRef.current.indexOf(promptText, localIdx + 1);\n            }\n          }\n\n          // Mention text truly gone — remove from formatters\n          if (!recovered && value.id && !ifIdExists(value.id, newMentionMap)) {\n            const trackingChar = value.trackingCharacter;\n            if (trackingChar) {\n              const targetedFormatter = allFormatters.current.get(trackingChar);\n              if (targetedFormatter) {\n                const existingCCUsers = [...targetedFormatter.getSuggestionItems()];\n                const userPosition = existingCCUsers.findIndex(\n                  (item: SuggestionItem) => item.id === value.id\n                );\n                if (userPosition !== -1) {\n                  existingCCUsers.splice(userPosition, 1);\n                  (targetedFormatter as CometChatMentionsFormatter).setSuggestionItems(existingCCUsers);\n                  if (!getMentionLimitView(targetedFormatter as CometChatMentionsFormatter)) {\n                    setWarningMessage('');\n                  }\n                }\n              }\n            }\n          }\n        }\n      });\n\n      mentionMap.current = newMentionMap;\n      // Sync mention visual styling after text change shifts mention positions\n      syncMentionRanges();\n    };\n\n    /**\n     * Handle selection change for mention tracking\n     */\n    const handleSelectionChange = (event: { nativeEvent: { selection: { start: number; end: number } } }) => {\n      const { selection } = event.nativeEvent;\n\n      if (Date.now() < ignoreSelectionUntil.current) {\n        return;\n      }\n\n      const cursorPos = selection.start;\n      const mentionRange = getMentionRangeAtCursor(mentionMap.current, cursorPos);\n\n      if (mentionRange) {\n        const distanceToStart = cursorPos - mentionRange.start;\n        const distanceToEnd = mentionRange.end - cursorPos;\n        let targetPosition: number;\n        if (distanceToStart <= distanceToEnd) {\n          targetPosition = mentionRange.start;\n        } else {\n          targetPosition = mentionRange.end;\n        }\n        if (targetPosition !== cursorPos) {\n          InteractionManager.runAfterInteractions(() => {\n            inputRef.current?.setSelection?.(targetPosition);\n            setSelectionPosition({ start: targetPosition, end: targetPosition });\n          });\n          return;\n        }\n      }\n\n      setSelectionPosition(selection);\n      openList(selection);\n    };\n\n    /**\n     * Insert mention at specific position in the mention map\n     */\n    const insertMentionAt = (\n      map: Map<string, SuggestionItem>,\n      insertAt: number,\n      key: string,\n      value: SuggestionItem\n    ): Map<string, SuggestionItem> => {\n      const mentionsArray = Array.from(map);\n      mentionsArray.splice(insertAt, 0, [key, value]);\n      return new Map(mentionsArray);\n    };\n\n    /**\n     * Check if an ID exists in the mention map\n     */\n    const ifIdExists = (id: string, hashmap: Map<string, SuggestionItem>): boolean => {\n      for (const [, value] of hashmap) {\n        if (value.id === id) {\n          return true;\n        }\n      }\n      return false;\n    };\n\n    /**\n     * Handle mention selection from suggestion list\n     */\n    const onMentionPress = (item: SuggestionItem) => {\n      // Enforce maxMentionLimit — prevent inserting if limit reached\n      if (mentionMap.current.size >= maxMentionLimit) {\n        onMentionLimitReached?.();\n        setShowMentionList(false);\n        setMentionsSearchData([]);\n        return;\n      }\n\n      setShowMentionList(false);\n      setMentionsSearchData([]);\n\n      const promptText = item.promptText || '';\n      const notAtLast = selectionPosition.start < plainTextInputRef.current.length;\n      const textDiff =\n        plainTextInputRef.current.length +\n        promptText.length -\n        searchStringRef.current.length -\n        plainTextInputRef.current.length;\n\n      let incr = 0;\n      let mentionPos = 0;\n\n      const newMentionMap = new Map(mentionMap.current);\n\n      const targetedFormatter = allFormatters.current.get(activeCharacter.current);\n\n      if (targetedFormatter) {\n        const existingCCUsers = [...targetedFormatter.getSuggestionItems()];\n        const userAlreadyExists = existingCCUsers.find(\n          (existingUser: SuggestionItem) => existingUser.id === item.id\n        );\n        if (!userAlreadyExists) {\n          const cometchatUIUserArray: Array<SuggestionItem> = [...existingCCUsers];\n          cometchatUIUserArray.push(item);\n          (targetedFormatter as CometChatMentionsFormatter).setSuggestionItems(cometchatUIUserArray);\n        }\n      }\n\n      mentionMap.current.forEach((value, key) => {\n        const position = {\n          start: parseInt(key.split('_')[0]),\n          end: parseInt(key.split('_')[1]),\n        };\n\n        if (!(selectionPosition.start <= position.start)) {\n          mentionPos += 1;\n        }\n\n        // Delete mention from hashmap if cursor is within it\n        if (\n          position.end === selectionPosition.end ||\n          (selectionPosition.start > position.start && selectionPosition.end <= position.end)\n        ) {\n          const newKey = `${position.start}_${position.end}`;\n          newMentionMap.delete(newKey);\n          mentionPos -= 1;\n        }\n\n        if (notAtLast && selectionPosition.start - 1 <= position.start) {\n          incr = selectionPosition.end - selectionPosition.start + textDiff;\n          const newKey = `${position.start + incr}_${position.end + incr}`;\n          newMentionMap.set(newKey, value);\n          newMentionMap.delete(key);\n        }\n      });\n      mentionMap.current = newMentionMap;\n\n      // Update the input text with the mention\n      const updatedPlainTextInput = `${plainTextInputRef.current.substring(\n        0,\n        selectionPosition.start - (1 + searchStringRef.current.length)\n      )}${promptText + ' '}${plainTextInputRef.current.substring(\n        selectionPosition.end,\n        plainTextInputRef.current.length\n      )}`;\n      plainTextInputRef.current = updatedPlainTextInput;\n\n      const key =\n        selectionPosition.start -\n        (1 + searchStringRef.current.length) +\n        '_' +\n        (selectionPosition.start - (searchStringRef.current.length + 1) + promptText.length);\n\n      const updatedMap = insertMentionAt(mentionMap.current, mentionPos, key, {\n        ...item,\n        trackingCharacter: activeCharacter.current,\n      });\n      mentionMap.current = updatedMap;\n\n      // Calculate cursor position after the mention + space\n      const newCursorPosition =\n        selectionPosition.start -\n        (searchStringRef.current.length + 1) +\n        (promptText.length) +\n        1; // +1 for the space after mention\n\n      // Update the input text state — ignore native selection events for 500ms\n      ignoreSelectionUntil.current = Date.now() + 500;\n      setInputTextProgrammatic(updatedPlainTextInput);\n\n      // Set cursor position and sync mention styling to native editor\n      setTimeout(() => {\n        inputRef.current?.setSelection?.(newCursorPosition);\n        // Update our tracking state without triggering a re-render to native\n        setSelectionPosition({ start: newCursorPosition, end: newCursorPosition });\n        // Apply mention visual styling (bold purple + background pill) in native editor\n        syncMentionRanges();\n      }, 100);\n    };\n\n    /**\n     * Handle end reached for suggestion list pagination\n     */\n    const onSuggestionListEndReached = () => {\n      // Pagination can be implemented here if needed\n    };\n\n    /**\n     * Get mention limit warning view\n     * Displays a warning when the mention limit is reached.\n     * Also sets the warningMessage state for display in CustomViewHeader.\n     * \n     * @param targettedFormatterParam - Optional formatter to check (defaults to active formatter)\n     * @returns boolean - true if warning should be shown, false otherwise\n     */\n    const getMentionLimitView = (targettedFormatterParam?: CometChatMentionsFormatter): boolean => {\n      const targetedFormatter = allFormatters.current.get(activeCharacter.current) ?? targettedFormatterParam;\n      \n      if (!(targetedFormatter instanceof CometChatMentionsFormatter)) {\n        return false;\n      }\n      \n      let shouldWarn = false;\n      let limit: number | undefined;\n      \n      // Check if formatter has limit capabilities\n      if (targetedFormatter.getLimit && targetedFormatter.getLimit()) {\n        limit = targetedFormatter.getLimit();\n        if (targetedFormatter.getUniqueUsersList && targetedFormatter.getUniqueUsersList()?.size >= limit) {\n          shouldWarn = true;\n        }\n      }\n      \n      if (!shouldWarn) {\n        setWarningMessage('');\n        return false;\n      }\n      \n      const errorString = targetedFormatter.getErrorString\n        ? targetedFormatter.getErrorString()\n        : `${t('MENTION_UPTO')} ${limit} ${limit === 1 ? t('TIME') : t('TIMES')} ${t('AT_A_TIME')}.`;\n      \n      setWarningMessage(errorString);\n      return true;\n    };\n\n    /**\n     * Render mention limit warning view in suggestion list\n     * Uses the warningMessage state set by getMentionLimitView\n     */\n    const renderMentionLimitWarning = () => {\n      if (!warningMessage) return null;\n      \n      return (\n        <View\n          style={{\n            flexDirection: 'row',\n            alignItems: 'center',\n            paddingTop: 5,\n            paddingLeft: 5,\n            borderTopWidth: 1,\n            borderTopColor: theme.color.borderDefault,\n          }}\n        >\n          <Icon\n            name=\"info-fill\"\n            color={theme.color.error}\n            height={20}\n            width={20}\n          />\n          <Text\n            style={{\n              marginLeft: 5,\n              color: theme.color.textSecondary,\n              ...theme.typography.caption1.regular,\n            }}\n          >\n            {warningMessage}\n          </Text>\n        </View>\n      );\n    };\n\n    /**\n     * Convert mentions to underlying text format for sending\n     * Iterates through mentionMap and replaces prompt text with underlying text\n     */\n    const getRegexString = (str: string): string => {\n      // Get an array of the entries in the map using the spread operator\n      const entries = [...mentionMap.current.entries()].reverse();\n\n      let uidInput = str;\n\n      // Iterate over the array in reverse order\n      entries.forEach(([key, value]) => {\n        const [start, end] = key.split('_').map(Number);\n\n        const pre = uidInput.substring(0, start);\n        const post = uidInput.substring(end);\n\n        uidInput = pre + value.underlyingText + post;\n      });\n\n      return uidInput;\n    };\n\n    /**\n     * Replace mention display names with underlying tokens in the given text.\n     * Uses name-based indexOf (not position-based splicing) because markdown\n     * syntax added by blocksToMarkdown shifts character positions.\n     * Entries are sorted left-to-right so indexOf finds the correct occurrence\n     * when the same display name appears multiple times.\n     */\n    const replaceMentionsWithTokens = (text: string): string => {\n      if (mentionMap.current.size === 0) return text;\n      const entries = [...mentionMap.current.entries()].sort((a, b) => {\n        return parseInt(a[0].split('_')[0]) - parseInt(b[0].split('_')[0]);\n      });\n      let result = text;\n      let offset = 0;\n      for (const [, value] of entries) {\n        const displayName = value.promptText || '';\n        const underlying = value.underlyingText || '';\n        if (!displayName || !underlying) continue;\n        const idx = result.indexOf(displayName, offset);\n        if (idx === -1) continue;\n        result = result.substring(0, idx) + underlying + result.substring(idx + displayName.length);\n        offset = idx + underlying.length;\n      }\n      return result;\n    };\n\n    /**\n     * Preview message for edit mode\n     * Called when an edit message event is received\n     * Populates the input with the original message text\n     */\n    const previewMessage = ({ message, status }: { message: any; status: string }) => {\n      if (status === messageStatus.inprogress) {\n        // Populate formatter SuggestionItems from the message's mentioned users.\n        // Only handleComposerPreview is needed — getFormattedText returns JSX\n        // which is unused here since we feed plain text to markdownToBlocks.\n        allFormatters.current.forEach((formatter: CometChatTextFormatter | CometChatMentionsFormatter) => {\n          formatter.handleComposerPreview?.(message);\n        });\n\n        // Resolve mention tokens (<@uid:...>, <@all:...>) to display names\n        // and build mentionMap so the native editor can apply mention styling.\n        const rawText = message?.text ?? '';\n        const mentionFormatter = allFormatters.current.get('@');\n        let resolvedText = rawText;\n        const newMentionMap = new Map<string, SuggestionItem>();\n\n        if (mentionFormatter instanceof CometChatMentionsFormatter) {\n          const suggestionItems = mentionFormatter.getSuggestionItems();\n          const itemLookup = new Map<string, SuggestionItem>();\n          for (const item of suggestionItems) {\n            itemLookup.set(item.id, item);\n          }\n\n          // Single-pass: collect matches, build resolved text via array join\n          const mentionRegex = /<@(?:uid|all):(.*?)>/g;\n          let match: RegExpExecArray | null;\n          const segments: string[] = [];\n          let lastIndex = 0;\n          const mentionPositions: Array<{ start: number; length: number; item: SuggestionItem }> = [];\n          let resolvedLength = 0;\n\n          while ((match = mentionRegex.exec(rawText)) !== null) {\n            // Append text before this match\n            const before = rawText.substring(lastIndex, match.index);\n            segments.push(before);\n            resolvedLength += before.length;\n\n            const item = itemLookup.get(match[1]);\n            const displayText = item?.promptText ?? `@${match[1]}`;\n            segments.push(displayText);\n\n            if (item) {\n              mentionPositions.push({ start: resolvedLength, length: displayText.length, item });\n            }\n            resolvedLength += displayText.length;\n            lastIndex = match.index + match[0].length;\n          }\n          // Append trailing text\n          segments.push(rawText.substring(lastIndex));\n          resolvedText = segments.join('');\n\n          // Build mentionMap from collected positions\n          for (const mp of mentionPositions) {\n            const key = `${mp.start}_${mp.start + mp.length}`;\n            newMentionMap.set(key, new SuggestionItem({\n              ...mp.item,\n              trackingCharacter: '@',\n            }));\n          }\n        }\n\n        mentionMap.current = newMentionMap;\n\n        // Set the message preview state with original message object\n        // (preserves SDK prototype chain for CometChatMessagePreview formatting)\n        setMessagePreview({\n          message: message,\n          mode: ConversationOptionConstants.edit,\n        });\n\n        // Parse markdown into structured blocks and load into the native editor\n        const blocks = markdownToBlocks(resolvedText);\n        inputTextRef.current = resolvedText;\n        plainTextInputRef.current = resolvedText;\n        blocksRef.current = blocks;\n\n        // Do NOT call setInputText here — that would push text via the React\n        // text prop, which races with setContent and can strip formatting or\n        // trigger handleTextChange that corrupts mentionMap. Instead, update\n        // only the refs and the inputText state silently after setContent loads.\n\n        // Load structured blocks into the native editor.\n        setTimeout(() => {\n          inputRef.current?.setContent?.(blocks);\n          inputRef.current?.focus();\n          // Update inputText state AFTER setContent so the text prop doesn't\n          // race with attributed text. The native editor already has the content.\n          setInputText(resolvedText);\n          // Apply mention styling after native editor finishes processing setContent\n          setTimeout(syncMentionRanges, 100);\n        }, 100);\n      }\n    };\n\n    /**\n     * Preview message for reply mode.\n     * Called when a ccReplyToMessage event is received.\n     * Clears any active edit preview before showing the reply preview.\n     */\n    const previewReplyMessage = (message: any) => {\n      // Clear edit preview if it exists\n      if (messagePreview) {\n        setMessagePreview(null);\n        mentionMap.current = new Map();\n        plainTextInputRef.current = '';\n        syncMentionRanges();\n      }\n\n      // Set the reply message state\n      setReplyMessage({\n        message: message,\n        mode: ConversationOptionConstants.reply,\n      });\n\n      // Focus input after setting preview\n      try {\n        inputRef.current?.focus();\n      } catch (error) {\n        // Ignore focus errors\n      }\n    };\n\n    /**\n     * Expose ref methods for programmatic control.\n     * Allows parent components to:\n     * - Trigger edit mode programmatically\n     * - Send text messages\n     * - Get current input text\n     * - Clear the input field\n     * - Reset streaming state\n     */\n    useImperativeHandle(ref, () => ({\n      previewMessageForEdit: previewMessage,\n      sendTextMessage,\n      getText: () => plainTextInputRef.current,\n      clear: () => {\n        clearInputBox();\n        inputRef.current?.clear();\n      },\n      // Rich text formatting methods — exposed so developers can build custom toolbars.\n      // These call the same native methods as the built-in inline toolbar.\n      // Use these when enableRichTextEditor={false} and you want to provide your own UI controls.\n\n      /** Toggles bold formatting on the current selection or at the cursor position. */\n      toggleBold: () => inputRef.current?.toggleBold(),\n      /** Toggles italic formatting on the current selection or at the cursor position. */\n      toggleItalic: () => inputRef.current?.toggleItalic(),\n      /** Toggles underline formatting on the current selection or at the cursor position. */\n      toggleUnderline: () => inputRef.current?.toggleUnderline(),\n      /** Toggles strikethrough formatting on the current selection or at the cursor position. */\n      toggleStrikethrough: () => inputRef.current?.toggleStrikethrough(),\n      /** Toggles inline code formatting on the current selection or at the cursor position. */\n      toggleCode: () => inputRef.current?.toggleCode(),\n      /** Toggles text highlight/background color on the current selection. Pass an optional hex color string. */\n      toggleHighlight: (color?: string) => inputRef.current?.toggleHighlight(color),\n      /** Converts the current line/block to a heading (toggles between heading and paragraph). */\n      setHeading: () => inputRef.current?.setHeading(),\n      /** Converts the current line/block to a bullet (unordered) list item. */\n      setBulletList: () => inputRef.current?.setBulletList(),\n      /** Converts the current line/block to a numbered (ordered) list item. */\n      setNumberedList: () => inputRef.current?.setNumberedList(),\n      /** Converts the current line/block to a blockquote. */\n      setQuote: () => inputRef.current?.setQuote(),\n      /** Converts the current line/block to a checklist item with a toggleable checkbox. */\n      setChecklist: () => inputRef.current?.setChecklist(),\n      /** Resets the current line/block back to a normal paragraph. */\n      setParagraph: () => inputRef.current?.setParagraph(),\n      /** Inserts a hyperlink at the current selection. Wraps selected text or inserts new link text. */\n      insertLink: (url: string, text: string) => inputRef.current?.insertLink(url, text),\n      /** Undoes the last editing action. */\n      undo: () => inputRef.current?.undo(),\n      /** Redoes the last undone editing action. */\n      redo: () => inputRef.current?.redo(),\n      /** Removes all inline formatting (bold, italic, underline, etc.) from the current selection. */\n      clearFormatting: () => inputRef.current?.clearFormatting(),\n      /** Increases the indentation level of the current line/block (for lists and quotes). */\n      indent: () => inputRef.current?.indent(),\n      /** Decreases the indentation level of the current line/block. */\n      outdent: () => inputRef.current?.outdent(),\n      /** Sets text alignment for the current line/block: 'left', 'center', or 'right'. */\n      setAlignment: (alignment: 'left' | 'center' | 'right') => inputRef.current?.setAlignment(alignment),\n      /** Programmatically focuses the editor (opens keyboard). */\n      focus: () => inputRef.current?.focus(),\n      /** Programmatically blurs the editor (dismisses keyboard). */\n      blur: () => inputRef.current?.blur(),\n      /**\n       * Reset streaming state and stop any active streaming.\n       * Resets showStopButton and isStreaming to false.\n       * Calls stopStreamingForRunId() with error handling.\n       */\n      resetStreaming: () => {\n        setShowStopButton(false);\n        setIsStreaming(false);\n        try {\n          stopStreamingForRunId();\n        } catch (error) {\n          // Silently handle streaming stop errors\n          // The UI state is already reset\n        }\n      },\n    }));\n\n    /**\n     * Update CustomViewHeader when warningMessage changes.\n     * Displays an info icon and warning text when mention limit is reached.\n     * Clears the header when warning is cleared.\n     */\n    useLayoutEffect(() => {\n      if (warningMessage) {\n        setCustomViewHeader(\n          <View\n            style={{\n              flexDirection: 'row',\n              alignItems: 'center',\n              padding: theme.spacing?.padding?.p2 || 8,\n              borderRadius: resolvedStyle?.containerStyle?.borderRadius as number || 8,\n              backgroundColor: resolvedStyle?.containerStyle?.backgroundColor as string || theme.color.background1,\n              borderColor: resolvedStyle?.containerStyle?.borderColor as string,\n              borderWidth: resolvedStyle?.containerStyle?.borderWidth as number,\n              marginBottom: 2,\n            }}\n          >\n            <Icon name='info-fill' color={theme.color.error} height={16} width={16} />\n            <Text\n              style={{\n                marginLeft: 5,\n                color: theme.color.error,\n                ...theme.typography.caption1.regular,\n              }}\n            >\n              {warningMessage}\n            </Text>\n          </View>\n        );\n        return;\n      }\n      setCustomViewHeader(null);\n    }, [warningMessage, theme, resolvedStyle]);\n\n    /**\n     * Parse a markdown string into the block format expected by the native\n     * editor's setContent(blocks:) / initialContentJson.\n     *\n     * This is the inverse of blocksToMarkdown. It handles:\n     *   - Code blocks (``` ... ```) → { type: \"codeBlock\", text, styles: [] }\n     *   - Blockquotes (> ...)      → { type: \"quote\", text, styles: [] }\n     *   - Bullet lists (- ...)     → { type: \"bullet\", text, styles }\n     *   - Numbered lists (1. ...)  → { type: \"numbered\", text, styles }\n     *   - Inline styles (bold **, italic _, underline <u></u>, strikethrough ~~, code `)\n     *     → { type: \"paragraph\", text, styles: [{style, start, end}] }\n     */\n    const markdownToBlocks = (markdown: string): any[] => {\n      if (!markdown) return [];\n\n      /**\n       * Parse inline markdown styles from a line of text.\n       * Returns the plain text and an array of style spans.\n       * Supports nested styles (e.g. **<u>bold underline</u>**).\n       */\n      const parseInlineStyles = (line: string): { text: string; styles: any[] } => {\n        const styles: any[] = [];\n        let plain = '';\n\n        // Helper: recursively parse inner content and offset style positions\n        const parseNested = (content: string, offset: number): string => {\n          const inner = parseInlineStyles(content);\n          for (const s of inner.styles) {\n            styles.push({ style: s.style, start: s.start + offset, end: s.end + offset });\n          }\n          return inner.text;\n        };\n\n        let i = 0;\n        while (i < line.length) {\n          // Markdown link: [text](url)\n          if (line[i] === '[') {\n            const closeBracket = line.indexOf('](', i + 1);\n            if (closeBracket !== -1) {\n              const closeParen = line.indexOf(')', closeBracket + 2);\n              if (closeParen !== -1) {\n                const start = plain.length;\n                const linkText = line.substring(i + 1, closeBracket);\n                const linkUrl = line.substring(closeBracket + 2, closeParen);\n                plain += linkText;\n                styles.push({ style: 'link', start, end: plain.length, url: linkUrl });\n                i = closeParen + 1;\n                continue;\n              }\n            }\n          }\n\n          // Triple-backtick inline code block: ```...```\n          if (line[i] === '`' && line[i + 1] === '`' && line[i + 2] === '`') {\n            const closeIdx = line.indexOf('```', i + 3);\n            if (closeIdx !== -1) {\n              const start = plain.length;\n              const content = line.substring(i + 3, closeIdx);\n              plain += content;\n              styles.push({ style: 'code', start, end: plain.length });\n              i = closeIdx + 3;\n              continue;\n            }\n          }\n\n          // Inline code: `...` (no nesting inside code)\n          if (line[i] === '`') {\n            const closeIdx = line.indexOf('`', i + 1);\n            if (closeIdx !== -1) {\n              const start = plain.length;\n              const content = line.substring(i + 1, closeIdx);\n              plain += content;\n              styles.push({ style: 'code', start, end: plain.length });\n              i = closeIdx + 1;\n              continue;\n            }\n          }\n\n          // Bold: **...**\n          if (line[i] === '*' && line[i + 1] === '*') {\n            const closeIdx = line.indexOf('**', i + 2);\n            if (closeIdx !== -1) {\n              const start = plain.length;\n              const content = line.substring(i + 2, closeIdx);\n              const innerText = parseNested(content, start);\n              plain += innerText;\n              styles.push({ style: 'bold', start, end: plain.length });\n              i = closeIdx + 2;\n              continue;\n            }\n          }\n\n          // Strikethrough: ~~...~~\n          if (line[i] === '~' && line[i + 1] === '~') {\n            const closeIdx = line.indexOf('~~', i + 2);\n            if (closeIdx !== -1) {\n              const start = plain.length;\n              const content = line.substring(i + 2, closeIdx);\n              const innerText = parseNested(content, start);\n              plain += innerText;\n              styles.push({ style: 'strikethrough', start, end: plain.length });\n              i = closeIdx + 2;\n              continue;\n            }\n          }\n\n          // Underline: <u>text</u> (HTML tag, case-insensitive)\n          if (line[i] === '<' && line.substring(i, i + 3).toLowerCase() === '<u>') {\n            const closeIdx = line.toLowerCase().indexOf('</u>', i + 3);\n            if (closeIdx !== -1) {\n              const start = plain.length;\n              const content = line.substring(i + 3, closeIdx);\n              const innerText = parseNested(content, start);\n              plain += innerText;\n              styles.push({ style: 'underline', start, end: plain.length });\n              i = closeIdx + 4;\n              continue;\n            }\n          }\n\n          // Italic: _..._\n          if (line[i] === '_') {\n            const closeIdx = line.indexOf('_', i + 1);\n            if (closeIdx !== -1) {\n              const start = plain.length;\n              const content = line.substring(i + 1, closeIdx);\n              const innerText = parseNested(content, start);\n              plain += innerText;\n              styles.push({ style: 'italic', start, end: plain.length });\n              i = closeIdx + 1;\n              continue;\n            }\n          }\n\n          // Plain character\n          plain += line[i];\n          i++;\n        }\n\n        return { text: plain, styles };\n      };\n\n      const lines = markdown.split('\\n');\n      const blocks: any[] = [];\n      let i = 0;\n\n      while (i < lines.length) {\n        const line = lines[i];\n\n        // Code block fence: ```\n        if (line.trimEnd() === '```' || line.startsWith('```')) {\n          const afterOpen = line.substring(3);\n          const firstClose = afterOpen.indexOf('```');\n\n          if (firstClose > 0) {\n            // Has closing ``` on the same line\n            const afterFirstBlock = afterOpen.substring(firstClose + 3).trim();\n            if (afterFirstBlock.length === 0) {\n              // Single ```content``` on this line — treat as one code block\n              const content = afterOpen.substring(0, firstClose);\n              blocks.push({ type: 'codeBlock', text: content, styles: [] });\n              i++;\n              continue;\n            } else {\n              // Multiple ```...``` or mixed content — treat as paragraph with inline code styles\n              const { text, styles } = parseInlineStyles(line);\n              blocks.push({ type: 'paragraph', text, styles });\n              i++;\n              continue;\n            }\n          }\n\n          // Standalone ``` or ```langId — multi-line fenced code block\n          i++;\n          const codeLines: string[] = [];\n          while (i < lines.length && lines[i].trimEnd() !== '```') {\n            codeLines.push(lines[i]);\n            i++;\n          }\n          // Each code line becomes its own codeBlock block (matching native editor format)\n          if (codeLines.length === 0) {\n            blocks.push({ type: 'codeBlock', text: '', styles: [] });\n          } else {\n            for (const cl of codeLines) {\n              blocks.push({ type: 'codeBlock', text: cl, styles: [] });\n            }\n          }\n          // Skip closing fence\n          if (i < lines.length) i++;\n          continue;\n        }\n\n        // Blockquote + bullet: > - ...\n        if (line.startsWith('> - ')) {\n          const content = line.substring(4);\n          const { text, styles } = parseInlineStyles(content);\n          blocks.push({ type: 'quoteBullet', text, styles });\n          i++;\n          continue;\n        }\n\n        // Blockquote + numbered: > 1. ...\n        if (/^> \\d+\\.\\s/.test(line)) {\n          const content = line.replace(/^> \\d+\\.\\s/, '');\n          const { text, styles } = parseInlineStyles(content);\n          blocks.push({ type: 'quoteNumbered', text, styles });\n          i++;\n          continue;\n        }\n\n        // Blockquote: > ...\n        if (line.startsWith('> ')) {\n          const content = line.substring(2);\n          const { text, styles } = parseInlineStyles(content);\n          blocks.push({ type: 'quote', text, styles });\n          i++;\n          continue;\n        }\n\n        // Bullet list: - ...\n        if (line.startsWith('- ')) {\n          const content = line.substring(2);\n          const { text, styles } = parseInlineStyles(content);\n          blocks.push({ type: 'bullet', text, styles });\n          i++;\n          continue;\n        }\n\n        // Numbered list: 1. ... (any digit prefix)\n        if (/^\\d+\\.\\s/.test(line)) {\n          const content = line.replace(/^\\d+\\.\\s/, '');\n          const { text, styles } = parseInlineStyles(content);\n          blocks.push({ type: 'numbered', text, styles });\n          i++;\n          continue;\n        }\n\n        // Heading: # ...\n        if (line.startsWith('# ')) {\n          const content = line.substring(2);\n          const { text, styles } = parseInlineStyles(content);\n          blocks.push({ type: 'heading', text, styles });\n          i++;\n          continue;\n        }\n\n        // Default: paragraph with inline styles\n        const { text, styles } = parseInlineStyles(line);\n        blocks.push({ type: 'paragraph', text, styles });\n        i++;\n      }\n\n      return blocks;\n    };\n\n    /**\n     * Convert RichTextEditor blocks to markdown string.\n     * Handles overlapping style ranges (e.g. bold+italic+underline on same text).\n     */\n    const blocksToMarkdown = (blocks: any[]): string => {\n      if (!blocks || blocks.length === 0) return '';\n\n      // Marker map for each style type — open/close pairs\n      // Most markers are symmetric (same open and close), but underline uses HTML tags.\n      const markerMap: Record<string, { open: string; close: string }> = {\n        bold: { open: '**', close: '**' },\n        italic: { open: '_', close: '_' },\n        underline: { open: '<u>', close: '</u>' },\n        strikethrough: { open: '~~', close: '~~' },\n        code: { open: '`', close: '`' },\n      };\n\n      // Order matters: outer markers wrap inner ones\n      const styleOrder = ['bold', 'underline', 'strikethrough', 'italic', 'code'];\n\n      const applyInlineStyles = (text: string, styles: any[]): string => {\n        if (!styles || styles.length === 0 || !text) return text;\n\n        // Build mention ranges by finding mention promptText within this block's text.\n        // This is more robust than position-based mapping because mentionMap positions\n        // are based on plain text (with list prefixes) while block text has prefixes stripped.\n        const mentionRanges: Array<[number, number]> = [];\n        mentionMap.current.forEach((value, _key) => {\n          const pt = value.promptText || '';\n          if (pt) {\n            let searchFrom = 0;\n            // Find all occurrences of this mention's promptText in the block text\n            while (searchFrom < text.length) {\n              const idx = text.indexOf(pt, searchFrom);\n              if (idx === -1) break;\n              mentionRanges.push([idx, idx + pt.length]);\n              searchFrom = idx + pt.length;\n            }\n          }\n        });\n\n        // Split styles around mention ranges so mentions stay clean.\n        const filteredStyles: any[] = [];\n        for (const s of styles) {\n          if (s.style === 'link' || mentionRanges.length === 0) {\n            filteredStyles.push(s);\n            continue;\n          }\n          const sStart = Math.max(0, s.start);\n          const sEnd = Math.min(text.length, s.end);\n          // Collect non-mention sub-ranges of this style\n          let cursor = sStart;\n          // Sort mention ranges for consistent processing\n          const sorted = [...mentionRanges].sort((a, b) => a[0] - b[0]);\n          for (const [mStart, mEnd] of sorted) {\n            if (mStart >= sEnd || mEnd <= sStart) continue; // no overlap\n            // Add the part before the mention\n            if (cursor < mStart) {\n              filteredStyles.push({ ...s, start: cursor, end: mStart });\n            }\n            cursor = Math.max(cursor, mEnd);\n          }\n          // Add the part after the last mention\n          if (cursor < sEnd) {\n            filteredStyles.push({ ...s, start: cursor, end: sEnd });\n          }\n        }\n\n        // Collect all boundary positions where style sets change\n        const boundaries = new Set<number>();\n        boundaries.add(0);\n        boundaries.add(text.length);\n        for (const s of filteredStyles) {\n          boundaries.add(Math.max(0, s.start));\n          boundaries.add(Math.min(text.length, s.end));\n        }\n        const sorted = Array.from(boundaries).sort((a, b) => a - b);\n\n        // Build segments with their active style sets\n        const segActiveStyles: string[][] = [];\n        const segTexts: string[] = [];\n        const segLinkUrls: string[] = [];\n        for (let i = 0; i < sorted.length - 1; i++) {\n          const segStart = sorted[i];\n          const segEnd = sorted[i + 1];\n          const segment = text.substring(segStart, segEnd);\n          if (segment.length === 0) continue;\n\n          const active: string[] = [];\n          let linkUrl = '';\n          for (const s of filteredStyles) {\n            const sStart = Math.max(0, s.start);\n            const sEnd = Math.min(text.length, s.end);\n            if (sStart <= segStart && sEnd >= segEnd) {\n              if (s.style === 'link' && s.url) {\n                linkUrl = s.url;\n              } else if (markerMap[s.style] !== undefined && !active.includes(s.style)) {\n                active.push(s.style);\n              }\n            }\n          }\n          segActiveStyles.push(active);\n          segTexts.push(segment);\n          segLinkUrls.push(linkUrl);\n        }\n\n        if (segTexts.length === 0) return text;\n\n        // Build markdown with properly nested markers.\n        // We maintain a stack of currently open styles. Between segments,\n        // we close styles that are ending (popping from stack) and open\n        // styles that are starting (pushing onto stack). This ensures\n        // markers are always properly nested.\n        let result = '';\n        let openStack: string[] = []; // styles currently open, in nesting order\n\n        for (let i = 0; i < segTexts.length; i++) {\n          const curr = segActiveStyles[i];\n          // Determine which styles in styleOrder should be active\n          const desired = styleOrder.filter(s => curr.includes(s));\n\n          // Close styles that are open but not desired (pop from top of stack)\n          // We must close in reverse stack order (innermost first)\n          const toClose: string[] = [];\n          const toReopen: string[] = [];\n          // Find styles to close: anything in openStack not in desired\n          // But we may need to close styles above them too (and reopen)\n          const newStack: string[] = [];\n          for (const s of openStack) {\n            if (desired.includes(s)) {\n              newStack.push(s);\n            }\n          }\n          // Close everything from top of stack down to what we need\n          // Then reopen what should stay\n          if (openStack.length > 0) {\n            // Find the deepest style that needs to close\n            let closeFrom = -1;\n            for (let j = openStack.length - 1; j >= 0; j--) {\n              if (!desired.includes(openStack[j])) {\n                closeFrom = j;\n                break;\n              }\n            }\n            if (closeFrom >= 0) {\n              // Close everything from top down to closeFrom\n              for (let j = openStack.length - 1; j >= closeFrom; j--) {\n                result += markerMap[openStack[j]].close;\n                if (desired.includes(openStack[j])) {\n                  toReopen.push(openStack[j]);\n                }\n              }\n              openStack = openStack.slice(0, closeFrom);\n              // Reopen styles that should stay (in their original order)\n              for (const s of toReopen.reverse()) {\n                result += markerMap[s].open;\n                openStack.push(s);\n              }\n            }\n          }\n\n          // Open new styles that are desired but not yet open\n          for (const s of desired) {\n            if (!openStack.includes(s)) {\n              result += markerMap[s].open;\n              openStack.push(s);\n            }\n          }\n\n          // Add segment text (with link wrapping if needed)\n          if (segLinkUrls[i]) {\n            result += `[${segTexts[i]}](${segLinkUrls[i]})`;\n          } else {\n            result += segTexts[i];\n          }\n        }\n\n        // Close all remaining open styles (innermost first)\n        for (let j = openStack.length - 1; j >= 0; j--) {\n          result += markerMap[openStack[j]].close;\n        }\n\n        return result;\n      };\n\n      // Strip trailing empty list items (bullet/numbered) so \"1. text\\n2. \" sends as \"1. text\"\n      let blockCount = blocks.length;\n      while (blockCount > 0) {\n        const last = blocks[blockCount - 1];\n        if ((last.type === 'bullet' || last.type === 'numbered' || last.type === 'quoteBullet' || last.type === 'quoteNumbered') && !(last.text || '').trim()) {\n          blockCount--;\n        } else {\n          break;\n        }\n      }\n\n      const lines: string[] = [];\n      let inCodeBlock = false;\n      let orderedCounter = 0;\n      for (let i = 0; i < blockCount; i++) {\n        const block = blocks[i];\n        const text = block.text || '';\n        const styles: any[] = block.styles || [];\n        const blockType = block.type || 'paragraph';\n\n        if (blockType === 'codeBlock') {\n          if (!inCodeBlock) {\n            // Check if this is a single-line code block (no consecutive codeBlock after it)\n            const nextBlock = i + 1 < blocks.length ? blocks[i + 1] : null;\n            if (!nextBlock || (nextBlock.type || 'paragraph') !== 'codeBlock') {\n              // Single-line: emit ```content``` (no newlines)\n              lines.push(`\\`\\`\\`${text}\\`\\`\\``);\n              orderedCounter = 0;\n              continue;\n            }\n            // Multi-line: use fenced format\n            lines.push('```');\n            inCodeBlock = true;\n          }\n          // Code block lines use raw text (no inline style markers)\n          lines.push(text);\n        } else {\n          // Close code block fence if we were inside one\n          if (inCodeBlock) {\n            lines.push('```');\n            inCodeBlock = false;\n          }\n\n          const styledText = applyInlineStyles(text, styles);\n\n          switch (blockType) {\n            case 'bullet':\n            case 'quoteBullet':\n              // Bullet lines don't reset the counter (Slack behavior)\n              lines.push(blockType === 'quoteBullet' ? `> - ${styledText}` : `- ${styledText}`);\n              break;\n            case 'numbered':\n              orderedCounter++;\n              lines.push(`${orderedCounter}. ${styledText}`);\n              break;\n            case 'quoteNumbered':\n              orderedCounter++;\n              lines.push(`> ${orderedCounter}. ${styledText}`);\n              break;\n            case 'quote': lines.push(`> ${styledText}`); break;\n            case 'heading': lines.push(`# ${styledText}`); break;\n            default:\n              orderedCounter = 0;\n              lines.push(styledText);\n              break;\n          }\n        }\n      }\n      // Close any trailing code block fence\n      if (inCodeBlock) {\n        lines.push('```');\n      }\n\n      return lines.join('\\n');\n    };\n\n    /**\n     * Send text message\n     * Includes agentic user support for:\n     * - Parent message ID tracking\n     * - Send button delay\n     * - Streaming start\n     * - Reply message support\n     */\n    const sendTextMessage = () => {\n      const trimmedText = inputText.trim();\n\n      if (trimmedText.length === 0) {\n        return;\n      }\n\n      // Check if in edit mode - call editMessage instead\n      if (messagePreview !== null) {\n        editMessage(messagePreview.message);\n        return;\n      }\n\n      // Capture current reply message before clearing (for quoted message)\n      const currentReplyMessage = replyMessage;\n      const replyMessageId = currentReplyMessage?.message?.getId?.() ?? null;\n\n      // Convert blocks to markdown if rich text is enabled, otherwise use plain text\n      let textToSend = enableRichTextEditor && blocksRef.current.length > 0\n        ? blocksToMarkdown(blocksRef.current)\n        : trimmedText;\n      // Apply pending links: replace link display text with [text](url) markdown\n      if (pendingLinksRef.current.size > 0) {\n        pendingLinksRef.current.forEach((url, displayText) => {\n          // Only replace if the text isn't already wrapped in markdown link syntax\n          const escaped = displayText.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n          const alreadyLinked = new RegExp(`\\\\[${escaped}\\\\]\\\\(`);\n          if (!alreadyLinked.test(textToSend)) {\n            // Use a function replacement to avoid $ special patterns in replacement string\n            textToSend = textToSend.replace(displayText, () => `[${displayText}](${url})`);\n          }\n        });\n        pendingLinksRef.current.clear();\n      }\n\n      // Process mentions to underlying text format before sending.\n      // Mention map positions are based on plain text (plainTextInputRef),\n      // but blocksToMarkdown adds markdown syntax (```, `, **, etc.) that\n      // shifts character positions. So when mentions exist, do a name-based\n      // find-and-replace on the markdown text instead of position-based splicing.\n      let processedText: string;\n      if (mentionMap.current.size > 0) {\n        processedText = replaceMentionsWithTokens(textToSend);\n      } else {\n        processedText = getRegexString(textToSend);\n      }\n\n      let textMessage = new CometChat.TextMessage(\n        chatWithId.current,\n        processedText,\n        chatWith.current\n      );\n\n      textMessage.setSender(loggedInUser.current!);\n      textMessage.setReceiver((user || group)!);\n      textMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n\n      // Handle parent message ID for threaded messages\n      // For agentic users, use tracked parent message ID if no prop provided\n      if (parentMessageId) {\n        textMessage.setParentMessageId(parentMessageId as number);\n      } else if (isAgenticUser() && parentMessageIdRef.current) {\n        textMessage.setParentMessageId(parentMessageIdRef.current);\n      }\n\n      /**\n       * Handle reply message - set quoted message for replies.\n       * Uses shared helper for consistency with CometChatMessageComposer.\n       */\n      if (replyMessageId && currentReplyMessage?.message) {\n        setQuotedMessageSafe(textMessage, currentReplyMessage.message, replyMessageId);\n      }\n\n      // Process text through formatters\n      allFormatters.current.forEach((formatter) => {\n        textMessage = formatter.handlePreMessageSend(textMessage);\n      });\n\n      clearInputBox();\n      // Clear mention map after sending\n      mentionMap.current = new Map();\n      endTyping();\n\n      /**\n       * Clear reply preview after preparing message.\n       */\n      setReplyMessage(null);\n\n      // If custom send handler is provided, use it\n      if (onSendButtonPress) {\n        onSendButtonPress(textMessage);\n        return;\n      }\n\n      // Emit in-progress event\n      CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n        message: textMessage,\n        status: messageStatus.inprogress,\n      });\n\n      // Play sound if enabled\n      if (!disableSoundForMessages) {\n        playAudio();\n      }\n\n      /**\n       * Enable 1-second delay for agentic users after sending.\n       * This prevents rapid-fire messages to AI agents.\n       */\n      if (isAgenticUser()) {\n        setIsSendButtonDisabledForDelay(true);\n        if (sendButtonDelayTimer.current) {\n          clearTimeout(sendButtonDelayTimer.current);\n        }\n        sendButtonDelayTimer.current = setTimeout(() => {\n          setIsSendButtonDisabledForDelay(false);\n        }, 1000);\n      }\n\n      // Send message via SDK\n      CometChat.sendMessage(textMessage)\n        .then((message: CometChat.BaseMessage) => {\n          /**\n           * For agentic users without parentMessageId prop:\n           * - Store the message ID as parent for subsequent messages\n           * - Start streaming for the message\n           */\n          if (isAgenticUser() && !parentMessageId && message?.getId) {\n            const messageId = typeof message.getId() === 'string' \n              ? Number(message.getId()) \n              : message.getId();\n            \n            // Store parent message ID for subsequent messages\n            if (!isNaN(messageId) && !parentMessageIdRef.current) {\n              parentMessageIdRef.current = messageId;\n            }\n            \n            // Start streaming for agentic user messages\n            if (messageId && !isNaN(messageId)) {\n              startStreamingForRunId(String(messageId));\n            }\n          }\n\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: message,\n            status: messageStatus.success,\n          });\n        })\n        .catch((error: CometChat.CometChatException) => {\n          onError?.(error);\n          (textMessage as any).data.metaData = { error: true };\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: textMessage,\n            status: messageStatus.error,\n          });\n        });\n    };\n\n    /**\n     * Edit an existing message\n     * Called when in edit mode and user submits the edited text\n     */\n    const editMessage = (originalMessage: any) => {\n      endTyping();\n\n      // Convert blocks to markdown and replace mention display names with tokens\n      const trimmedText = inputText.trim();\n      let textToSend = enableRichTextEditor && blocksRef.current.length > 0\n        ? blocksToMarkdown(blocksRef.current)\n        : trimmedText;\n      textToSend = replaceMentionsWithTokens(textToSend);\n\n      // Create new text message with edited content\n      const textMessage = new CometChat.TextMessage(\n        chatWithId.current,\n        textToSend,\n        chatWith.current\n      );\n\n      // Set the message ID from the original message\n      textMessage.setId(originalMessage.id);\n\n      if (parentMessageId) {\n        textMessage.setParentMessageId(parentMessageId as number);\n      }\n\n      // Clear input and preview\n      clearInputBox();\n      setMessagePreview(null);\n\n      // If custom send handler is provided, use it\n      if (onSendButtonPress) {\n        onSendButtonPress(textMessage);\n        return;\n      }\n\n      // Play sound if enabled\n      if (!disableSoundForMessages) {\n        playAudio();\n      }\n\n      // Edit message via SDK\n      CometChat.editMessage(textMessage)\n        .then((editedMessage: CometChat.BaseMessage) => {\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n            message: editedMessage,\n            status: messageStatus.success,\n          });\n        })\n        .catch((error: CometChat.CometChatException) => {\n          onError?.(error);\n        });\n    };\n\n    /**\n     * Check if the text has actual user content beyond formatting markers.\n     * When a user taps bullet, numbered list, blockquote, or applies inline\n     * formatting without typing content, the editor inserts markers but no\n     * real content — send should stay disabled.\n     *\n     * Handles: empty code block fences, empty inline format markers\n     * (** **, _ _, <u></u>, ~~ ~~, ` `), nested empty formatting,\n     * and block prefixes (• , N. , ▎ ).\n     */\n    const hasActualContent = (text: string): boolean => {\n      let stripped = text;\n      // Remove code block fences and keep only inner content\n      stripped = stripped.replace(/```[\\s\\S]*?```/g, (m) => m.slice(3, -3));\n      // Remove inline format markers (keep inner content)\n      stripped = stripped.replace(/\\*\\*(.+?)\\*\\*/g, '$1');\n      stripped = stripped.replace(/<u>(.+?)<\\/u>/gi, '$1');\n      stripped = stripped.replace(/~~(.+?)~~/g, '$1');\n      stripped = stripped.replace(/`(.+?)`/g, '$1');\n      stripped = stripped.replace(/(?<![a-zA-Z0-9])_(.+?)_(?![a-zA-Z0-9])/g, '$1');\n      // Remove empty inline format markers (no content between delimiters)\n      stripped = stripped.replace(/\\*\\*\\s*\\*\\*/g, '');\n      stripped = stripped.replace(/<u>\\s*<\\/u>/gi, '');\n      stripped = stripped.replace(/~~\\s*~~/g, '');\n      stripped = stripped.replace(/`\\s*`/g, '');\n      stripped = stripped.replace(/(?<![a-zA-Z0-9])_\\s*_(?![a-zA-Z0-9])/g, '');\n      // Remove block prefixes (bullet, numbered list, blockquote, and combinations)\n      stripped = stripped\n        .split('\\n')\n        .map(line => line.replace(/^(>\\s)?(-\\s|•\\s|‧\\s|▎\\s|\\d+\\.\\s)+/, '').trim())\n        .join('');\n      return stripped.length > 0;\n    };\n\n    /**\n     * Mic button slide/fade animation effect.\n     * Triggers forward animation (slide out + fade) when text appears,\n     * and reverse animation (slide in + fade in) when text is cleared.\n     * Rapid toggling is handled by Animated API cancellation — calling\n     * .start() on a new animation cancels the previous one.\n     */\n    const hasContent = hasActualContent(inputText);\n    useEffect(() => {\n      if (hideVoiceRecordingButton) return;\n\n      // Run visual animation (native driver) and layout collapse (JS driver) in parallel\n      Animated.parallel([\n        // Visual: slide + fade (native driver for 60fps)\n        Animated.timing(micAnimValue, {\n          toValue: hasContent ? 1 : 0,\n          duration: MIC_ANIM_DURATION,\n          useNativeDriver: true,\n        }),\n        // Layout: width collapse (JS driver, needed for layout props)\n        Animated.timing(micWidthAnim, {\n          toValue: hasContent ? 0 : 1,\n          duration: MIC_ANIM_DURATION,\n          useNativeDriver: false,\n        }),\n      ]).start();\n    }, [hasContent, hideVoiceRecordingButton]);\n\n    /**\n     * Check if send button should be disabled.\n     * For agentic users, also considers:\n     * - Streaming state (disabled while AI is responding)\n     * - Send button delay (disabled for 1 second after sending)\n     */\n    const isSendDisabled = isStreaming || !hasActualContent(inputText) || isSendButtonDisabledForDelay;\n\n    /**\n     * Get send button tint color based on state\n     */\n    const getSendButtonTint = () => {\n      if (isSendDisabled) {\n        return theme.color.iconSecondary;\n      }\n      return resolvedStyle?.sendIconStyle?.tintColor || theme.color.primary;\n    };\n\n    /**\n     * Get attachment icon tint color\n     */\n    const getAttachmentIconTint = () => {\n      return resolvedStyle?.attachmentIconStyle?.tintColor || theme.color.iconSecondary;\n    };\n\n    /**\n     * Render secondary button (left side, for API compatibility)\n     * By default, shows the attachment button (📎) which opens the attachment action sheet.\n     * For agentic users, the attachment button is automatically hidden.\n     */\n    const renderSecondaryButton = () => {\n      // Hide attachment button for agentic users\n      if (hideAttachmentButton) {\n        return null;\n      }\n\n      if (SecondaryButtonView) {\n        return (\n          <SecondaryButtonView\n            user={user}\n            group={group}\n            composerId={id || 'single-line-composer'}\n          />\n        );\n      }\n\n      // Default attachment button - opens action sheet\n      const handleAttachmentClick = () => {\n        if (onAttachmentClick) {\n          onAttachmentClick();\n        }\n        setShowActionSheet(true);\n      };\n\n      // Always show attachment button by default (per requirements doc)\n      // The button opens the attachment action sheet with default options from ChatConfigurator\n      // Use SVG icon name for proper tintColor support\n      return (\n        <IconButton\n          name={attachmentIcon ? undefined : 'add-circle'}\n          icon={attachmentIcon}\n          onClick={handleAttachmentClick}\n          buttonStyle={Style.iconButton}\n          iconStyle={Style.icon}\n          tintColor={getAttachmentIconTint()}\n        />\n      );\n    };\n\n    /**\n     * Render auxiliary button\n     * Uses custom EmojiButton component with proper styling (no extra padding).\n     * For agentic users, auxiliary buttons are automatically hidden.\n     * When placed on the right (next to mic), slides/fades with the same animation as the mic button.\n     */\n    const renderAuxiliaryButton = () => {\n      // Hide auxiliary buttons if prop is set\n      if (hideAuxiliaryButton || hideAuxiliaryButtons) {\n        return null;\n      }\n\n      // Hide stickers button if prop is set\n      if (hideStickersButton) {\n        return null;\n      }\n\n      // For agentic users, hide default auxiliary buttons (but allow custom ones)\n      if (isAgenticUser() && !AuxiliaryButtonView) {\n        return null;\n      }\n\n      let content: JSX.Element;\n\n      // If custom AuxiliaryButtonView is provided, use it\n      if (AuxiliaryButtonView) {\n        content = (\n          <AuxiliaryButtonView\n            user={user}\n            group={group}\n            composerId={id || 'single-line-composer'}\n          />\n        );\n      } else {\n        // Use custom EmojiButton with proper styling (no extra padding)\n        content = (\n          <EmojiButton\n            user={user}\n            group={group}\n            composerIdMap={composerIdMap}\n            replyToMessage={replyMessage?.message}\n            closeReplyPreview={closeReplyPreview}\n            editorRef={inputRef}\n          />\n        );\n      }\n\n      // No animation needed for sticker — it naturally shifts via flexbox\n      // when the mic button's layout collapses\n      return content;\n    };\n\n    /**\n     * Default agent send button view component.\n     * Uses CometChatSendButtonView for agentic users with streaming support.\n     */\n    const DefaultAgentSendButtonView = useCallback(\n      ({ isButtonDisabled, composerRef }: { isButtonDisabled: boolean; composerRef: any }) => (\n        <CometChatSendButtonView\n          isButtonDisabled={isButtonDisabled}\n          composerRef={composerRef}\n          isStreaming={isStreaming}\n          showStopButton={showStopButton}\n          setShowStopButton={setShowStopButton}\n        />\n      ),\n      [isStreaming, showStopButton]\n    );\n\n    /**\n     * Render send button\n     * Always shows send button with disabled/enabled states based on input content.\n     * When input is empty, send button is disabled with inactive tint color.\n     * When input has text, send button is enabled with active tint color.\n     * For agentic users, renders the agent send button with streaming support.\n     */\n    const renderSendButton = () => {\n      if (hideSendButton) {\n        return null;\n      }\n\n      // For agentic users, use the agent send button view\n      if (isAgenticUser()) {\n        const disabled = isStreaming || inputText.trim().length === 0 || (messagePreview !== null) || isSendButtonDisabledForDelay;\n        const SendButtonComponent = AgentSendButtonView || DefaultAgentSendButtonView;\n        \n        // Create a ref-like object that matches what CometChatSendButtonView expects\n        const composerRef = {\n          current: {\n            sendTextMessage: () => {\n              if (!disabled) {\n                sendTextMessage();\n              }\n            }\n          }\n        };\n        \n        return <SendButtonComponent isButtonDisabled={disabled} composerRef={composerRef} />;\n      }\n\n      if (SendButtonView) {\n        return (\n          <SendButtonView\n            user={user}\n            group={group}\n            composerId={id || 'single-line-composer'}\n          />\n        );\n      }\n\n      // Always show send button with disabled/enabled state based on input content\n      // Figma spec: 32×32 circle, primary bg when active, background4 when disabled\n      const sendIconSize = theme.spacing.spacing.s5;\n      const sendIconColor = isSendDisabled\n        ? (theme.color.iconSecondary as string)\n        : (theme.color.primaryButtonIcon as string);\n      return (\n        <TouchableOpacity\n          testID=\"send-button\"\n          onPress={sendTextMessage}\n          disabled={isSendDisabled}\n          style={[{\n            width: theme.spacing.spacing.s8,\n            height: theme.spacing.spacing.s8,\n            borderRadius: theme.spacing.spacing.s15,\n            padding: theme.spacing.padding.p1,\n            justifyContent: 'center',\n            alignItems: 'center',\n            backgroundColor: isSendDisabled\n              ? (theme.color.background4 as string)\n              : (theme.color.primary as string),\n          }, sendButtonStyle]}\n        >\n          {sendButtonIcon ? (\n            <Icon\n              icon={sendButtonIcon}\n              width={sendIconSize}\n              height={sendIconSize}\n              color={sendIconColor}\n            />\n          ) : (\n            <Icon\n              name=\"send-fill\"\n              width={sendIconSize}\n              height={sendIconSize}\n              color={sendIconColor}\n            />\n          )}\n        </TouchableOpacity>\n      );\n    };\n\n    /**\n     * Render voice recording button with slide/fade animation.\n     * Shows/hides based on hideVoiceRecordingButton (derived from prop or agentic user status).\n     * For agentic users, voice recording is automatically hidden.\n     * The button slides right (translateX 0→30) and fades out (opacity 1→0) when text is entered,\n     * and reverses when text is cleared. Layout collapses to width:0 after animation completes.\n     */\n    const RecordAudioButtonView = () => {\n      // Preserve guard: hideVoiceRecordingButton or agentic returns null before wrapper\n      if (hideVoiceRecordingButton) {\n        return null;\n      }\n      // Hide voice recording button when inline recorder is active\n      if (showInlineRecorder) {\n        return null;\n      }\n      // Two nested Animated.Views to avoid native/JS driver conflict:\n      // Outer: JS driver — animates width + margin for layout collapse (eats the gap)\n      // Inner: native driver — animates opacity + translateX for smooth visuals\n      return (\n        <Animated.View\n          style={{\n            width: micWidthAnim.interpolate({ inputRange: [0, 1], outputRange: [0, 24] }),\n            marginLeft: micWidthAnim.interpolate({ inputRange: [0, 1], outputRange: [-16, 0] }),\n            overflow: 'hidden' as const,\n          }}\n        >\n          <Animated.View\n            pointerEvents={hasContent ? 'none' : 'auto'}\n            style={{\n              opacity: micAnimValue.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),\n              transform: [{ translateX: micAnimValue.interpolate({ inputRange: [0, 1], outputRange: [0, MIC_SLIDE_DISTANCE] }) }],\n            }}\n          >\n            <IconButton\n              name={voiceRecordingIconURL ? undefined : 'mic'}\n              icon={voiceRecordingIconURL}\n              onClick={() => {\n                // Show inline recorder\n                setTimeout(() => setShowInlineRecorder(true), 50);\n              }}\n              buttonStyle={Style.iconButton}\n              iconStyle={Style.icon}\n              tintColor={getAttachmentIconTint()}\n            />\n          </Animated.View>\n        </Animated.View>\n      );\n    };\n\n    // Container styles - outer wrapper with padding (matches MessageComposer)\n    const wrapperStyle: StyleProp<ViewStyle> = [\n      Style.wrapper,\n      {\n        backgroundColor: resolvedStyle?.containerStyle?.backgroundColor || theme.color.background3,\n      },\n    ];\n\n    // Inner container styles with border (matches MessageComposer: messageInputStyles.containerStyle)\n    const containerStyle: StyleProp<ViewStyle> = [\n      Style.container,\n      resolvedStyle?.containerStyle,\n      {\n        backgroundColor: resolvedStyle?.messageInputStyles?.containerStyle?.backgroundColor || theme.color.background1,\n        borderColor: resolvedStyle?.messageInputStyles?.containerStyle?.borderColor || theme.color.borderDefault,\n      },\n    ];\n\n    // Input styles - use messageInputStyles from v5 theme\n    const textInputStyle: StyleProp<TextStyle> = [\n      Style.textInput,\n      resolvedStyle?.messageInputStyles?.textStyle,\n      {\n        color: resolvedStyle?.messageInputStyles?.textStyle?.color || theme.color.textPrimary,\n        // Apply dynamic height when autoExpand is enabled\n        ...(autoExpand ? { height: inputHeight } : {}),\n      },\n      inputStyle,\n    ];\n\n    // Separator style (matches Figma: Seprator)\n    const separatorStyle: StyleProp<ViewStyle> = [\n      Style.separator,\n      resolvedStyle?.messageInputStyles?.dividerStyle,\n    ];\n\n    return (\n      <>\n        {/* Modal for custom views */}\n        <Modal\n          animationType=\"slide\"\n          visible={isVisible}\n          onRequestClose={() => {\n            setIsVisible(false);\n          }}\n          presentationStyle=\"pageSheet\"\n        >\n          {CustomView && CustomView}\n        </Modal>\n        \n        {/* KeyboardAvoidingView wrapper */}\n        <KeyboardAvoidingView\n          key={id}\n          behavior={Platform.OS === 'ios' ? 'padding' : 'height'}\n          keyboardVerticalOffset={Platform.select({ ios: kbOffset })}\n          {...keyboardAvoidingViewProps}\n        >\n          {/* Outer wrapper with padding (matches Figma: Message Composer) */}\n          <View style={wrapperStyle}>\n            {/* Inner container with border (matches Figma: Base_Message Composer) */}\n            <View style={containerStyle}>\n              {/* Action Sheet for attachments */}\n              <ActionSheetBoard\n                sheetRef={bottomSheetRef}\n                options={actionSheetItems}\n                shouldShow={showActionSheet}\n                onClose={() => setShowActionSheet(false)}\n                style={resolvedStyle?.attachmentOptionsStyles}\n              />\n              \n              {/* Voice Recording - removed bottom sheet, using inline recorder instead */}\n              \n              {HeaderView ? (\n                <HeaderView />\n              ) : CustomViewHeader ? (\n                typeof CustomViewHeader === 'function' ? (\n                  <CustomViewHeader />\n                ) : (\n                  CustomViewHeader\n                )\n              ) : null}\n              \n              {/* Mention Suggestion List */}\n              {showMentionList && plainTextInputRef.current.length > 0 && mentionsSearchData.length > 0 && (\n                <View\n                  style={[\n                    theme.mentionsListStyle.containerStyle,\n                    // Keep height stable to reduce flicker when list data loads/empties\n                    { maxHeight: Dimensions.get('window').height * (messagePreview ? 0.2 : (Platform.OS === 'ios' ? 0.15 : 0.22)) },\n                  ]}\n                >\n                  <CometChatSuggestionList\n                    data={mentionsSearchData}\n                    listStyle={theme.mentionsListStyle}\n                    onPress={onMentionPress}\n                    onEndReached={onSuggestionListEndReached}\n                    loading={suggestionListLoader}\n                  />\n                  {renderMentionLimitWarning()}\n                </View>\n              )}\n              \n              {/* Message Preview Tray for Edit Mode */}\n              <MessagePreviewTray\n                shouldShow={messagePreview !== null}\n                message={messagePreview?.message}\n                title={t('EDIT_MESSAGE')}\n                onClose={() => {\n                  setMessagePreview(null);\n                  clearInputBox();\n                }}\n              />\n              \n              {replyMessage && replyMessage.message && (\n                <CometChatMessagePreview\n                  message={replyMessage.message}\n                  showCloseIcon={true}\n                  closeIconURL={ICONS.CLOSE}\n                  onCloseClick={closeReplyPreview}\n                  titleStyle={{ color: theme.color.textHighlight as string }}\n                  style={{\n                    borderRadius: 8,\n                    borderLeftWidth: 3,\n                    borderLeftColor: theme.color.borderHighlight as string,\n                    margin: theme.spacing?.padding?.p1 || 4,\n                  }}\n                />\n              )}\n              \n              {/* Conditional: Show inline recorder OR input row */}\n              {showInlineRecorder ? (\n                <CometChatInlineAudioRecorder\n                  onSubmit={handleInlineRecorderSubmit}\n                  onCancel={handleInlineRecorderCancel}\n                  style={(resolvedStyle as any)?.inlineAudioRecorderStyle}\n                />\n              ) : (\n                <>\n                {/* Top Field - Input row with icons (matches Figma: Top Field) */}\n                <View\n                  style={[\n                    Style.inputRow,\n                    isExpanded && { alignItems: 'flex-end' as const },\n                  ]}\n                >\n                  {/* Left icons container - dynamic alignment based on expansion state */}\n                  <View style={[\n                    Style.leftIconsContainer,\n                    isExpanded && { alignItems: 'flex-end' as const }\n                  ]}>\n                    {/* Secondary button (attachment) */}\n                    {renderSecondaryButton()}\n                  </View>\n                  \n                  {/* Auxiliary button on left if alignment is 'left' */}\n                  {resolvedAlignment === 'left' && renderAuxiliaryButton()}\n                  \n                  {/* Rich Text Editor - replaces TextInput with same styling */}\n                  <View\n                    style={{ flex: 1, maxHeight: resolvedMaxHeight, overflow: 'hidden' }}\n                    {...(Platform.OS === 'web' ? {\n                      onKeyDown: (e: any) => {\n                        // Tab key inserts indentation on web (Req 3.1); no-op on native (Req 3.2)\n                        if (e.key === 'Tab') {\n                          e.preventDefault();\n                          inputRef.current?.setText?.(inputText + '\\t');\n                        }\n                      },\n                    } : {})}\n                  >\n                    <RichTextEditor\n                      ref={inputRef}\n                      testID=\"rich-text-editor\"\n                      initialContent={editContentBlocks}\n                      style={{\n                        backgroundColor: 'transparent',\n                      }}\n                      onContentChange={(event: ContentChangeEvent) => {\n                        const newText = event.nativeEvent.text;\n                        // Store blocks for markdown conversion when sending.\n                        // The RichTextEditor wrapper already parses blocksJson into blocks,\n                        // so event.nativeEvent.blocks is always populated.\n                        if (event.nativeEvent.blocks && event.nativeEvent.blocks.length > 0) {\n                          blocksRef.current = event.nativeEvent.blocks;\n                        }\n                        if (skipNextContentChange.current) {\n                          skipNextContentChange.current = false;\n                          plainTextInputRef.current = newText;\n                          inputTextRef.current = newText;\n                          return;\n                        }\n\n                        handleTextChange(newText);\n                      }}\n                      onSelectionChange={(event: any) => {\n                        const { start, end } = event.nativeEvent;\n                        handleSelectionChange({ nativeEvent: { selection: { start, end } } });\n                      }}\n                      onActiveStylesChange={(styles: ActiveStylesState) => {\n                        activeStylesRef.current = styles;\n                        setActiveStyles(styles);\n                        // Sync codeBlockActive from native codeBlock field\n                        setCodeBlockActive(!!styles.codeBlock);\n                      }}\n                      onSizeChange={(height: number) => {\n                        if (autoExpand) {\n                          // Snap to resolvedMinHeight when height is within 2px of it\n                          // to prevent sub-pixel layout drift between initial empty state\n                          // and post-clear empty state on iOS (UITextView.sizeThatFits\n                          // returns slightly different values for these two states)\n                          const snapped = Math.abs(height - resolvedMinHeight) <= 2 ? resolvedMinHeight : height;\n                          const clamped = Math.max(resolvedMinHeight, Math.min(snapped, resolvedMaxHeight));\n                          setInputHeight(clamped);\n                          const newIsExpanded = clamped > resolvedMinHeight;\n                          if (newIsExpanded !== isExpanded) {\n                            setIsExpanded(newIsExpanded);\n                          }\n                        }\n                      }}\n                      onFocus={() => {\n                        setIsFocused(true);\n                        startTyping();\n                      }}\n                      onBlur={() => {\n                        setIsFocused(false);\n                        endTyping();\n                      }}\n                      placeholder={placeHolderText || (isAgenticUser() ? t('ASK_ANYTHING') : t('ENTER_YOUR_MESSAGE_HERE'))}\n                      placeholderTextColor={resolvedStyle?.messageInputStyles?.placeHolderTextColor as string || theme.color.textTertiary as string}\n                      variant=\"plain\"\n                      showToolbar={false}\n                      maxHeight={resolvedMaxHeight}\n                      numberOfLines={maxLines ?? 5}\n                      textStyle={{\n                        fontSize: (resolvedStyle?.messageInputStyles?.textStyle as any)?.fontSize ?? 14,\n                        color: (resolvedStyle?.messageInputStyles?.textStyle?.color || theme.color.textPrimary) as string,\n                        fontFamily: (resolvedStyle?.messageInputStyles?.textStyle as any)?.fontFamily,\n                      }}\n                      codeBackgroundColor={theme.color.neutral200 as string}\n                      codeBorderColor={theme.color.neutral300 as string}\n                      codeTextColor={(theme.mode === 'dark' ? theme.color.extendedPrimary900 : theme.color.primary) as string}\n                      codeFontSize={theme.typography.caption1.regular.fontSize as number}\n                      onLinkTap={onLinkTap}\n                      enterKeyBehavior={enterKeyBehavior as string}\n                      showTextSelectionMenuItems={showTextSelectionMenuItems}\n                      onSendRequest={() => {\n                        if (!isSendDisabled) {\n                          sendTextMessage();\n                        }\n                      }}\n                    />\n                  </View>\n                  \n                  {/* Right icons container - dynamic alignment based on expansion state */}\n                  <View style={[\n                    Style.rightIconsContainer,\n                    isExpanded && { alignItems: 'flex-end' as const }\n                  ]}>\n                    {/* Auxiliary button (stickers/emoji) on right — slides with mic width collapse */}\n                    {resolvedAlignment === 'right' && !hideVoiceRecordingButton && (\n                      renderAuxiliaryButton()\n                    )}\n                    {resolvedAlignment === 'right' && hideVoiceRecordingButton && renderAuxiliaryButton()}\n                    \n                    {/* Voice recording button - matches Figma design */}\n                    {!hideVoiceRecordingButton && !isAgenticUser() && <RecordAudioButtonView />}\n                    \n                    {/* Send button */}\n                    {renderSendButton()}\n                  </View>\n                </View>\n\n                {/* Rich text toolbar — shown below input row */}\n                {enableRichTextEditor && !hideRichTextFormattingOptions && (\n                  <View style={{\n                    flexDirection: 'row',\n                    alignItems: 'center',\n                    paddingHorizontal: theme.spacing.padding.p2,\n                    paddingVertical: 6,\n                    borderTopWidth: StyleSheet.hairlineWidth,\n                    borderTopColor: theme.color.borderLight as string,\n                  }}>\n                    <ScrollView\n                      testID=\"toolbar-scrollview\"\n                      horizontal\n                      showsHorizontalScrollIndicator={false}\n                      contentContainerStyle={{ alignItems: 'center', gap: 2 }}\n                    >\n                      {richTextToolbarItems.map((item, index) => {\n                        if (item.type === 'separator') {\n                          return <View key={`sep-${index}`} style={[richTextToolbarStyles.separator, { backgroundColor: theme.color.borderLight as string }]} />;\n                        }\n                        // Slack-style: disable inline format buttons inside code blocks;\n                        // block-level buttons (lists, quote, code-block) stay enabled.\n                        const codeBlockAllowedKeys = ['ordered-list', 'unordered-list', 'blockquote', 'code-block'];\n                        const isDisabled = codeBlockActive && !codeBlockAllowedKeys.includes(item.key);\n                        let isActive = item.isActive?.(activeStyles) ?? false;\n                        // Distinguish inline code vs code block highlighting\n                        if (item.key === 'inline-code') {\n                          isActive = activeStyles.code && !codeBlockActive;\n                        } else if (item.key === 'code-block') {\n                          isActive = codeBlockActive;\n                        }\n                        const iconColor = isDisabled\n                          ? theme.color.iconSecondary\n                          : isActive ? theme.color.iconPrimary : theme.color.iconSecondary;\n                        return (\n                          <TouchableOpacity\n                            key={item.key}\n                            testID={`toolbar-${item.key}`}\n                            accessibilityValue={{ text: isActive ? 'on' : 'off' }}\n                            disabled={isDisabled}\n                            style={[richTextToolbarStyles.btn, isActive && { backgroundColor: theme.color.background4 as string }, isDisabled && { opacity: 0.3 }]}\n                            onPress={() => {\n                              if (item.key === 'inline-code') {\n                                setCodeBlockActive(false);\n                              }\n                              // Code-block active state is driven by native\n                              // onActiveStylesChange — don't eagerly set it here\n                              // since the button is a toggle.\n                              item.onPress(inputRef, selectionPosition, inputTextRef, setLinkText, setLinkUrl, setShowLinkModal);\n                            }}\n                          >\n                            <Icon\n                              name={item.iconName as any}\n                              width={theme.spacing.spacing.s6}\n                              height={theme.spacing.spacing.s6}\n                              color={iconColor}\n                            />\n                          </TouchableOpacity>\n                        );\n                      })}\n                    </ScrollView>\n                  </View>\n                )}\n                </>\n              )}\n              \n              {/* Separator line - only show when there's footer content (matches Figma: Seprator) */}\n              {(FooterView || CustomViewFooter) && <View style={separatorStyle} />}\n              \n              {FooterView ? (\n                <FooterView />\n              ) : CustomViewFooter ? (\n                typeof CustomViewFooter === 'function' ? (\n                  <CustomViewFooter />\n                ) : (\n                  CustomViewFooter\n                )\n              ) : null}\n            </View>\n          </View>\n        </KeyboardAvoidingView>\n\n        {/* Link insertion popup (reuses CometChatLinkConfirmPopup in insert mode) */}\n        <CometChatLinkConfirmPopup\n          visible={showLinkModal}\n          url=\"\"\n          initialEditText={linkText}\n          initialEditUrl={linkUrl}\n          insertMode\n          onDismiss={() => setShowLinkModal(false)}\n          onEdit={(newUrl, newText) => {\n            inputRef.current?.insertLink(newUrl, newText);\n            pendingLinksRef.current.set(newText, newUrl);\n            setShowLinkModal(false);\n          }}\n          onRemove={() => setShowLinkModal(false)}\n        />\n\n        {/* Link tap edit/remove dialog (shown when user taps an existing link) */}\n        <CometChatLinkConfirmPopup\n          visible={linkTapData !== null}\n          url={linkTapData?.url ?? ''}\n          initialEditUrl={editLinkUrl}\n          initialEditText={editLinkText}\n          onDismiss={() => {\n            setLinkTapData(null);\n            setLinkEditMode(false);\n            inputRef.current?.focus();\n          }}\n          onEdit={(newUrl, newText) => {\n            if (linkTapData) {\n              (inputRef.current as any)?.updateLink(\n                linkTapData.location,\n                linkTapData.length,\n                newUrl,\n                newText,\n              );\n            }\n            setLinkTapData(null);\n            setLinkEditMode(false);\n          }}\n          onRemove={() => {\n            if (linkTapData) {\n              (inputRef.current as any)?.removeLink(linkTapData.location, linkTapData.length);\n            }\n            setLinkTapData(null);\n            setLinkEditMode(false);\n          }}\n        />\n      </>\n    );\n  }\n);\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatCompactMessageComposer/SingleLineTextComposerConfiguration.tsx",
    "content": "import { ImageSourcePropType, ImageStyle, TextStyle, ViewStyle } from 'react-native';\nimport { SingleLineMessageComposerStyleInterface } from './CometChatCompactMessageComposer';\nimport { MessageComposerStyle } from '../CometChatMessageComposer/styles';\nimport {\n  DEFAULT_MIN_HEIGHT,\n  DEFAULT_MAX_LINES,\n  DEFAULT_LINE_HEIGHT,\n  DEFAULT_PADDING_VERTICAL,\n  calculateMaxHeightFromLines,\n} from './heightUtils';\n\n/**\n * Default values for CometChatSingleLineMessageComposer\n * \n * This configuration file documents all default values used by the component.\n * These defaults are applied when props are not explicitly provided.\n */\n\n/**\n * Default prop values for CometChatSingleLineMessageComposer\n */\nexport const SingleLineMessageComposerDefaults = {\n  /**\n   * Position of auxiliary buttons\n   * @default 'right'\n   */\n  auxiliaryButtonsAlignment: 'right' as 'left' | 'right',\n\n  /**\n   * Whether to hide auxiliary buttons\n   * @default false\n   */\n  hideAuxiliaryButton: false,\n\n  /**\n   * Whether to disable typing indicator events\n   * @default false\n   */\n  disableTypingEvents: false,\n\n  /**\n   * Whether to disable sound on message send\n   * Note: Disabled by default for single-line composer (unlike MessageComposer)\n   * @default true\n   */\n  disableSoundForMessages: true,\n\n  /**\n   * Whether to disable mentions functionality\n   * @default false\n   */\n  disableMentions: false,\n\n  /**\n   * Whether to hide voice recording button\n   * @default false\n   */\n  hideVoiceRecording: false,\n\n  /**\n   * Image quality for camera captures (1-100)\n   * @default 20\n   */\n  imageQuality: 20,\n\n  /**\n   * Placeholder text localization key\n   * Uses localize('ENTER_YOUR_MESSAGE_HERE') at runtime\n   */\n  placeholderTextKey: 'ENTER_YOUR_MESSAGE_HERE',\n\n  /**\n   * Maximum number of lines before scrolling is enabled\n   * @default 5\n   */\n  maxLines: DEFAULT_MAX_LINES,\n\n  /**\n   * Minimum height for the input in pixels\n   * @default 40 (single line height)\n   */\n  minInputHeight: DEFAULT_MIN_HEIGHT,\n\n  /**\n   * Maximum height for the input in pixels (calculated from maxLines by default)\n   * Overrides maxLines if explicitly provided\n   * @default calculated from maxLines (5 * 24 + 12 * 2 = 144)\n   */\n  maxInputHeight: calculateMaxHeightFromLines(DEFAULT_MAX_LINES, DEFAULT_LINE_HEIGHT, DEFAULT_PADDING_VERTICAL),\n};\n\n/**\n * Default style values for CometChatSingleLineMessageComposer\n * These are applied using theme palette values at runtime\n */\nexport const SingleLineMessageComposerStyleDefaults = {\n  /**\n   * Container border radius\n   * @default 8\n   */\n  borderRadius: 8,\n\n  /**\n   * Container border width\n   * @default 1\n   */\n  borderWidth: 1,\n\n  /**\n   * Input background color\n   * @default 'transparent'\n   */\n  inputBackgroundColor: 'transparent',\n\n  /**\n   * Minimum height for the text input (for auto-expand mode)\n   * @default 40\n   */\n  minHeight: DEFAULT_MIN_HEIGHT,\n\n  /**\n   * Maximum height for the text input (for auto-expand mode)\n   * @default calculated from maxLines (5 * 24 + 12 * 2 = 144)\n   */\n  maxHeight: calculateMaxHeightFromLines(DEFAULT_MAX_LINES, DEFAULT_LINE_HEIGHT, DEFAULT_PADDING_VERTICAL),\n\n  /**\n   * Line height for text (used in maxLines calculation)\n   * @default 24\n   */\n  lineHeight: DEFAULT_LINE_HEIGHT,\n};\n\n/**\n * Theme-dependent style defaults (v5 pattern)\n * These functions return the default values based on the current theme.\n * \n * V5 uses theme.color instead of theme.palette:\n * - theme.color.background1 instead of theme.palette.getBackgroundColor()\n * - theme.color.borderLight instead of theme.palette.getAccent100()\n * - theme.color.textPrimary instead of theme.palette.getAccent()\n * - theme.color.textTertiary instead of theme.palette.getAccent500()\n * - theme.color.primary instead of theme.palette.getPrimary()\n * - theme.color.iconTertiary instead of theme.palette.getAccent400()\n * - theme.color.iconSecondary instead of theme.palette.getAccent500()\n */\nexport const getThemeStyleDefaults = (theme: any): Partial<MessageComposerStyle> => ({\n  /**\n   * Container background color\n   * @default theme.color.background1\n   */\n  containerStyle: {\n    backgroundColor: theme.color?.background1,\n  } as ViewStyle,\n\n  /**\n   * Message input styles derived from theme\n   */\n  messageInputStyles: {\n    containerStyle: {\n      borderColor: theme.color?.borderLight,\n    } as ViewStyle,\n    textStyle: {\n      color: theme.color?.textPrimary,\n    } as TextStyle,\n    placeHolderTextColor: theme.color?.textTertiary,\n    dividerStyle: {\n      backgroundColor: theme.color?.borderLight,\n    } as ViewStyle,\n  },\n\n  /**\n   * Attachment icon style\n   */\n  attachmentIconStyle: {\n    tintColor: theme.color?.iconSecondary,\n  } as ImageStyle,\n});\n\n/**\n * Layout defaults from styles.ts\n * These are fixed pixel values used in the component layout\n */\nexport const SingleLineMessageComposerLayoutDefaults = {\n  /**\n   * Outer wrapper padding\n   */\n  wrapper: {\n    paddingTop: 0,\n    paddingBottom: 8,\n    paddingHorizontal: 8,\n  },\n\n  /**\n   * Input row padding and gap\n   */\n  inputRow: {\n    padding: 12,\n    gap: 12,\n  },\n\n  /**\n   * Right icons container gap\n   */\n  rightIconsGap: 16,\n\n  /**\n   * Icon dimensions\n   */\n  iconSize: {\n    height: 24,\n    width: 24,\n  },\n\n  /**\n   * Text input font settings\n   */\n  textInput: {\n    fontSize: 14,\n    lineHeight: 16.8, // 14 * 1.2\n  },\n\n  /**\n   * Separator height\n   */\n  separatorHeight: 1,\n};\n\n/**\n * Configuration interface for CometChatSingleLineMessageComposer\n */\nexport interface SingleLineMessageComposerConfigurationInterface {\n  /**\n   * Position of auxiliary buttons\n   */\n  auxiliaryButtonsAlignment?: 'left' | 'right';\n\n  /**\n   * Whether to hide auxiliary buttons\n   */\n  hideAuxiliaryButton?: boolean;\n\n  /**\n   * Whether to hide all auxiliary buttons (alias for hideAuxiliaryButton)\n   */\n  hideAuxiliaryButtons?: boolean;\n\n  /**\n   * Whether to disable typing indicator events\n   */\n  disableTypingEvents?: boolean;\n\n  /**\n   * Whether to disable sound on message send\n   */\n  disableSoundForMessages?: boolean;\n\n  /**\n   * Whether to disable mentions functionality\n   */\n  disableMentions?: boolean;\n\n  /**\n   * Whether to hide voice recording button\n   */\n  hideVoiceRecording?: boolean;\n\n  /**\n   * Whether to hide the attachment button\n   */\n  hideAttachmentButton?: boolean;\n\n  /**\n   * Whether to hide the stickers button\n   */\n  hideStickersButton?: boolean;\n\n  /**\n   * Whether to hide the send button\n   */\n  hideSendButton?: boolean;\n\n  /**\n   * Image quality for camera captures (1-100)\n   */\n  imageQuality?: number;\n\n  /**\n   * Placeholder text for the input\n   */\n  placeHolderText?: string;\n\n  /**\n   * Initial text for the composer\n   */\n  initialComposertext?: string;\n\n  /**\n   * Custom styles for the composer\n   */\n  style?: SingleLineMessageComposerStyleInterface;\n\n  /**\n   * Custom icon for the send button\n   */\n  sendButtonIcon?: ImageSourcePropType;\n\n  /**\n   * Custom icon for attachments\n   */\n  attachmentIcon?: ImageSourcePropType;\n\n  /**\n   * Custom icon for voice recording\n   */\n  voiceRecordingIconURL?: string;\n\n  /**\n   * Maximum number of lines before scrolling is enabled.\n   * @default 5\n   */\n  maxLines?: number;\n\n  /**\n   * Minimum height for the input in pixels.\n   * @default 40 (single line height)\n   */\n  minInputHeight?: number;\n\n  /**\n   * Maximum height for the input in pixels.\n   * Overrides maxLines if both are provided.\n   */\n  maxInputHeight?: number;\n\n  /**\n   * Whether to disable the @all mention suggestion\n   */\n  disableMentionAll?: boolean;\n\n  /**\n   * Custom label for the group-wide mention\n   */\n  mentionAllLabel?: string;\n\n  /**\n   * Hide camera option in attachment menu\n   */\n  hideCameraOption?: boolean;\n\n  /**\n   * Hide image attachment option\n   */\n  hideImageAttachmentOption?: boolean;\n\n  /**\n   * Hide video attachment option\n   */\n  hideVideoAttachmentOption?: boolean;\n\n  /**\n   * Hide audio attachment option\n   */\n  hideAudioAttachmentOption?: boolean;\n\n  /**\n   * Hide file attachment option\n   */\n  hideFileAttachmentOption?: boolean;\n\n  /**\n   * Hide polls attachment option\n   */\n  hidePollsAttachmentOption?: boolean;\n\n  /**\n   * Hide collaborative document option\n   */\n  hideCollaborativeDocumentOption?: boolean;\n\n  /**\n   * Hide collaborative whiteboard option\n   */\n  hideCollaborativeWhiteboardOption?: boolean;\n}\n\n/**\n * Configuration class for CometChatSingleLineMessageComposer\n * \n * Use this class to create a configuration object with custom defaults\n * that can be passed to the component.\n */\nexport class SingleLineMessageComposerConfiguration implements SingleLineMessageComposerConfigurationInterface {\n  /**\n   * Position of auxiliary buttons\n   */\n  auxiliaryButtonsAlignment?: 'left' | 'right';\n\n  /**\n   * Whether to hide auxiliary buttons\n   */\n  hideAuxiliaryButton?: boolean;\n\n  /**\n   * Whether to hide all auxiliary buttons (alias for hideAuxiliaryButton)\n   */\n  hideAuxiliaryButtons?: boolean;\n\n  /**\n   * Whether to disable typing indicator events\n   */\n  disableTypingEvents?: boolean;\n\n  /**\n   * Whether to disable sound on message send\n   */\n  disableSoundForMessages?: boolean;\n\n  /**\n   * Whether to disable mentions functionality\n   */\n  disableMentions?: boolean;\n\n  /**\n   * Whether to hide voice recording button\n   */\n  hideVoiceRecording?: boolean;\n\n  /**\n   * Whether to hide the attachment button\n   */\n  hideAttachmentButton?: boolean;\n\n  /**\n   * Whether to hide the stickers button\n   */\n  hideStickersButton?: boolean;\n\n  /**\n   * Whether to hide the send button\n   */\n  hideSendButton?: boolean;\n\n  /**\n   * Image quality for camera captures (1-100)\n   */\n  imageQuality?: number;\n\n  /**\n   * Placeholder text for the input\n   */\n  placeHolderText?: string;\n\n  /**\n   * Initial text for the composer\n   */\n  initialComposertext?: string;\n\n  /**\n   * Custom styles for the composer\n   */\n  style?: SingleLineMessageComposerStyleInterface;\n\n  /**\n   * Custom icon for the send button\n   */\n  sendButtonIcon?: ImageSourcePropType;\n\n  /**\n   * Custom icon for attachments\n   */\n  attachmentIcon?: ImageSourcePropType;\n\n  /**\n   * Custom icon for voice recording\n   */\n  voiceRecordingIconURL?: string;\n\n  /**\n   * Maximum number of lines before scrolling is enabled.\n   * @default 5\n   */\n  maxLines?: number;\n\n  /**\n   * Minimum height for the input in pixels.\n   * @default 40 (single line height)\n   */\n  minInputHeight?: number;\n\n  /**\n   * Maximum height for the input in pixels.\n   * Overrides maxLines if both are provided.\n   */\n  maxInputHeight?: number;\n\n  /**\n   * Whether to disable the @all mention suggestion\n   */\n  disableMentionAll?: boolean;\n\n  /**\n   * Custom label for the group-wide mention\n   */\n  mentionAllLabel?: string;\n\n  /**\n   * Hide camera option in attachment menu\n   */\n  hideCameraOption?: boolean;\n\n  /**\n   * Hide image attachment option\n   */\n  hideImageAttachmentOption?: boolean;\n\n  /**\n   * Hide video attachment option\n   */\n  hideVideoAttachmentOption?: boolean;\n\n  /**\n   * Hide audio attachment option\n   */\n  hideAudioAttachmentOption?: boolean;\n\n  /**\n   * Hide file attachment option\n   */\n  hideFileAttachmentOption?: boolean;\n\n  /**\n   * Hide polls attachment option\n   */\n  hidePollsAttachmentOption?: boolean;\n\n  /**\n   * Hide collaborative document option\n   */\n  hideCollaborativeDocumentOption?: boolean;\n\n  /**\n   * Hide collaborative whiteboard option\n   */\n  hideCollaborativeWhiteboardOption?: boolean;\n\n  constructor(config: Partial<SingleLineMessageComposerConfigurationInterface> = {}) {\n    Object.assign(this, config);\n  }\n\n  /**\n   * Create a configuration with all defaults explicitly set\n   */\n  static withDefaults(): SingleLineMessageComposerConfiguration {\n    return new SingleLineMessageComposerConfiguration({\n      auxiliaryButtonsAlignment: SingleLineMessageComposerDefaults.auxiliaryButtonsAlignment,\n      hideAuxiliaryButton: SingleLineMessageComposerDefaults.hideAuxiliaryButton,\n      disableTypingEvents: SingleLineMessageComposerDefaults.disableTypingEvents,\n      disableSoundForMessages: SingleLineMessageComposerDefaults.disableSoundForMessages,\n      disableMentions: SingleLineMessageComposerDefaults.disableMentions,\n      hideVoiceRecording: SingleLineMessageComposerDefaults.hideVoiceRecording,\n      imageQuality: SingleLineMessageComposerDefaults.imageQuality,\n      maxLines: SingleLineMessageComposerDefaults.maxLines,\n      minInputHeight: SingleLineMessageComposerDefaults.minInputHeight,\n      maxInputHeight: SingleLineMessageComposerDefaults.maxInputHeight,\n    });\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatCompactMessageComposer/heightUtils.ts",
    "content": "/**\n * Height Calculation Utility Functions for Auto-Expanding TextInput\n *\n * These pure functions handle height calculations for the auto-expanding\n * TextInput feature in CometChatSingleLineMessageComposer.\n *\n * @module heightUtils\n */\n\n/**\n * Default constants for height calculations\n */\nexport const DEFAULT_MIN_HEIGHT = 24;\nexport const DEFAULT_MAX_HEIGHT = 200;\nexport const DEFAULT_LINE_HEIGHT = 24;\nexport const DEFAULT_MAX_LINES = 5;\nexport const DEFAULT_PADDING_VERTICAL = 12;\n\n/**\n * Pure function to calculate the target input height.\n * Clamps the content height between minHeight and maxHeight.\n *\n * @param contentHeight - The actual content height from onContentSizeChange\n * @param minHeight - Minimum allowed height (must be positive)\n * @param maxHeight - Maximum allowed height (must be >= minHeight)\n * @returns The clamped height value\n *\n * @example\n * // Content fits within bounds\n * calculateInputHeight(100, 40, 200) // returns 100\n *\n * // Content exceeds max\n * calculateInputHeight(250, 40, 200) // returns 200\n *\n * // Content below min\n * calculateInputHeight(20, 40, 200) // returns 40\n *\n * **Validates: Requirements 1.1, 1.2, 1.3**\n */\nexport function calculateInputHeight(\n  contentHeight: number,\n  minHeight: number,\n  maxHeight: number\n): number {\n  // Input validation: handle negative or zero values\n  const validContentHeight = Math.max(0, contentHeight);\n  const validMinHeight = Math.max(1, minHeight);\n  // Ensure maxHeight is at least minHeight\n  const validMaxHeight = Math.max(validMinHeight, maxHeight);\n\n  // Clamp content height between min and max\n  return Math.min(Math.max(validContentHeight, validMinHeight), validMaxHeight);\n}\n\n/**\n * Pure function to calculate max height from maxLines.\n * Computes the maximum height based on number of lines, line height, and padding.\n *\n * @param maxLines - Number of lines (must be positive, defaults to DEFAULT_MAX_LINES)\n * @param lineHeight - Height per line in pixels (must be positive)\n * @param paddingVertical - Vertical padding in pixels (must be non-negative)\n * @returns Maximum height in pixels\n *\n * @example\n * // 5 lines with 24px line height and 12px padding\n * calculateMaxHeightFromLines(5, 24, 12) // returns 144 (5 * 24 + 12 * 2)\n *\n * **Validates: Requirements 2.3**\n */\nexport function calculateMaxHeightFromLines(\n  maxLines: number,\n  lineHeight: number,\n  paddingVertical: number\n): number {\n  // Input validation: handle negative or zero values\n  const validMaxLines = Math.max(1, maxLines);\n  const validLineHeight = Math.max(1, lineHeight);\n  const validPaddingVertical = Math.max(0, paddingVertical);\n\n  return validMaxLines * validLineHeight + validPaddingVertical * 2;\n}\n\n/**\n * Pure function to determine icon alignment based on expansion state.\n * Returns 'flex-start' (top alignment) when expanded, 'center' when collapsed.\n *\n * @param currentHeight - Current input height in pixels\n * @param minHeight - Minimum (single-line) height in pixels\n * @returns 'flex-start' when expanded (height > minHeight), 'center' when collapsed\n *\n * @example\n * // Expanded state (height > minHeight)\n * getIconAlignment(100, 40) // returns 'flex-start'\n *\n * // Collapsed state (height === minHeight)\n * getIconAlignment(40, 40) // returns 'center'\n *\n * **Validates: Requirements 3.1, 3.2, 3.3**\n */\nexport function getIconAlignment(\n  currentHeight: number,\n  minHeight: number\n): 'flex-start' | 'center' {\n  // Input validation: handle negative or zero values\n  const validCurrentHeight = Math.max(0, currentHeight);\n  const validMinHeight = Math.max(1, minHeight);\n\n  return validCurrentHeight > validMinHeight ? 'flex-start' : 'center';\n}\n\n/**\n * Configuration options for resolving effective max height.\n */\nexport interface MaxHeightConfig {\n  /**\n   * Maximum number of lines before scrolling is enabled.\n   * Used to calculate maxHeight if maxHeight is not explicitly provided.\n   */\n  maxLines?: number;\n  /**\n   * Maximum height in pixels. Takes precedence over maxLines if provided.\n   */\n  maxHeight?: number;\n  /**\n   * Line height for text (used in maxLines calculation).\n   * @default DEFAULT_LINE_HEIGHT\n   */\n  lineHeight?: number;\n  /**\n   * Vertical padding (used in maxLines calculation).\n   * @default DEFAULT_PADDING_VERTICAL\n   */\n  paddingVertical?: number;\n}\n\n/**\n * Pure function to resolve the effective maximum height based on configuration.\n * When both maxLines and maxHeight are provided, maxHeight takes precedence.\n *\n * @param config - Configuration object with maxLines and/or maxHeight\n * @returns The effective maximum height in pixels\n *\n * @example\n * // maxHeight takes precedence over maxLines\n * resolveEffectiveMaxHeight({ maxLines: 5, maxHeight: 100 }) // returns 100\n *\n * // maxLines is used when maxHeight is not provided\n * resolveEffectiveMaxHeight({ maxLines: 5 }) // returns 144 (5 * 24 + 12 * 2)\n *\n * // Default values when neither is provided\n * resolveEffectiveMaxHeight({}) // returns DEFAULT_MAX_HEIGHT\n *\n * **Validates: Requirements 2.2, 2.3**\n */\nexport function resolveEffectiveMaxHeight(config: MaxHeightConfig): number {\n  const {\n    maxLines,\n    maxHeight,\n    lineHeight = DEFAULT_LINE_HEIGHT,\n    paddingVertical = DEFAULT_PADDING_VERTICAL,\n  } = config;\n\n  // If maxHeight is explicitly provided (not undefined), it takes precedence\n  if (maxHeight !== undefined) {\n    // Validate maxHeight: must be positive\n    return Math.max(1, maxHeight);\n  }\n\n  // If maxLines is provided, calculate maxHeight from it\n  if (maxLines !== undefined) {\n    return calculateMaxHeightFromLines(maxLines, lineHeight, paddingVertical);\n  }\n\n  // Default: use DEFAULT_MAX_HEIGHT\n  return DEFAULT_MAX_HEIGHT;\n}\n\n/**\n * Pure function to determine if height state should be updated.\n * Returns true only when the new calculated height differs from the current height.\n * This optimization prevents unnecessary re-renders when height doesn't change.\n *\n * @param newHeight - The newly calculated height from content size change\n * @param currentHeight - The current height state value\n * @returns true if height should be updated, false otherwise\n *\n * @example\n * // Height changed - should update\n * shouldUpdateHeight(100, 40) // returns true\n *\n * // Height unchanged - should not update\n * shouldUpdateHeight(40, 40) // returns false\n *\n * // Floating point comparison with tolerance\n * shouldUpdateHeight(40.0001, 40) // returns false (within tolerance)\n *\n * **Validates: Requirements 7.2**\n */\nexport function shouldUpdateHeight(newHeight: number, currentHeight: number): boolean {\n  // Use a small tolerance for floating point comparison to avoid\n  // unnecessary updates due to floating point precision issues\n  const TOLERANCE = 0.001;\n  return Math.abs(newHeight - currentHeight) > TOLERANCE;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatCompactMessageComposer/index.ts",
    "content": "import {\n  CometChatCompactMessageComposer,\n  CometChatCompactMessageComposerInterface,\n  SingleLineMessageComposerStyleInterface,\n} from './CometChatCompactMessageComposer';\nimport {\n  SingleLineMessageComposerConfiguration,\n  SingleLineMessageComposerConfigurationInterface,\n  SingleLineMessageComposerDefaults,\n  SingleLineMessageComposerStyleDefaults,\n  SingleLineMessageComposerLayoutDefaults,\n  getThemeStyleDefaults,\n} from './SingleLineTextComposerConfiguration';\nimport {\n  calculateInputHeight,\n  calculateMaxHeightFromLines,\n  getIconAlignment,\n  DEFAULT_MIN_HEIGHT,\n  DEFAULT_MAX_HEIGHT,\n  DEFAULT_LINE_HEIGHT,\n  DEFAULT_MAX_LINES,\n  DEFAULT_PADDING_VERTICAL,\n} from './heightUtils';\nimport {\n  Style,\n  MIN_INPUT_HEIGHT,\n  SingleLineComposerStyle,\n  getSingleLineComposerStyle,\n} from './styles';\n\nexport {\n  CometChatCompactMessageComposer,\n  SingleLineMessageComposerConfiguration,\n  SingleLineMessageComposerConfigurationInterface,\n  CometChatCompactMessageComposerInterface,\n  SingleLineMessageComposerStyleInterface,\n  // Default values exports\n  SingleLineMessageComposerDefaults,\n  SingleLineMessageComposerStyleDefaults,\n  SingleLineMessageComposerLayoutDefaults,\n  getThemeStyleDefaults,\n  // Height calculation utilities\n  calculateInputHeight,\n  calculateMaxHeightFromLines,\n  getIconAlignment,\n  DEFAULT_MIN_HEIGHT,\n  DEFAULT_MAX_HEIGHT,\n  DEFAULT_LINE_HEIGHT,\n  DEFAULT_MAX_LINES,\n  DEFAULT_PADDING_VERTICAL,\n  // Style exports\n  Style,\n  MIN_INPUT_HEIGHT,\n  SingleLineComposerStyle,\n  getSingleLineComposerStyle,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatCompactMessageComposer/resources/index.ts",
    "content": "import CLOSE from '../../CometChatMessageComposer/resources/close.png';\n\nexport const ICONS = {\n  CLOSE,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatCompactMessageComposer/styles.ts",
    "content": "import {\n  ColorValue,\n  ImageSourcePropType,\n  ImageStyle,\n  StyleSheet,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { ActionSheetStyle, CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { JSX } from \"react\";\n\n/**\n * Constants for auto-expanding TextInput height calculations.\n * These are exported for use in component logic and style calculations.\n *\n * @see heightUtils.ts for calculation functions\n */\nexport const MIN_INPUT_HEIGHT = 24;\nexport const DEFAULT_LINE_HEIGHT = 24;\n\nexport const Style = StyleSheet.create({\n  // Outer wrapper with padding (matches Figma: pb-8, px-8)\n  wrapper: {\n    paddingTop: 0,\n    paddingBottom: 8,\n    paddingHorizontal: 8,\n  },\n  // Main container with border and rounded corners (matches Figma: Base_Message Composer)\n  container: {\n    flexDirection: 'column',\n    borderWidth: 1,\n    borderRadius: 8,\n    overflow: 'hidden',\n  },\n  // Input row with icons and text input (matches Figma: Top Field)\n  inputRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingHorizontal: 12,\n    paddingVertical: 12,\n  },\n  // Expanded input row variant - icons align to top when input is expanded\n  expandedInputRow: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n    paddingHorizontal: 12,\n    paddingVertical: 12,\n  },\n  // Left icons container (matches Figma: Icons left)\n  leftIconsContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginRight: 12,\n  },\n  // Left icons container - top aligned for expanded state\n  leftIconsContainerTop: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n    marginRight: 12,\n    paddingTop: 0,\n  },\n  // Right icons container with spacing (matches Figma: Icons right - gap 16px)\n  rightIconsContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginLeft: 12,\n    gap: 16,\n  },\n  // Right icons container - top aligned for expanded state\n  rightIconsContainerTop: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n    marginLeft: 12,\n    paddingTop: 0,\n    gap: 16,\n  },\n  // Text input - flex to fill available space (matches Figma: Placeholder)\n  textInput: {\n    flex: 1,\n    fontSize: 14,\n    lineHeight: DEFAULT_LINE_HEIGHT,\n    padding: 0,\n    margin: 0,\n    minHeight: MIN_INPUT_HEIGHT,\n  },\n  // Text input for auto-expand mode - supports dynamic height\n  textInputAutoExpand: {\n    flex: 1,\n    fontSize: 14,\n    lineHeight: DEFAULT_LINE_HEIGHT,\n    padding: 0,\n    margin: 0,\n    minHeight: MIN_INPUT_HEIGHT,\n    textAlignVertical: 'top',\n  },\n  // Icon button touchable area\n  iconButton: {\n    justifyContent: 'center',\n    alignItems: 'center',\n    minWidth: 24,\n    minHeight: 24,\n  },\n  // ImageButton base style\n  imageButtonBase: {\n    minWidth: 24,\n    minHeight: 24,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  // Icon size (matches Figma: 24x24)\n  icon: {\n    height: 24,\n    width: 24,\n    minHeight: 24,\n    minWidth: 24,\n  },\n  // Separator line (matches Figma: Seprator)\n  separator: {\n    height: 1,\n    width: '100%',\n  },\n  // Auxiliary container for custom buttons\n  auxiliaryContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  // Live reaction styles\n  liveReactionStyle: {\n    alignItems: 'flex-end',\n  },\n  liveReactionBtnStyle: {\n    height: 24,\n    width: 24,\n    resizeMode: 'stretch',\n  },\n  // Legacy aliases for backward compatibility\n  sendButton: {\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  sendIcon: {\n    height: 24,\n    width: 24,\n  },\n  // Mention suggestion list container\n  mentionListContainer: {\n    borderTopWidth: 1,\n    paddingVertical: 5,\n    paddingHorizontal: 0,\n    justifyContent: 'flex-end',\n    zIndex: 1,\n  },\n  // Mention limit warning container\n  mentionLimitContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingTop: 5,\n    paddingLeft: 5,\n    borderTopWidth: 1,\n  },\n  // Mention limit icon\n  mentionLimitIcon: {\n    height: 20,\n    width: 20,\n  },\n  // Mention limit text\n  mentionLimitText: {\n    marginLeft: 5,\n  },\n});\n\nconst getAttachmentOptionsStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ActionSheetStyle => {\n  return {\n    optionsItemStyle: {\n      containerStyle: {\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p4,\n        backgroundColor: color.background1,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 5,\n      },\n      titleStyle: {\n        ...typography.body.regular,\n        color: color.textPrimary,\n        paddingLeft: spacing.padding.p1,\n      },\n      iconStyle: {\n        height: 24,\n        width: 24,\n      },\n      iconContainerStyle: {},\n    },\n  };\n};\n\nexport type SingleLineComposerStyle = {\n  containerStyle: ViewStyle;\n  sendIcon?: ImageSourcePropType | JSX.Element;\n  sendIconStyle: ImageStyle;\n  sendIconContainerStyle: ViewStyle;\n  attachmentIcon?: ImageSourcePropType | JSX.Element;\n  attachmentIconStyle: ImageStyle;\n  voiceRecordingIcon: ImageSourcePropType | JSX.Element;\n  voiceRecordingIconStyle: ImageStyle;\n  messageInputStyles: {\n    containerStyle: ViewStyle;\n    textStyle: TextStyle;\n    placeHolderTextColor?: ColorValue | undefined;\n    dividerStyle: ViewStyle;\n  };\n  mentionsStyle: CometChatTheme[\"mentionsStyle\"];\n  stickerIcon: {\n    active: ImageSourcePropType | JSX.Element;\n    inactive: ImageSourcePropType | JSX.Element;\n  };\n  stickerIconStyle: {\n    active: ImageStyle;\n    inactive: ImageStyle;\n  };\n  mediaRecorderStyle: CometChatTheme[\"mediaRecorderStyle\"];\n  attachmentOptionsStyles: ActionSheetStyle;\n};\n\nexport const getSingleLineComposerStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<SingleLineComposerStyle> => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      width: \"100%\",\n    },\n    messageInputStyles: {\n      containerStyle: {\n        borderRadius: spacing.radius.r2,\n        borderWidth: spacing.spacing.s0_5 / 2,\n        borderColor: color.borderDefault,\n        backgroundColor: color.background1,\n      },\n      textStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p2,\n        color: color.textPrimary,\n        maxHeight: spacing.spacing.s20,\n        ...typography.body.regular,\n      },\n      placeHolderTextColor: color.textTertiary,\n      dividerStyle: {\n        height: 1,\n        backgroundColor: color.borderLight,\n        marginVertical: spacing.margin.m1,\n      },\n    },\n    attachmentIconStyle: {\n      tintColor: color.iconSecondary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    voiceRecordingIconStyle: {\n      tintColor: color.iconSecondary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    mentionsStyle: {\n      textStyle: {\n        ...typography.body.regular,\n        color: color.textHighlight,\n      },\n      selfTextStyle: {\n        ...typography.body.regular,\n        color: color.warning,\n      },\n    },\n    stickerIconStyle: {\n      inactive: {\n        tintColor: color.iconSecondary,\n      },\n      active: {\n        tintColor: color.primary,\n      },\n    },\n    attachmentOptionsStyles: getAttachmentOptionsStyle(color, spacing, typography),\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatConversations/CometChatConversations.tsx",
    "content": "/*\n * CometChatConversations.tsx\n * ---------------------------------------------------------------------------\n * CometChatConversations is a React Native component that displays a list of conversations\n * It provides features such as message receipt visibility, custom sound notifications,\n * date formatting, and selection modes (none, single, multiple).\n * It also allows for custom rendering of conversation items, error handling, and loading states.\n * The component supports user and group events, message events, and call events.\n * It also provides options for customizing the appearance of the conversation list.\n * ---------------------------------------------------------------------------\n */\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useCallback, useMemo } from \"react\";\nimport { Text, View, GestureResponderEvent, Platform, StyleSheet } from \"react-native\";\nimport {\n  ChatConfigurator,\n  CometChatAvatar,\n  CometChatConversationEvents,\n  CometChatList,\n  CometChatListActionsInterface,\n  CometChatMentionsFormatter,\n  CometChatRetryButton,\n  CometChatSoundManager,\n  CometChatStatusIndicator,\n  CometChatTextFormatter,\n  CometChatUIKit,\n  CometChatUiKitConstants,\n  CometChatUrlsFormatter,\n} from \"../shared\";\nimport { CometChatSearch } from \"../CometChatSearch\";\nimport { SelectionMode } from \"../shared/base/Types\";\nimport {\n  ConversationTypeConstants,\n  GroupTypeConstants,\n  MessageCategoryConstants,\n  MessageReceipt,\n  MessageStatusConstants,\n  ReceiverTypeConstants,\n} from \"../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { CommonUtils } from \"../shared/utils/CommonUtils\";\nimport { stripMarkdown, preparePreviewText } from \"../shared/utils/MarkdownUtils\";\nimport { CometChatRichTextFormatter } from \"../shared/formatters/CometChatRichTextFormatter\";\nimport { getMessagePreviewInternal, applyMentionsFormatting } from \"../shared/utils/MessageUtils\";\nimport { CometChatBadge } from \"../shared/views/CometChatBadge\";\nimport { CometChatConfirmDialog } from \"../shared/views/CometChatConfirmDialog\";\nimport { CometChatDate } from \"../shared/views/CometChatDate\";\nimport { CometChatReceipt } from \"../shared/views/CometChatReceipt\";\nimport { CometChatTooltipMenu } from \"../shared/views/CometChatTooltipMenu\";\nimport { ErrorEmptyView } from \"../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { useTheme } from \"../theme\";\nimport { Skeleton } from \"./Skeleton\";\nimport { ConversationStyle, Style } from \"./style\";\nimport MessageReceiptUtils from \"../shared/utils/MessageReceiptUtils\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport Delete from \"../shared/icons/components/delete\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { MenuItemInterface } from \"../shared/views/CometChatTooltipMenu/CometChatTooltipMenu\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\n// Unique listener IDs for conversation, user, group, message and call events.\nconst conversationListenerId = \"chatlist_\" + new Date().getTime();\nconst userListenerId = \"chatlist_user_\" + new Date().getTime();\nconst groupListenerId = \"chatlist_group_\" + new Date().getTime();\nconst messageListenerId = \"chatlist_message_\" + new Date().getTime();\nconst callListenerId = \"call_\" + new Date().getTime();\n\n/** Module-level rich text formatter — reused across renders (no GC churn) */\nconst conversationRichTextFormatter = new CometChatRichTextFormatter();\n\n/**\n * Interface defining props for the CometChatConversations component.\n */\nexport interface ConversationInterface {\n  /**\n   * Hide the submit (selection) button.\n   */\n  hideSubmitButton?: boolean;\n  /**\n   * Toggles message receipts (single/double‑tick) inside the subtitle. When\n   * `false`, ticks are not rendered for the last outgoing message.\n   */\n  receiptsVisibility?: boolean;\n  /**\n   * Toggle sound playback for received messages.\n   */\n  disableSoundForMessages?: boolean;\n  /**\n   * Custom sound file path for received messages.\n   */\n  customSoundForMessages?: string;\n  /**\n   * Function to generate a custom date string for a conversation.\n   * @param conversation - The conversation object.\n   * @returns A string representing the date.\n   */\n  datePattern?: (conversation: CometChat.Conversation) => string;\n  /**\n   * Completely overrides the default rendering of each conversation item in the list.\n   *\n   * **Note:** When `ItemView` is provided, all internal rendering logic – including\n   * LeadingView, TitleView, SubtitleView, TrailingView – is ignored.\n   *\n   * **Important:** If you use `ItemView`, you are also responsible for handling:\n   *\n   * - **`onItemPress`** — trigger conversation open or custom action.\n   * - **`onItemLongPress`** — show tooltip or perform contextual action.\n   * - **Selection mode** (`selectionMode: \"single\" | \"multiple\"`) — you must manage\n   *   selection state, checkboxes, and visual feedback yourself.\n   */\n  ItemView?: (item: CometChat.Conversation) => JSX.Element;\n  /**\n   * Functional component for rendering options in the app bar.\n   */\n  AppBarOptions?: () => JSX.Element;\n  /**\n   * Hide the back button.\n   */\n  hideBackButton?: boolean;\n  /**\n   * Selection mode: \"none\" | \"single\" | \"multiple\".\n   */\n  selectionMode?: SelectionMode;\n  /**\n   * Callback when conversation selection is complete.\n   */\n  onSelection?: (conversations: Array<CometChat.Conversation>) => void;\n  /**\n   * Callback when submit selection button is pressed.\n   */\n  onSubmit?: (conversation: Array<CometChat.Conversation>) => void;\n  /**\n   * Custom view for the empty state.\n   */\n  EmptyView?: () => JSX.Element;\n  /**\n   * Custom view for the error state.\n   */\n  ErrorView?: () => JSX.Element;\n  /**\n   * Custom view for the loading state.\n   */\n  LoadingView?: () => JSX.Element;\n  /**\n   * Request builder to fetch conversations.\n   */\n  conversationsRequestBuilder?: CometChat.ConversationsRequestBuilder;\n  /**\n   * Custom leading view for a conversation item.\n   */\n  LeadingView?: (conversation: CometChat.Conversation) => JSX.Element;\n  /**\n   * Custom title view for a conversation item.\n   */\n  TitleView?: (conversation: CometChat.Conversation) => JSX.Element;\n  /**\n   * Custom subtitle view for a conversation item.\n   */\n  SubtitleView?: (item: CometChat.Conversation) => JSX.Element;\n  /**\n   * Custom tail view for a conversation item.\n   */\n  TrailingView?: (item: CometChat.Conversation) => JSX.Element;\n  /**\n   * Hide error view.\n   */\n  hideError?: boolean;\n  /**\n   * Callback for when a conversation item is pressed.\n   */\n  onItemPress?: (item: CometChat.Conversation) => void;\n  /**\n   * Callback for when a conversation item is long pressed.\n   */\n  onItemLongPress?: (item: CometChat.Conversation) => void;\n  /**\n   * Callback when an error occurs while fetching conversations.\n   */\n  onError?: (e: CometChat.CometChatException) => void;\n  /**\n   * Callback for when the back action is triggered.\n   */\n  onBack?: () => void;\n  /**\n   * Array of text formatter classes.\n   */\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n  /**\n   * Custom styles for the conversation view.\n   */\n  style?: DeepPartial<ConversationStyle>;\n  /**\n   * Hide the header of the conversation list.\n   */\n  hideHeader?: boolean;\n  /**\n   * Callback triggered when the fetched list is empty.\n   */\n  onEmpty?: () => void;\n  /**\n   * Callback triggered once the users have loaded and are not empty.\n   */\n  onLoad?: (list: CometChat.Conversation[]) => void;\n  /**\n   * A function to **replace** the default menu items entirely for a users.\n   */\n  options?: (conversation: CometChat.Conversation) => MenuItemInterface[];\n  /**\n   * A function to **append** more menu items on top of the default menu items for a users.\n   */\n  addOptions?: (conversation: CometChat.Conversation) => MenuItemInterface[];\n  /**\n   * Toggle user status visibilty.\n   */\n  usersStatusVisibility?: boolean;\n  /**\n   * Toggle group type visibilty.\n   */\n  groupTypeVisibility?: boolean;\n  /**\n   * Toggle delete conversation option  visibilty.\n   */\n  deleteConversationOptionVisibility?: boolean;\n  /**\n   * Toggle search bar visibility in the conversations header.\n   */\n  showSearchBar?: boolean;\n  /**\n   * Callback triggered when the search bar is clicked or focused.\n   */\n  onSearchBarClicked?: () => void;\n  /**\n   * Callback triggered when search text changes.\n   */\n  onSearchTextChanged?: (searchText: string) => void;\n  /**\n   * Current search text value.\n   */\n  searchText?: string;\n  /**\n   * Custom search view component to display in the conversations header.\n   */\n  SearchView?: () => JSX.Element;\n\n}\n\n/**\n * CometChatConversations is a container component that wraps and formats the conversation list.\n * It handles events such as new messages, typing indicators, call events, and group events.\n */\nexport const CometChatConversations = (props: ConversationInterface) => {\n  const {\n    receiptsVisibility = true,\n    disableSoundForMessages = false,\n    hideHeader = false,\n    customSoundForMessages,\n    datePattern,\n    ItemView,\n    AppBarOptions,\n    hideSubmitButton = false,\n    hideBackButton = true,\n    selectionMode = \"none\",\n    onSelection,\n    onSubmit,\n    EmptyView,\n    ErrorView,\n    LoadingView,\n    conversationsRequestBuilder,\n    LeadingView,\n    TitleView,\n    SubtitleView,\n    TrailingView,\n    hideError = false,\n    onItemPress,\n    onItemLongPress,\n    onError,\n    onBack,\n    textFormatters,\n    style,\n    onEmpty,\n    onLoad,\n    options,\n    addOptions,\n    usersStatusVisibility = true,\n    groupTypeVisibility = true,\n    deleteConversationOptionVisibility = true,\n    showSearchBar = false,\n    onSearchBarClicked,\n    onSearchTextChanged,\n    searchText = \"\",\n    SearchView,\n  } = props;\n\n  // Reference for accessing CometChatList methods\n  const conversationListRef = React.useRef<CometChatListActionsInterface>(null);\n  // Store the logged in user for comparison and event handling.\n  const loggedInUser = React.useRef<CometChat.User>(undefined);\n  // State to control the confirmation dialog for deleting a conversation.\n  const [confirmDelete, setConfirmDelete] = React.useState<string | undefined>(undefined);\n  // State to control selection mode for conversation items.\n  const [selecting, setSelecting] = React.useState(selectionMode === \"none\" ? false : true);\n  const [selectedConversation, setSelectedConversations] = React.useState<\n    Array<CometChat.Conversation>\n  >([]);\n  // Timer for debouncing member-added events.\n  const onMemberAddedToGroupDebounceTimer = React.useRef<ReturnType<typeof setTimeout> | null>(\n    null\n  );\n  // Reference to store long press identifier.\n  const longPressId = React.useRef<string | undefined>(undefined);\n  const longPressedConversation = React.useRef<CometChat.Conversation>(undefined);\n\n  // Reference to store tooltip position for long press events.\n  const tooltipPositon = React.useRef({\n    pageX: 0,\n    pageY: 0,\n  });\n  const [tooltipVisible, setTooltipVisible] = React.useState(false);\n\n  // Merge theme styles with provided style overrides.\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n\n  // Configure conversation subtitle formatter with theme-aware inline code colors\n  useMemo(() => {\n    conversationRichTextFormatter.setStyle({\n      inlineCodeStyle: {\n        color: theme.color.primary as string,\n      },\n      inlineCodeContainerStyle: {\n        backgroundColor: \"rgba(120, 120, 128, 0.22)\",\n        borderRadius: 4,\n        borderWidth: 0.5,\n        borderColor: \"rgba(120, 120, 128, 0.35)\",\n        paddingHorizontal: 4,\n        paddingVertical: 1,\n      },\n    });\n  }, [theme]);\n\n  const mergedStyles = useMemo(() => {\n    const baseStyles = deepMerge(theme.conversationStyles, style ?? {});\n    // Add search styling for CometChatList component\n    return {\n      ...baseStyles,\n      searchStyle: {\n        textStyle: {\n          color: theme.color.textPrimary,\n          ...theme.typography.heading4.regular,\n        },\n        placehodlerTextStyle: {\n          color: theme.color.textTertiary,\n        },\n        containerStyle: {\n          backgroundColor: theme.color.background3,\n          borderRadius: theme.spacing.radius.max,\n          paddingHorizontal: theme.spacing.spacing.s3,\n          marginVertical: theme.spacing.spacing.s2,\n          flexDirection: 'row' as const,\n          alignItems: 'center' as const,\n          gap: theme.spacing.spacing.s1,\n        },\n        iconStyle: {\n          tintColor: theme.color.iconSecondary,\n          width: 24,\n          height: 24,\n        },\n      },\n    } as any;\n  }, [theme, style]);\n\n\n\n  /**\n   * ErrorStateView renders a view to show when an error occurs.\n   */\n  const ErrorStateView = useCallback(() => {\n    if (hideError) return null;\n    return (\n      <ErrorEmptyView\n        title={t(\"OOPS\")}\n        subTitle={t(\"SOMETHING_WENT_WRONG\")}\n        tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n        Icon={\n          <Icon\n            name='error-state'\n            size={theme.spacing.margin.m15 << 1}\n            containerStyle={{\n              marginBottom: theme.spacing.margin.m5,\n              ...mergedStyles?.errorStateStyle?.iconContainerStyle,\n            }}\n            icon={mergedStyles?.errorStateStyle?.icon}\n            imageStyle={mergedStyles?.errorStateStyle?.iconStyle}\n          />\n        }\n        containerStyle={mergedStyles?.errorStateStyle?.containerStyle}\n        titleStyle={mergedStyles?.errorStateStyle?.titleStyle}\n        subTitleStyle={mergedStyles?.errorStateStyle?.subTitleStyle}\n        RetryView={<CometChatRetryButton onPress={() => conversationListRef.current?.reload()} />}\n      />\n    );\n  }, [theme, mergedStyles, hideError]);\n\n  /**\n   * EmptyStateView renders a view when no conversations are available.\n   */\n  const EmptyStateView = useCallback(() => {\n    return (\n      <ErrorEmptyView\n        title={t(\"NO_CONVERSATIONS\")}\n        subTitle={t(\"CONVERSATIONS_EMPTY_MESSAGE\")}\n        Icon={\n          <Icon\n            name='empty-state'\n            size={theme.spacing.spacing.s15 << 1}\n            containerStyle={{\n              marginBottom: theme.spacing.spacing.s5,\n              ...mergedStyles?.emptyStateStyle?.iconContainerStyle,\n            }}\n            icon={mergedStyles?.emptyStateStyle?.icon}\n            imageStyle={mergedStyles?.emptyStateStyle?.iconStyle}\n          />\n        }\n        containerStyle={mergedStyles?.emptyStateStyle?.containerStyle}\n        titleStyle={mergedStyles?.emptyStateStyle?.titleStyle}\n        subTitleStyle={mergedStyles?.emptyStateStyle?.subTitleStyle}\n      />\n    );\n  }, [theme, mergedStyles]);\n\n  /**\n   * Handler for user online/offline events. Finds the corresponding conversation and updates it.\n   */\n  const userEventHandler = (...args: any[]) => {\n    const { uid } = args[0];\n    let item: CometChat.Conversation | any =\n      (conversationListRef.current?.getListItem(\n        `${uid}_user_${loggedInUser.current?.getUid()}`\n      ) as unknown as CometChat.Conversation) ||\n      (conversationListRef.current?.getListItem(\n        `${loggedInUser.current?.getUid()}_user_${uid}`\n      ) as unknown as CometChat.Conversation);\n    const user: CometChat.User = item.getConversationWith();\n    if (user.getBlockedByMe() || user.getHasBlockedMe()) return;\n    if (item) {\n      let updatedConversation = CommonUtils.clone(item);\n      updatedConversation.setConversationWith(args[0]);\n      conversationListRef.current?.updateList(updatedConversation);\n    }\n  };\n\n  /**\n   * Returns a conversation that matches a typing indicator.\n   */\n  const getConversationRefFromTypingIndicator = (typingIndicator: CometChat.TypingIndicator) => {\n    let list = conversationListRef.current?.getAllListItems();\n    return list?.find((item: CometChat.Conversation) => {\n      return (\n        (typingIndicator.getReceiverType() == ReceiverTypeConstants.user &&\n          item.getConversationType() == ReceiverTypeConstants.user &&\n          (item.getConversationWith() as CometChat.User).getUid() ==\n            typingIndicator.getSender().getUid() &&\n          !(\n            (item.getConversationWith() as CometChat.User)?.getBlockedByMe() ||\n            (item.getConversationWith() as CometChat.User)?.getHasBlockedMe()\n          )) ||\n        (typingIndicator.getReceiverType() == ReceiverTypeConstants.group &&\n          item.getConversationType() == ReceiverTypeConstants.group &&\n          (item.getConversationWith() as CometChat.Group).getGuid() ==\n            typingIndicator.getReceiverId())\n      );\n    });\n  };\n\n  /**\n   * Handler for typing events in conversations.\n   * Toggle the *live typing…* indicator on a conversation row.\n   */\n  const typingEventHandler = (...args: any) => {\n    let conversation: CometChat.Conversation = CommonUtils.clone(\n      getConversationRefFromTypingIndicator(args[0])\n    );\n    if (conversation) {\n      let isTyping = args[1];\n      let newConversation = conversation;\n      if (isTyping && !newConversation?.[\"lastMessage\"]?.[\"typing\"]) {\n        newConversation[\"lastMessage\"][\"typing\"] =\n          args[0]?.receiverType === \"group\"\n            ? `${args[0].sender.name} ${t(\"IS_TYPING\")}`\n            : t(\"IS_TYPING\");\n      } else {\n        delete newConversation[\"lastMessage\"][\"typing\"];\n      }\n      conversationListRef.current!.updateList(newConversation);\n    }\n  };\n\n  /**\n   * Checks and updates the last message in a conversation if it matches the provided message.\n   * @param newMessage - The new message object.\n   */\n  const checkAndUpdateLastMessage = (newMessage: CometChat.BaseMessage) => {\n    CometChat.CometChatHelper.getConversationFromMessage(newMessage).then(\n      (conversation: CometChat.Conversation) => {\n        let conver: CometChat.Conversation = conversationListRef.current!.getListItem(\n          conversation.getConversationId()\n        );\n        if (!conver) return;\n        let lastMessageId = conver.getLastMessage().getId();\n        if (lastMessageId == newMessage.getId()) {\n          conversationListRef.current!.updateList(CommonUtils.clone(conversation));\n        }\n      }\n    );\n  };\n\n  /**\n   * Determines whether the last message and unread count should be updated.\n   * @param message - The message to check.\n   * @returns True if an update is needed.\n   */\n  const shouldUpdateLastMessageAndUnreadCount = (message: CometChat.BaseMessage) => {\n    // Do not update for threaded messages if not enabled.\n    if (\n      message.getParentMessageId() &&\n      !CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnMessageReplies()\n    ) {\n      return false;\n    }\n\n    // Do not update for custom messages if not allowed.\n    if (message.getCategory() == CometChatUiKitConstants.MessageCategoryConstants.custom) {\n      let customMessage = message as CometChat.CustomMessage;\n      if (\n        !customMessage.willUpdateConversation() &&\n        !(\n          customMessage.getMetadata() &&\n          (customMessage.getMetadata() as any)[\"incrementUnreadCount\"]\n        ) &&\n        !CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCustomMessages()\n      ) {\n        return false;\n      }\n    }\n\n    // Check for group actions.\n    if (\n      message.getCategory() == CometChatUiKitConstants.MessageCategoryConstants.action &&\n      message.getReceiverType() == CometChatUiKitConstants.ReceiverTypeConstants.group\n    ) {\n      return CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnGroupActions();\n    }\n\n    // Check for call activities.\n    if (\n      message.getCategory() == CometChatUiKitConstants.MessageCategoryConstants.call &&\n      !CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()\n    ) {\n      return false;\n    }\n    return true;\n  };\n\n  /**\n   * Updates the conversation with a new message and moves it to the top of the list.\n   * @param newMessage - The new message to update.\n   */\n  const updateLastMessage = (newMessage: CometChat.BaseMessage) => {\n    CometChat.CometChatHelper.getConversationFromMessage(newMessage)\n      .then((conversation) => {\n        if (newMessage.getCategory() === MessageCategoryConstants.interactive) {\n          // TODO: Show unsupported message view.\n        }\n        const oldConversation: CometChat.Conversation = conversationListRef.current!.getListItem(\n          conversation.getConversationId()\n        );\n        if (oldConversation == undefined) {\n          // If conversation not found, add it.\n          CometChat.CometChatHelper.getConversationFromMessage(newMessage)\n            .then((newConversation) => {\n              if (\n                newConversation?.getLastMessage().getSender().getUid() !==\n                loggedInUser.current?.getUid()\n              )\n                newConversation.setUnreadMessageCount(1);\n              conversationListRef.current!.addItemToList(newConversation, 0);\n            })\n            .catch((err) => onError && onError(err));\n          return;\n        }\n        // Update last message and unread count.\n        oldConversation.setLastMessage(newMessage);\n        if (newMessage.getSender().getUid() != loggedInUser.current?.getUid())\n          oldConversation.setUnreadMessageCount(oldConversation.getUnreadMessageCount() + 1);\n        conversationListRef.current!.updateAndMoveToFirst(CommonUtils.clone(oldConversation));\n      })\n      .catch((err) => {\n        console.log(\"Error\", err);\n      });\n  };\n\n  /**\n   * Plays the notification sound for incoming messages.\n   */\n  const playNotificationSound = () => {\n    if (disableSoundForMessages) return;\n    CometChatSoundManager.play(\n      customSoundForMessages || CometChatSoundManager.SoundOutput.incomingMessageFromOther\n    );\n  };\n\n  /**\n   * Determines if a message should be marked as delivered.\n   * @param message - The message object.\n   * @returns True if the message does not have a \"deliveredAt\" property.\n   */\n  const shouldMarkAsDelivered = (message: object) => {\n    return !message.hasOwnProperty(\"deliveredAt\");\n  };\n\n  /**\n   * Marks a message as delivered and plays notification sound if applicable.\n   * @param message - The message to mark as delivered.\n   */\n  const markMessageAsDelivered = (message: CometChat.BaseMessage) => {\n    if (message.hasOwnProperty(\"deletedAt\")) return;\n\n    if (shouldMarkAsDelivered(message)) {\n      CometChat.markAsDelivered(message);\n      playNotificationSound();\n    }\n  };\n\n  /**\n   * Updates message receipt for the conversation.\n   * @param receipt - The message receipt.\n   */\n  const updateMessageReceipt = (receipt: CometChat.MessageReceipt) => {\n    const conv: CometChat.Conversation | boolean =\n      receipt?.getReceiverType() === ReceiverTypeConstants.user\n        ? (conversationListRef.current?.getListItem(\n            `${receipt?.getReceiver()}_user_${receipt?.getSender().getUid()}`\n          ) as unknown as CometChat.Conversation) ||\n          (conversationListRef.current?.getListItem(\n            `${receipt?.getSender()?.getUid()}_user_${receipt?.getReceiver()}`\n          ) as unknown as CometChat.Conversation)\n        : [\n            receipt.RECEIPT_TYPE.DELIVERED_TO_ALL_RECEIPT,\n            receipt.RECEIPT_TYPE.READ_BY_ALL_RECEIPT,\n          ].includes(receipt?.getReceiptType()) &&\n          (conversationListRef.current?.getListItem(\n            `group_${receipt?.getReceiver()}`\n          ) as unknown as CometChat.Conversation);\n\n    if (\n      conv &&\n      conv.getConversationType() == ConversationTypeConstants.group &&\n      conv.getLastMessage().getSender().getUid() !== loggedInUser.current!.getUid()\n    ) {\n      return;\n    }\n\n    if (\n      conv &&\n      conv?.getLastMessage &&\n      (String(typeof conv.getLastMessage().getId === 'function' ? conv.getLastMessage().getId() : conv.getLastMessage().id) === String(receipt.getMessageId()))\n    ) {\n      let newConversation = CommonUtils.clone(conv);\n      if (receipt.getReadAt()) {\n        newConversation.getLastMessage().setReadAt(receipt.getReadAt());\n      }\n      if (receipt.getDeliveredAt()) {\n        newConversation.getLastMessage().setDeliveredAt(receipt.getDeliveredAt());\n      }\n      conversationListRef.current?.updateList(newConversation);\n    }\n  };\n\n  /**\n   * Handler for when a message (text/media/custom) is received.\n   * Marks the message as delivered and updates the conversation.\n   * @param args - Contains the new message.\n   */\n  const messageEventHandler = (...args: any) => {\n    let message = args[0];\n    markMessageAsDelivered(message);\n    updateLastMessage(message);\n  };\n\n  /**\n   * Handler for various group actions such as member kicked, banned, left, or scope change.\n   * @param message - The action message.\n   * @param otherDetails - Additional details about the action.\n   */\n  const groupHandler = (\n    message: CometChat.Action,\n    otherDetails: {\n      action?: string;\n      actionOn?: CometChat.User;\n      actionBy?: CometChat.User;\n      group?: CometChat.Group;\n      newScope?: CometChat.GroupMemberScope;\n      oldScope?: CometChat.GroupMemberScope;\n    } = {}\n  ) => {\n    let conversation: CometChat.Conversation = conversationListRef.current!.getListItem(\n      message.getConversationId()\n    ) as unknown as CometChat.Conversation;\n    let { action, actionOn, actionBy, group, newScope, oldScope } = otherDetails;\n    if (conversation) {\n      if (action == \"scopeChange\" && actionOn?.getUid() !== loggedInUser.current!.getUid()) {\n        oldScope = undefined;\n        newScope = undefined;\n      }\n      const oldScopeLocal: any =\n        oldScope ?? (conversation.getConversationWith() as CometChat.Group).getScope();\n      if (\n        action &&\n        [\"kicked\", \"banned\", \"left\"].includes(action) &&\n        actionOn &&\n        actionOn.getUid() == loggedInUser.current!.getUid()\n      ) {\n        conversationListRef.current!.removeItemFromList(message.getConversationId());\n        return;\n      } else {\n        if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnGroupActions()) {\n          return;\n        }\n        conversation.setLastMessage(message);\n        if (group) {\n          !group.getScope() && group.setScope(newScope ?? oldScopeLocal);\n          conversation.setConversationWith(group);\n        }\n        conversationListRef.current!.updateList(conversation);\n      }\n    } else {\n      CometChat.CometChatHelper.getConversationFromMessage(message).then((newConversation) => {\n        const conversation: CometChat.Conversation = conversationListRef.current!.getListItem(\n          message.getConversationId()\n        ) as unknown as CometChat.Conversation;\n        if (conversation) {\n          groupHandler(message);\n        } else {\n          conversationListRef.current!.addItemToList(newConversation, 0);\n        }\n      });\n    }\n  };\n\n  /**\n   * Handles the conversation click event.\n   * If an onItemPress callback is provided, it is invoked.\n   * Else, toggles selection of the conversation.\n   * @param conversation - The conversation object that was clicked.\n   */\n  const conversationClicked = (conversation: CometChat.Conversation) => {\n    if (onItemPress) {\n      onItemPress(conversation);\n      return;\n    }\n    if (!selecting) {\n      // Fire event if not selecting.\n      return;\n    }\n\n    if (selectionMode == \"none\") return;\n\n    let index = selectedConversation.findIndex(\n      (tmpConver: CometChat.Conversation) =>\n        tmpConver.getConversationId() == conversation.getConversationId()\n    );\n    if (index < 0) {\n      if (selectionMode == \"single\") setSelectedConversations([conversation]);\n\n      if (selectionMode == \"multiple\")\n        setSelectedConversations([...selectedConversation, conversation]);\n    } else {\n      selectedConversation.splice(index, 1);\n      setSelectedConversations([...selectedConversation]);\n    }\n  };\n\n  /**\n   * Removes a conversation from the selection list.\n   * @param id - The conversation ID.\n   */\n  const removeItemFromSelectionList = (id: any) => {\n    if (selecting) {\n      let index = selectedConversation.findIndex((member) => member.getConversationId() == id);\n      if (index > -1) {\n        let tmpSelectedConversations = [...selectedConversation];\n        tmpSelectedConversations.splice(index, 1);\n        setSelectedConversations(tmpSelectedConversations);\n      }\n    }\n  };\n\n  /**\n   * Removes a conversation from the list by calling the delete API and then updating the UI.\n   * @param id - The conversation ID to remove.\n   */\n  const removeConversation = (id: string) => {\n    let conversation = conversationListRef.current!.getListItem(id);\n    const { conversationWith, conversationType } = conversation;\n    let conversationWithId =\n      conversationType == \"group\" ? conversationWith.guid : conversationWith.uid;\n    CometChat.deleteConversation(conversationWithId, conversationType)\n      .then((success) => {\n        CometChatUIEventHandler.emitConversationEvent(\n          CometChatConversationEvents.ccConversationDeleted,\n          { conversation: conversation }\n        );\n        conversationListRef.current!.removeItemFromList(id);\n        removeItemFromSelectionList(id);\n      })\n      .catch((err) => console.log(err));\n  };\n\n  /**\n   * Returns a formatted preview for the last message in a conversation.\n   * @param conversations - The conversation object.\n   * @param theme - The theme object.\n   * @returns A JSX.Element containing the preview.\n   */\n  const getMessagePreview = (conversations: CometChat.Conversation, theme?: CometChatTheme) => {\n    const loggedInUserId = CometChatUIKit.loggedInUser!.getUid();\n    let lastMessage: CometChat.BaseMessage =\n      conversations?.getLastMessage && conversations.getLastMessage();\n    if (!lastMessage) return null;\n    let messageText: string | JSX.Element = \"\";\n    messageText = ChatConfigurator.getDataSource().getLastConversationMessage(conversations, theme);\n    \n    // Detect block-level elements in text messages before stripMarkdown flattens them\n    let blockType: 'blockquote' | 'codeBlock' | 'list' | null = null;\n    let codeBlockLine = '';\n    let listPrefix = '';\n    if (\n      lastMessage instanceof CometChat.TextMessage &&\n      lastMessage.getCategory() === MessageCategoryConstants.message &&\n      typeof messageText === \"string\"\n    ) {\n      const rawText = messageText;\n      const preview = preparePreviewText(rawText);\n      if (preview.isBlockquote) {\n        blockType = 'blockquote';\n        // Run formatter on content only (no `> ` prefix) so it won't re-parse as block\n        messageText = getFormattedText(lastMessage, preview.text.trim());\n      } else if (preview.codeBlockFirstLine !== null) {\n        blockType = 'codeBlock';\n        codeBlockLine = preview.codeBlockFirstLine;\n      } else if (preview.listPrefix) {\n        blockType = 'list';\n        listPrefix = preview.listPrefix;\n        messageText = getFormattedText(lastMessage, preview.text.trim());\n      } else {\n        messageText = getFormattedText(lastMessage, preview.text.trim());\n      }\n    } else if (lastMessage && typeof messageText === \"string\") {\n      messageText = getFormattedText(lastMessage, messageText?.trim());\n    }\n\n    if (\n      lastMessage instanceof CometChat.TextMessage &&\n      lastMessage.getCategory() === MessageCategoryConstants.message &&\n      (() => {\n        const text = typeof lastMessage.getText === \"function\" ? lastMessage.getText() : undefined;\n        return typeof text === \"string\" && text.slice(0, 50).match(/https?:\\/\\//);\n      })()\n    ) {\n      messageText = getMessagePreviewInternal(\"link-fill\", t(\"LINK\"), { theme });\n    } else if (blockType === 'codeBlock') {\n      messageText = (\n        <View style={conversationPreviewStyles.codeBlockRow}>\n          <View style={[conversationPreviewStyles.codeBlockBadge, { backgroundColor: theme?.color?.background2 as string || '#FAFAFA', borderColor: theme?.color?.borderDefault as string || '#E8E8E8' }]}>\n            <Text numberOfLines={1} ellipsizeMode='tail' style={[conversationPreviewStyles.codeBlockText, { color: theme?.color?.textPrimary as string || '#141414' }]}>\n              {codeBlockLine + '..'}\n            </Text>\n          </View>\n        </View>\n      );\n    } else if (blockType === 'blockquote') {\n      messageText = (\n        <View style={conversationPreviewStyles.blockquoteRow}>\n          <View style={[conversationPreviewStyles.blockquoteBar, { backgroundColor: theme?.color?.primary as string }]} />\n          <Text numberOfLines={1} ellipsizeMode='tail' style={[mergedStyles.itemStyle.subtitleStyle, conversationPreviewStyles.blockquoteText]}>\n            {messageText}\n          </Text>\n        </View>\n      );\n    } else if (blockType === 'list') {\n      messageText = (\n        <Text\n          style={[mergedStyles.itemStyle.subtitleStyle, { flexShrink: 2 }]}\n          numberOfLines={1}\n          ellipsizeMode='tail'\n        >\n          {listPrefix}{messageText}{'...'}\n        </Text>\n      );\n    } else if (messageText && typeof messageText === 'string') {\n      messageText = (\n        <Text\n          style={[mergedStyles.itemStyle.subtitleStyle, { flexShrink: 2 }]}\n          numberOfLines={1}\n          ellipsizeMode='tail'\n        >\n          {messageText}\n        </Text>\n      );\n    } else if (messageText && typeof messageText !== 'string') {\n      // JSX element from rich text formatter — wrap with truncation\n      messageText = (\n        <Text\n          style={[mergedStyles.itemStyle.subtitleStyle, { flexShrink: 2 }]}\n          numberOfLines={1}\n          ellipsizeMode='tail'\n        >\n          {messageText}\n        </Text>\n      );\n    }\n\n    let groupText = \"\";\n    if (!(lastMessage instanceof CometChat.Action)) {\n      if (lastMessage.getReceiverType() == ReceiverTypeConstants.group) {\n        if (lastMessage.getSender().getUid() == loggedInUserId) {\n          groupText = t(\"YOU\") + \": \";\n        } else {\n          groupText = lastMessage.getSender().getName() + \": \";\n        }\n      }\n    }\n\n    return (\n      <>\n        {groupText && (\n          <Text\n            style={[mergedStyles.itemStyle.subtitleStyle, { flexShrink: 0, maxWidth: '40%' }]}\n            numberOfLines={1}\n            ellipsizeMode='tail'\n          >\n            {groupText}\n          </Text>\n        )}\n        {messageText}\n      </>\n    );\n  };\n\n  /**\n   * Applies text formatters to the message text.\n   * @param message - The message object.\n   * @param subtitle - The raw text to format.\n   * @returns The formatted text.\n   */\n  function getFormattedText(message: CometChat.BaseMessage, subtitle: string) {\n    // For text messages, use the rich text formatter to preserve inline styles\n    // (bold, italic, underline, strikethrough, inline code). The subtitle has\n    // already been processed by preparePreviewText which strips block-level\n    // markers, so the formatter will only encounter inline formatting.\n    let messageTextTmp: string | JSX.Element;\n    if (\n      message instanceof CometChat.TextMessage &&\n      message.getCategory() === MessageCategoryConstants.message\n    ) {\n      const formatted = conversationRichTextFormatter.getFormattedText(subtitle);\n      messageTextTmp = formatted ?? subtitle;\n    } else {\n      // Non-text messages: strip markdown as before\n      messageTextTmp = stripMarkdown(subtitle);\n    }\n    let allFormatters = [...(textFormatters || [])];\n\n    // Apply mentions formatting using shared helper (DRY — same as CometChatMessagePreview)\n    messageTextTmp = applyMentionsFormatting(message, messageTextTmp, subtitle, mergedStyles.mentionsStyles);\n\n    if (\n      message instanceof CometChat.TextMessage &&\n      message.getCategory() === MessageCategoryConstants.message &&\n      (() => {\n        const text = typeof message.getText === \"function\" ? message.getText() : undefined;\n        return typeof text === \"string\" && text.slice(0, 50).match(/https?:\\/\\//);\n      })()\n    ) {\n      // For link messages, simply return the text.\n      return messageTextTmp;\n    }\n\n    if (allFormatters && allFormatters.length) {\n      for (let i = 0; i < allFormatters.length; i++) {\n        let suggestionUsers = allFormatters[i].getSuggestionItems();\n        allFormatters[i].setMessage(message);\n        suggestionUsers.length > 0 && allFormatters[i].setSuggestionItems(suggestionUsers);\n        let _formatter = CommonUtils.clone(allFormatters[i]);\n        messageTextTmp = _formatter.getFormattedText(\n          messageTextTmp,\n          mergedStyles.itemStyle.subtitleStyle\n        );\n      }\n    }\n\n    return messageTextTmp;\n  }\n\n  /**\n   * Component to render the last message view for a conversation item.\n   * @param params - Contains conversation and typing indicator text.\n   * @returns A JSX.Element rendering the last message.\n   */\n  const LastMessageView = (params: {\n    conversations: CometChat.Conversation;\n    typingText: string;\n  }) => {\n    const lastMessage = params.conversations.getLastMessage();\n    if (!lastMessage)\n      return (\n        <Text\n          style={[mergedStyles.itemStyle.subtitleStyle]}\n          numberOfLines={1}\n          ellipsizeMode={\"tail\"}\n        >\n          {t(\"TAP_TO_START_CONVERSATION\")}\n        </Text>\n      );\n    let readReceipt;\n    if (params.typingText) {\n      return (\n        <View style={Style.row}>\n          <Text\n            numberOfLines={1}\n            ellipsizeMode={\"tail\"}\n            style={[mergedStyles.typingIndicatorStyle]}\n          >\n            {params.typingText}\n          </Text>\n        </View>\n      );\n    }\n\n    if (\n      lastMessage &&\n      lastMessage.getSender().getUid() == loggedInUser.current!.getUid() &&\n      !lastMessage.getDeletedAt()\n    ) {\n      const status = MessageReceiptUtils.getReceiptStatus(lastMessage);\n      readReceipt =\n        !receiptsVisibility || lastMessage?.getDeletedAt() ? null : (\n          <CometChatReceipt receipt={status as MessageReceipt} style={mergedStyles.itemStyle.receiptStyles} />\n        );\n    }\n\n    let threadView: JSX.Element | null = null;\n\n    if (lastMessage?.getParentMessageId()) {\n      threadView = (\n        <>\n          <Icon\n            name='subdirectory-arrow-right-fill'\n            size={theme.spacing.spacing.s4}\n            color={mergedStyles.itemStyle.subtitleStyle.color}\n          />\n          {/* Optional: Add text for thread indicator */}\n        </>\n      );\n    }\n\n    return (\n      <View style={[Style.row, { gap: 2, alignItems: \"center\" }]}>\n        {threadView}\n        <View style={[Style.row, { gap: 2, alignItems: \"center\" }]}>\n          {![\"call\", \"action\"].includes(params[\"conversations\"].getLastMessage().getCategory())\n            ? readReceipt\n            : null}\n          {getMessagePreview(params[\"conversations\"], theme)}\n        </View>\n      </View>\n    );\n  };\n\n  /**\n   * Returns the trailing view (date and badge) for a conversation item.\n   * @param conversation - The conversation object.\n   * @returns A JSX.Element for the trailing view.\n   */\n  const getTrailingView = useCallback(\n    (conversation: CometChat.Conversation) => {\n      const customPattern = () => datePattern?.(conversation);\n      const timestamp = conversation.getLastMessage()?.getSentAt();\n      if (!timestamp) return <></>;\n      return (\n        <View\n          style={[\n            { marginHorizontal: 6, justifyContent: \"center\", alignItems: \"flex-end\" },\n            mergedStyles.itemStyle.trailingViewContainerStyle,\n          ]}\n        >\n          <CometChatDate\n            timeStamp={timestamp * 1000}\n            customDateString={customPattern && customPattern()}\n            pattern={\"conversationDate\"}\n            style={mergedStyles?.itemStyle?.dateStyle}\n          />\n          <CometChatBadge\n            count={conversation.getUnreadMessageCount()}\n            style={mergedStyles?.itemStyle?.badgeStyle}\n          />\n        </View>\n      );\n    },\n    [mergedStyles, datePattern]\n  );\n\n  /**\n   * Updates the conversation's last message for a group conversation.\n   * @param message - The new message.\n   * @param group - The group the conversation belongs to.\n   */\n  const updateConversationLastMessage = (\n    message: CometChat.BaseMessage,\n    group: CometChat.Group\n  ) => {\n    try {\n      let conversation: CometChat.Conversation = conversationListRef.current?.getListItem(\n        message.getConversationId()\n      );\n      if (conversation) {\n        conversation = CommonUtils.clone(conversation);\n        conversation.setLastMessage(message);\n        conversation.setConversationWith(group);\n        conversationListRef.current?.updateAndMoveToFirst(conversation);\n      } else {\n        CometChat.CometChatHelper.getConversationFromMessage(message)\n          .then((newConversation) => {\n            if (\n              newConversation?.getLastMessage().getSender().getUid() !==\n              loggedInUser.current?.getUid()\n            )\n              newConversation.setUnreadMessageCount(1);\n            conversationListRef.current!.addItemToList(newConversation, 0);\n          })\n          .catch((err) => onError && onError(err));\n      }\n    } catch (error: any) {\n      onError && onError(error);\n    }\n  };\n\n  /**\n   * Increments the unread message count for a conversation.\n   * @param conversation - The conversation to update.\n   * @returns The updated conversation.\n   */\n  const updateUnreadMessageCount = (conversation: CometChat.Conversation) => {\n    const oldConversation: CometChat.Conversation = conversationListRef.current!.getListItem(\n      conversation[\"conversationId\"]\n    ) as unknown as CometChat.Conversation;\n    if (oldConversation == undefined) {\n      conversation.setUnreadMessageCount(1);\n      return conversation;\n    }\n    oldConversation.setUnreadMessageCount(oldConversation.getUnreadMessageCount() + 1);\n    return oldConversation;\n  };\n\n  // Set up event listeners for user, call, group and message events.\n  React.useEffect(() => {\n    // Get logged in user.\n    CometChat.getLoggedinUser()\n      .then((u) => {\n        loggedInUser.current = u!;\n      })\n      .catch((err) => console.log(err));\n\n    // Listen for user online/offline changes.\n    CometChat.addUserListener(\n      userListenerId,\n      new CometChat.UserListener({\n        onUserOnline: (onlineUser: any) => {\n          userEventHandler(onlineUser);\n        },\n        onUserOffline: (offlineUser: any) => {\n          userEventHandler(offlineUser);\n        },\n      })\n    );\n\n    // Listen for call events.\n    CometChat.addCallListener(\n      callListenerId,\n      new CometChat.CallListener({\n        onIncomingCallReceived: (call: CometChat.Call) => {\n          CometChat.CometChatHelper.getConversationFromMessage(call)\n            .then((conversation) => {\n              if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n                return;\n              }\n              conversation = updateUnreadMessageCount(conversation);\n              conversation.setLastMessage(call);\n              conversationListRef.current!.updateList(conversation);\n            })\n            .catch((e) => {\n              onError && onError(e);\n            });\n        },\n        onOutgoingCallAccepted: (call: any) => {\n          CometChat.CometChatHelper.getConversationFromMessage(call)\n            .then((conversation) => {\n              if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n                return;\n              }\n              conversation = updateUnreadMessageCount(conversation);\n              conversation.setLastMessage(call);\n              conversationListRef.current!.updateList(conversation);\n            })\n            .catch((e) => {\n              onError && onError(e);\n            });\n        },\n        onOutgoingCallRejected: (call: any) => {\n          CometChat.CometChatHelper.getConversationFromMessage(call)\n            .then((conversation) => {\n              if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n                return;\n              }\n              conversation = updateUnreadMessageCount(conversation);\n              conversation.setLastMessage(call);\n              conversationListRef.current!.updateList(conversation);\n            })\n            .catch((e) => {\n              onError && onError(e);\n            });\n        },\n        onIncomingCallCancelled: (call: any) => {\n          CometChat.CometChatHelper.getConversationFromMessage(call)\n            .then((conversation) => {\n              if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n                return;\n              }\n              conversation = updateUnreadMessageCount(conversation);\n              conversation.setLastMessage(call);\n              conversationListRef.current!.updateList(conversation);\n            })\n            .catch((e) => {\n              onError && onError(e);\n            });\n        },\n      })\n    );\n\n    // Listen for group events.\n    CometChat.addGroupListener(\n      groupListenerId,\n      new CometChat.GroupListener({\n        onGroupMemberScopeChanged: (\n          message: CometChat.Action,\n          changedUser: CometChat.GroupMember,\n          newScope: CometChat.GroupMemberScope,\n          oldScope: CometChat.GroupMemberScope,\n          changedGroup: CometChat.Group\n        ) => {\n          groupHandler(message, {\n            action: \"scopeChange\",\n            actionOn: changedUser,\n            newScope: newScope,\n            oldScope: oldScope,\n            group: changedGroup,\n          });\n        },\n        onGroupMemberKicked: (\n          message: CometChat.Action,\n          kickedUser: CometChat.User,\n          kickedBy: CometChat.User,\n          kickedFrom: CometChat.Group\n        ) => {\n          groupHandler(message, {\n            action: \"kicked\",\n            actionOn: kickedUser,\n            actionBy: kickedBy,\n            group: kickedFrom,\n          });\n        },\n        onGroupMemberLeft: (\n          message: CometChat.Action,\n          leavingUser: CometChat.User,\n          group: CometChat.Group\n        ) => {\n          groupHandler(message, { action: \"left\", actionOn: leavingUser, group });\n        },\n        onGroupMemberUnbanned: (message: CometChat.Action) => {\n          groupHandler(message);\n        },\n        onGroupMemberBanned: (\n          message: CometChat.Action,\n          bannedUser: CometChat.User,\n          bannedBy: CometChat.User,\n          bannedFrom: CometChat.Group\n        ) => {\n          groupHandler(message, {\n            action: \"banned\",\n            actionOn: bannedUser,\n            actionBy: bannedBy,\n            group: bannedFrom,\n          });\n        },\n        onMemberAddedToGroup: (\n          message: CometChat.Action,\n          userAdded: CometChat.User,\n          userAddedBy: CometChat.User,\n          userAddedIn: CometChat.Group\n        ) => {\n          if (onMemberAddedToGroupDebounceTimer.current) {\n            clearTimeout(onMemberAddedToGroupDebounceTimer.current);\n          }\n          onMemberAddedToGroupDebounceTimer.current = setTimeout(() => {\n            groupHandler(message, {\n              action: \"joined\",\n              actionOn: userAdded,\n              actionBy: userAddedBy,\n              group: userAddedIn,\n            });\n          }, 50);\n        },\n        onGroupMemberJoined: (message: CometChat.Action) => {\n          groupHandler(message);\n        },\n      })\n    );\n\n    // Listen for conversation deletion events.\n    CometChatUIEventHandler.addConversationListener(conversationListenerId, {\n      ccConversationDeleted: ({ conversation }: { conversation: CometChat.Conversation }) => {\n        conversationListRef.current!.removeItemFromList(conversation.getConversationId());\n        removeItemFromSelectionList(conversation.getConversationId());\n      },\n      // Handle conversation updates from external sources (e.g., when conversation properties change)\n      ccUpdateConversation: ({ conversation }: { conversation: CometChat.Conversation }) => {\n        conversationListRef.current!.updateList(conversation);\n      },\n    });\n    // Listen for message events.\n    CometChatUIEventHandler.addMessageListener(messageListenerId, {\n      ccMessageSent: ({ message, status }: any) => {\n        if (status == MessageStatusConstants.success) {\n          if (!shouldUpdateLastMessageAndUnreadCount(message)) {\n            return;\n          }\n          updateLastMessage(message);\n        }\n      },\n      ccMessageRead: ({ message }: { message: CometChat.BaseMessage }) => {\n        checkAndUpdateLastMessage(message);\n      },\n      ccMessageDeleted: ({ message }: { message: CometChat.BaseMessage }) => {\n        checkAndUpdateLastMessage(message);\n      },\n      ccMessageEdited: ({ message }: { message: CometChat.BaseMessage }) => {\n        checkAndUpdateLastMessage(message);\n      },\n      onTextMessageReceived: (textMessage: CometChat.TextMessage) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(textMessage)) {\n          return;\n        }\n        messageEventHandler(textMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n      onMediaMessageReceived: (mediaMessage: CometChat.MediaMessage) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(mediaMessage)) {\n          return;\n        }\n        messageEventHandler(mediaMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n      onCustomMessageReceived: (customMessage: CometChat.CustomMessage) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(customMessage)) {\n          return;\n        }\n        messageEventHandler(customMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n      onMessageDeleted: (deletedMessage: CometChat.BaseMessage) => {\n        checkAndUpdateLastMessage(deletedMessage);\n      },\n      onMessageEdited: (editedMessage: CometChat.BaseMessage) => {\n        checkAndUpdateLastMessage(editedMessage);\n      },\n      onMessagesRead: (messageReceipt: CometChat.MessageReceipt) => {\n        updateMessageReceipt(messageReceipt);\n      },\n      onMessagesDelivered: (messageReceipt: CometChat.MessageReceipt) => {\n        updateMessageReceipt(messageReceipt);\n      },\n      onMessagesDeliveredToAll: (messageReceipt: CometChat.MessageReceipt) => {\n        updateMessageReceipt(messageReceipt);\n      },\n      onMessagesReadByAll: (messageReceipt: CometChat.MessageReceipt) => {\n        updateMessageReceipt(messageReceipt);\n      },\n      onTypingStarted: (typingIndicator: CometChat.TypingIndicator) => {\n        typingEventHandler(typingIndicator, true);\n      },\n      onTypingEnded: (typingIndicator: CometChat.TypingIndicator) => {\n        typingEventHandler(typingIndicator, false);\n      },\n      onFormMessageReceived: (formMessage: any) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(formMessage)) {\n          return;\n        }\n        messageEventHandler(formMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n      onCardMessageReceived: (cardMessage: any) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(cardMessage)) {\n          return;\n        }\n        messageEventHandler(cardMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n      onSchedulerMessageReceived: (schedulerMessage: any) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(schedulerMessage)) {\n          return;\n        }\n        messageEventHandler(schedulerMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n      onCustomInteractiveMessageReceived: (customInteractiveMessage: any) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(customInteractiveMessage)) {\n          return;\n        }\n        messageEventHandler(customInteractiveMessage);\n        !disableSoundForMessages && CometChatSoundManager.play(\"incomingMessage\");\n      },\n    });\n    // Listen for additional group events.\n    CometChatUIEventHandler.addGroupListener(groupListenerId, {\n      ccGroupCreated: ({ group }: { group: CometChat.Group }) => {\n        CometChat.getConversation(\n          group.getGuid(),\n          CometChatUiKitConstants.ConversationTypeConstants.group\n        ).then((conversation) => {\n          conversationListRef.current?.addItemToList(conversation, 0);\n        });\n      },\n      ccGroupDeleted: ({ group }: { group: CometChat.Group }) => {\n        CometChat.getConversation(\n          group.getGuid(),\n          CometChatUiKitConstants.ConversationTypeConstants.group\n        ).then((conversation) => {\n          conversationListRef.current?.removeItemFromList(conversation.getConversationId());\n          removeItemFromSelectionList(conversation.getConversationId());\n        });\n      },\n      ccGroupLeft: ({ leftGroup }: { leftGroup: CometChat.Group }) => {\n        const foundConversation = conversationListRef.current?.getAllListItems().find((conv) => {\n          const convWith = conv.getConversationWith();\n          return convWith instanceof CometChat.Group && convWith.getGuid() === leftGroup.getGuid();\n        });\n        if (foundConversation) {\n          conversationListRef.current?.removeItemFromList(foundConversation.getConversationId());\n          removeItemFromSelectionList(foundConversation.getConversationId());\n        }\n      },\n      ccGroupMemberKicked: ({\n        message,\n        kickedFrom,\n      }: {\n        message: CometChat.Action;\n        kickedFrom: CometChat.Group;\n      }) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(message)) {\n          return;\n        }\n        updateConversationLastMessage(message, kickedFrom);\n      },\n      ccGroupMemberBanned: ({ message }: { message: CometChat.Action }) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(message)) {\n          return;\n        }\n        groupHandler(message);\n      },\n      ccGroupMemberUnBanned: ({ message }: { message: CometChat.Action }) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(message)) {\n          return;\n        }\n        groupHandler(message);\n      },\n      ccOwnershipChanged: ({ message }: { message: CometChat.Action }) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(message)) {\n          return;\n        }\n        CometChat.CometChatHelper.getConversationFromMessage(message)\n          .then((conversation) => {\n            conversationListRef.current?.updateList(conversation);\n          })\n          .catch((e) => {\n            onError && onError(e);\n          });\n      },\n      ccGroupMemberAdded: ({\n        message,\n        userAddedIn,\n      }: {\n        message: CometChat.Action;\n        userAddedIn: CometChat.Group;\n      }) => {\n        if (!shouldUpdateLastMessageAndUnreadCount(message)) {\n          return;\n        }\n        updateConversationLastMessage(message, userAddedIn);\n      },\n    });\n\n    // Listen for user block events.\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: ({ user }: { user: CometChat.User }) => {\n        const uid = user.getUid();\n        const loggedInUid = loggedInUser.current?.getUid();\n        if (!loggedInUid) return;\n\n        const candidateIds = [`${uid}_user_${loggedInUid}`, `${loggedInUid}_user_${uid}`];\n\n        const item: CometChat.Conversation | undefined = candidateIds\n          .map((id) => conversationListRef.current?.getListItem(id))\n          .find(Boolean);\n\n        if (!item) return;\n\n        if (\n          conversationsRequestBuilder &&\n          conversationsRequestBuilder.build().isIncludeBlockedUsers()\n        ) {\n          const updatedConversation = CommonUtils.clone(item);\n          updatedConversation.setConversationWith(user);\n          conversationListRef.current?.updateList(updatedConversation);\n          return;\n        }\n\n        conversationListRef?.current?.removeItemFromList(item.getConversationId());\n        removeItemFromSelectionList(item.getConversationId());\n      },\n      ccUserUnBlocked: ({ user }: { user: CometChat.User }) => {\n        /**unblocked handling is required to enable user presence listener for the user**/\n        const uid = user.getUid();\n        let item: CometChat.Conversation | any =\n          (conversationListRef.current?.getListItem(\n            `${uid}_user_${loggedInUser.current?.getUid()}`\n          ) as unknown as CometChat.Conversation) ||\n          (conversationListRef.current?.getListItem(\n            `${loggedInUser.current?.getUid()}_user_${uid}`\n          ) as unknown as CometChat.Conversation);\n        if (item) {\n          let updatedConversation = CommonUtils.clone(item);\n          updatedConversation.setConversationWith(user);\n          conversationListRef.current?.updateList(updatedConversation);\n        }\n      },\n    });\n\n    // Listen for call events via UI event handler.\n    CometChatUIEventHandler.addCallListener(callListenerId, {\n      ccOutgoingCall: ({ call }: any) => {\n        CometChat.CometChatHelper.getConversationFromMessage(call)\n          .then((conversation) => {\n            if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n              return;\n            }\n            conversation = updateUnreadMessageCount(conversation);\n            conversationListRef.current!.updateList(conversation);\n          })\n          .catch((e) => {\n            onError && onError(e);\n          });\n      },\n      ccCallAccepted: ({ call }: any) => {\n        CometChat.CometChatHelper.getConversationFromMessage(call)\n          .then((conversation) => {\n            if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n              return;\n            }\n            conversation = updateUnreadMessageCount(conversation);\n            conversationListRef.current!.updateList(conversation);\n          })\n          .catch((e) => {\n            onError && onError(e);\n          });\n      },\n      ccCallRejected: ({ call }: any) => {\n        CometChat.CometChatHelper.getConversationFromMessage(call)\n          .then((conversation) => {\n            if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n              return;\n            }\n            conversation = updateUnreadMessageCount(conversation);\n            conversationListRef.current!.updateList(conversation);\n          })\n          .catch((e) => {\n            onError && onError(e);\n          });\n      },\n      ccCallEnded: ({ call }: any) => {\n        CometChat.CometChatHelper.getConversationFromMessage(call)\n          .then((conversation) => {\n            if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) {\n              return;\n            }\n            conversation = updateUnreadMessageCount(conversation);\n            conversationListRef.current!.updateList(conversation);\n          })\n          .catch((e) => {\n            onError && onError(e);\n          });\n      },\n    });\n\n    // Cleanup all listeners on unmount.\n    return () => {\n      CometChat.removeUserListener(userListenerId);\n      CometChat.removeCallListener(callListenerId);\n      CometChat.removeGroupListener(groupListenerId);\n      CometChatUIEventHandler.removeMessageListener(messageListenerId);\n      CometChatUIEventHandler.removeConversationListener(conversationListenerId);\n      CometChatUIEventHandler.removeGroupListener(groupListenerId);\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n    };\n  }, []);\n\n  const getStatusIndicator = (conv: CometChat.Conversation) => {\n    const withObj = conv.getConversationWith();\n\n    if (groupTypeVisibility) {\n      if (withObj instanceof CometChat.Group) {\n        if (withObj.getType() === GroupTypeConstants.password) return \"password\";\n        if (withObj.getType() === GroupTypeConstants.private) return \"private\";\n      }\n    } else {\n      return undefined;\n    }\n\n    if (usersStatusVisibility) {\n      if (\n        withObj instanceof CometChat.User &&\n        withObj.getStatus() === CometChatUiKitConstants.UserStatusConstants.online &&\n        !withObj.getHasBlockedMe() &&\n        !withObj.getBlockedByMe()\n      ) {\n        return \"online\";\n      }\n      return \"offline\";\n    } else {\n      return undefined;\n    }\n  };\n\n  const LeadingViewRaw = useCallback(\n    (conv: CometChat.Conversation) => {\n      const withObj = conv.getConversationWith();\n      const avatarURL = withObj instanceof CometChat.User ? withObj.getAvatar() : withObj.getIcon();\n      const name = withObj.getName();\n      \n      return (\n        <>\n          <CometChatAvatar\n            image={{ uri: avatarURL }}\n            name={name}\n            style={mergedStyles.itemStyle.avatarStyle}\n          />\n          <CometChatStatusIndicator\n            type={getStatusIndicator(conv)}\n            style={mergedStyles?.itemStyle?.statusIndicatorStyle}\n          />\n        </>\n      );\n    },\n    [mergedStyles]\n  );\n\n  const TitleViewRaw = useCallback(\n    (conv: CometChat.Conversation) => {\n      const conversationWith = conv.getConversationWith();\n      const isAgentic = conversationWith instanceof CometChat.User && conversationWith.getRole() === \"@agentic\";\n\n      return (\n        <View style={[\n          isAgentic ? { justifyContent: 'center' } : null\n        ]}>\n          <Text\n            numberOfLines={1}\n            ellipsizeMode='tail'\n            style={mergedStyles.itemStyle.titleStyle}\n          >\n            {conversationWith.getName()}\n          </Text>\n        </View>\n      );\n    },\n    [mergedStyles]\n  );\n\n  const SubtitleViewRaw = (conv: CometChat.Conversation) => {\n    const conversationWith = conv.getConversationWith();\n    if (conversationWith instanceof CometChat.User && conversationWith.getRole() === \"@agentic\") {\n      return <></>;\n    }\n    return (\n      <LastMessageView conversations={conv} typingText={conv?.[\"lastMessage\"]?.[\"typing\"]} />\n    );\n  };\n\n  const TrailingViewRaw = useCallback((conv: CometChat.Conversation) => {\n    const conversationWith = conv.getConversationWith();\n    if (conversationWith instanceof CometChat.User && conversationWith.getRole() === \"@agentic\") {\n      return <></>;\n    }\n    return getTrailingView(conv);\n  }, [getTrailingView]);\n\n  return (\n    <View style={mergedStyles.containerStyle}>\n      <CometChatList\n            AppBarOptions={AppBarOptions}\n            onError={onError}\n            ref={conversationListRef}\n            LeadingView={LeadingView ? LeadingView : LeadingViewRaw}\n            TitleView={TitleView ? TitleView : TitleViewRaw}\n            SubtitleView={SubtitleView ? SubtitleView : SubtitleViewRaw}\n            TrailingView={TrailingView ? TrailingView : TrailingViewRaw}\n            requestBuilder={\n              conversationsRequestBuilder || new CometChat.ConversationsRequestBuilder().setLimit(30)\n            }\n            hideStickyHeader={true}\n            title={!hideHeader ? t(\"CHATS\") : \"\"}\n            listStyle={mergedStyles}\n            hideSearch={!showSearchBar}\n            hideHeader={hideHeader}\n            hideSubmitButton={hideSubmitButton}\n            SearchView={SearchView}\n            onSearchBarClicked={onSearchBarClicked}\n            onItemPress={(conversation) =>\n              selectionMode === \"none\" ? conversationClicked(conversation) : null\n            }\n            onItemLongPress={(conversation: CometChat.Conversation, e?: GestureResponderEvent) => {\n              if (selectionMode === \"none\") {\n                if (onItemLongPress) {\n                  onItemLongPress(conversation);\n                  return;\n                }\n                if (e && \"nativeEvent\" in e) {\n                  longPressId.current = conversation.getConversationId();\n                  longPressedConversation.current = conversation;\n                  tooltipPositon.current = {\n                    pageX: e.nativeEvent.pageX,\n                    pageY: e.nativeEvent.pageY,\n                  };\n                  setTooltipVisible(true);\n                }\n              }\n            }}\n            listItemKey={\"conversationId\"}\n            LoadingView={LoadingView ?? (() => <Skeleton style={mergedStyles.skeletonStyle} />)}\n            ItemView={ItemView}\n            EmptyView={EmptyView ? EmptyView : () => <EmptyStateView />}\n            ErrorView={ErrorView ? ErrorView : () => <ErrorStateView />}\n            onBack={onBack}\n            hideBackButton={hideBackButton}\n            onSelection={onSelection}\n            onSubmit={onSubmit}\n            selectionMode={selectionMode}\n            hideError={hideError}\n            onListFetched={(conversations: CometChat.Conversation[]) => {\n              if (conversations.length === 0) {\n                onEmpty?.();\n              } else {\n                onLoad?.(conversations);\n              }\n            }}\n          />\n      <CometChatTooltipMenu\n        visible={tooltipVisible}\n        onClose={() => {\n          setTooltipVisible(false);\n        }}\n        event={{\n          nativeEvent: tooltipPositon.current,\n        }}\n        menuItems={\n          options\n            ? options(longPressedConversation.current!)\n            : [\n                ...[\n                  ...(deleteConversationOptionVisibility\n                    ? [\n                        {\n                          text: \"Delete\",\n                          onPress: () => {\n                            setConfirmDelete(longPressId.current);\n                            setTooltipVisible(false);\n                          },\n                          icon: (\n                            <Delete\n                              color={theme.color.error}\n                              height={theme.spacing.spacing.s6}\n                              width={theme.spacing.spacing.s6}\n                            />\n                          ),\n                          textStyle: { color: theme.color.error },\n                        },\n                      ]\n                    : []),\n                ],\n                ...(addOptions ? addOptions(longPressedConversation.current!) : []),\n              ]\n        }\n      />\n      <CometChatConfirmDialog\n        titleText={t(\"DELETE_THIS_CONVERSATION\")}\n        icon={<Icon name='delete' size={theme.spacing.spacing.s12} color={theme.color.error} />}\n        cancelButtonText={t(\"CANCEL\")}\n        confirmButtonText={t(\"DELETE\")}\n        messageText={t(\"SURE_TO_DELETE_CHAT\")}\n        isOpen={confirmDelete != undefined}\n        onCancel={() => setConfirmDelete(undefined)}\n        onConfirm={() => {\n          removeConversation(confirmDelete!);\n          setConfirmDelete(undefined);\n        }}\n        {...mergedStyles.confirmDialogStyle}\n      />\n    </View>\n  );\n};\n\n// Static styles for conversation preview block-level elements (code block, blockquote, list).\n// Theme-dependent values (colors) are applied inline via style array merging.\nconst conversationPreviewStyles = StyleSheet.create({\n  codeBlockRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    flexShrink: 2,\n  },\n  codeBlockBadge: {\n    borderRadius: 4,\n    borderWidth: 1,\n    paddingHorizontal: 6,\n    paddingVertical: 2,\n    flexShrink: 1,\n  },\n  codeBlockText: {\n    fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',\n    fontSize: 11,\n  },\n  blockquoteRow: {\n    flexDirection: 'row',\n    alignItems: 'stretch',\n    backgroundColor: 'rgba(104, 81, 214, 0.08)',\n    borderRadius: 6,\n    flexShrink: 2,\n    minHeight: 22,\n    paddingVertical: 1,\n  },\n  blockquoteBar: {\n    width: 3,\n    borderRadius: 1.5,\n    marginVertical: 3,\n    marginLeft: 4,\n  },\n  blockquoteText: {\n    flex: 1,\n    paddingHorizontal: 5,\n    lineHeight: 18,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatConversations/Skeleton.tsx",
    "content": "/**\n * Skeleton.tsx\n *\n * A lightweight, theme-aware shimmer “skeleton” placeholder for\n * CometChat conversation lists.\n * ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––\n *  • 20 rows (top + bottom layers) drawn with react-native-svg.\n *  • Two Animated views slide across on a loop to create\n *    the shimmer illusion.\n *  • Colours, speed, opacity and gradient come from the active\n *    CometChatTheme but can be overridden per-instance via `style`.\n */\n\nimport React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, G, LinearGradient, Path, Stop } from \"react-native-svg\";\n\nimport { useTheme } from \"../theme\";\nimport { CometChatTheme } from \"../theme/type\";\n\n// ───────────────────────────────────────────────────────────────\n// Consts & Types\n// ───────────────────────────────────────────────────────────────\nconst { width: screenWidth } = Dimensions.get(\"window\");\nconst SKELETON_ROW_COUNT = 20; // How many rows to render\n\ntype SkeletonStyle = CometChatTheme[\"conversationStyles\"][\"skeletonStyle\"];\n\ninterface SkeletonProps {\n  style?: SkeletonStyle; // Optional per-instance overrides\n}\n\n// ───────────────────────────────────────────────────────────────\n// Reusable SVG snippets\n// ───────────────────────────────────────────────────────────────\n\n/**\n * Bottom SVG layer – pure grey rectangles that will be *under* the shimmer.\n */\nconst SkeletonItemBottom: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n\n  return (\n    <Svg\n      height={screenWidth / 5}\n      viewBox='0 0 360 72'\n      fill='none'\n      preserveAspectRatio='xMidYMid meet'\n    >\n      <G>\n        <Path d='M64 12H16V60H64V12Z' fill='url(#paint0_linear)' />\n      </G>\n      <Path d='M236 16.5H76V35.5H236V16.5Z' fill='url(#paint0_linear)' />\n      <Path d='M344 16.5H284V35.5H344V16.5Z' fill='url(#paint0_linear)' />\n      <Path d='M344 43.5H76V55.5H344V43.5Z' fill='url(#paint0_linear)' />\n\n      {/* Gradient definition */}\n      <Defs>\n        <LinearGradient\n          id='paint0_linear'\n          x1={16}\n          y1={36}\n          x2={64}\n          y2={36}\n          gradientUnits='userSpaceOnUse'\n        >\n          <Stop\n            stopColor={\n              style?.linearGradientColors?.[0] ??\n              theme.conversationStyles.skeletonStyle.linearGradientColors[0]\n            }\n          />\n          <Stop\n            offset={1}\n            stopColor={\n              style?.linearGradientColors?.[1] ??\n              theme.conversationStyles.skeletonStyle.linearGradientColors[1]\n            }\n          />\n        </LinearGradient>\n      </Defs>\n    </Svg>\n  );\n};\n\n/**\n * Top SVG “cookie-cutter” layer – punched out shapes that mask out the\n * shimmer where content will eventually appear.\n */\nconst SkeletonItemTop: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n\n  return (\n    <Svg height={screenWidth / 5} viewBox='0 0 360 72' fill='none'>\n      {/* The giant path draws avatar circle + text bars */}\n      <Path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M0 0H360V72H0V0ZM16 36C16 22.7452 26.7452 12 40 12C53.2548 12 64 22.7452 64 36C64 49.2548 53.2548 60 40 60C26.7452 60 16 49.2548 16 36ZM84 16.5C79.5817 16.5 76 20.0817 76 24.5V27.5C76 31.9183 79.5817 35.5 84 35.5H228C232.418 35.5 236 31.9183 236 27.5V24.5C236 20.0817 232.418 16.5 228 16.5H84ZM284 24.5C284 20.0817 287.582 16.5 292 16.5H336C340.418 16.5 344 20.0817 344 24.5V27.5C344 31.9183 340.418 35.5 336 35.5H292C287.582 35.5 284 31.9183 284 27.5V24.5ZM82 43.5C78.6863 43.5 76 46.1863 76 49.5C76 52.8137 78.6863 55.5 82 55.5H338C341.314 55.5 344 52.8137 344 49.5C344 46.1863 341.314 43.5 338 43.5H82Z'\n        fill={\n          /* Mask must match background so the shimmer underneath shows */\n          style?.containerBackgroundColor ||\n          theme.conversationStyles.skeletonStyle.containerBackgroundColor\n        }\n      />\n    </Svg>\n  );\n};\n\n// ───────────────────────────────────────────────────────────────\n// Main Component\n// ───────────────────────────────────────────────────────────────\nexport const Skeleton: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  /* Kick-off the shimmer animation once on mount */\n  useEffect(() => {\n    animatedValue.setValue(0);\n    Animated.loop(\n      Animated.timing(animatedValue, {\n        toValue: 1,\n        duration: (1 / (style?.speed || theme.conversationStyles.skeletonStyle.speed)) * 1000,\n        easing: Easing.linear,\n        useNativeDriver: false, // translateX is layout-related\n      })\n    ).start();\n  }, [animatedValue, style?.speed || theme.conversationStyles.skeletonStyle.speed]);\n\n  /* Map 0-1 → off-screen-left → off-screen-right */\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth * 2, screenWidth],\n  });\n\n  /* Render ----------------------------------------------------- */\n  return (\n    <ScrollView showsVerticalScrollIndicator={false} scrollEnabled={false}>\n      {/* Bottom layer rectangles */}\n      {Array.from({ length: SKELETON_ROW_COUNT }, (_, i) => (\n        <SkeletonItemBottom style={style} key={`bottom-${i}`} />\n      ))}\n\n      {/* Two angled shimmer bars */}\n      {[0, screenWidth / 2].map((offset, i) => (\n        <Animated.View\n          key={`shimmer-${i}`}\n          style={[\n            styles.animatedView,\n            {\n              transform: [\n                { translateX: Animated.add(shimmerTranslateX, offset) },\n                { translateY: -20 },\n                { rotate: \"15deg\" },\n              ],\n              backgroundColor:\n                style?.shimmerBackgroundColor ??\n                theme.conversationStyles.skeletonStyle.shimmerBackgroundColor,\n              opacity:\n                style?.shimmerOpacity ?? theme.conversationStyles.skeletonStyle.shimmerOpacity,\n            },\n          ]}\n        />\n      ))}\n\n      {/* Top mask layer – sits above shimmer to carve out shapes */}\n      <View style={StyleSheet.absoluteFill}>\n        {Array.from({ length: SKELETON_ROW_COUNT }, (_, i) => (\n          <SkeletonItemTop style={style} key={`top-${i}`} />\n        ))}\n      </View>\n    </ScrollView>\n  );\n};\n\n// ───────────────────────────────────────────────────────────────\n// Styles\n// ───────────────────────────────────────────────────────────────\nconst styles = StyleSheet.create({\n  animatedView: {\n    width: \"25%\", // narrow bar looks nicer at an angle\n    top: 0,\n    bottom: 0,\n    position: \"absolute\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatConversations/index.ts",
    "content": "export { CometChatConversations } from \"./CometChatConversations\";\n\nexport { Skeleton } from \"./Skeleton\";\n\nexport { getConversationStyleLight, getConversationStyleDark } from \"./style\";\n\nexport type {\n    ConversationInterface\n} from \"./CometChatConversations\";"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatConversations/resources/index.ts",
    "content": "import backIcon from \"./back.png\";\nimport readIcon from \"./blue-double-tick-icon.png\";\nimport closeIcon from \"./close.png\";\nimport deleteIcon from \"./delete.png\";\nimport errorIcon from \"./error.png\";\nimport deliveredIcon from \"./grey-double-tick-icon.png\";\nimport sentIcon from \"./grey-tick-icon.png\";\nimport newIcon from \"./new.png\";\nimport passwordGroupIcon from \"./password.png\";\nimport privateGroupIcon from \"./private.png\";\nimport rightTickIcon from \"./rightTick.png\";\nimport waitingIcon from \"./sending.png\";\nimport spineerIcon from \"./spineer.png\";\n\nexport {\n  backIcon,\n  closeIcon,\n  deleteIcon,\n  deliveredIcon,\n  errorIcon,\n  newIcon,\n  passwordGroupIcon,\n  privateGroupIcon,\n  readIcon,\n  rightTickIcon,\n  sentIcon,\n  spineerIcon,\n  waitingIcon,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatConversations/style.ts",
    "content": "import {\n  ColorValue,\n  ImageSourcePropType,\n  ImageStyle,\n  StyleSheet,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { AvatarStyle, getAvatarStyle } from \"../shared/views/CometChatAvatar\";\nimport { BadgeStyle } from \"../shared/views/CometChatBadge\";\nimport { DateStyle } from \"../shared/views/CometChatDate\";\nimport { ReceiptStyles } from \"../shared/views/CometChatReceipt\";\nimport { StatusIndicatorStyles } from \"../shared/views/CometChatStatusIndicator\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { CometChatListStylesInterface } from \"../shared\";\nimport { ConfirmDialogStyle } from \"../shared/views/CometChatConfirmDialog/style\";\nimport { JSX } from \"react\";\n\nexport const Style = StyleSheet.create({\n  listContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    backgroundColor: \"green\",\n  },\n  errorEmptyStateContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    paddingHorizontal: \"10%\",\n    flexDirection: \"column\",\n  },\n  row: {\n    flex: 1,\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n});\n\nexport type ConversationStyle = Omit<\n  CometChatListStylesInterface,\n  \"searchStyle\" | \"sectionHeaderTextStyle\"\n> & {\n  containerStyle: ViewStyle;\n  statusIndicatorStyles?: StatusIndicatorStyles;\n  typingIndicatorStyle: TextStyle;\n  titleStyle: TextStyle;\n  selectionIconStyle: ImageStyle;\n  emptyStateStyle: {\n    titleStyle: TextStyle;\n    subTitleStyle: TextStyle;\n    containerStyle: ViewStyle;\n    icon: ImageSourcePropType | JSX.Element;\n  };\n  errorStateStyle: {\n    titleStyle: TextStyle;\n    subTitleStyle: TextStyle;\n    containerStyle: ViewStyle;\n    icon: ImageSourcePropType | JSX.Element;\n  };\n  backButtonIcon?: ImageSourcePropType | JSX.Element;\n  backButtonIconStyle: ImageStyle;\n  itemStyle: {\n    avatarStyle: AvatarStyle;\n    containerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    statusIndicatorStyle: Partial<StatusIndicatorStyles>;\n    badgeStyle: Partial<BadgeStyle>;\n    receiptStyles: Partial<ReceiptStyles>;\n    dateStyle: Partial<DateStyle>;\n  };\n  skeletonStyle: {\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n    containerBackgroundColor: ColorValue;\n  };\n  mentionsStyles: CometChatTheme[\"mentionsStyle\"];\n  headerContainerStyle?: ViewStyle;\n  confirmDialogStyle: DeepPartial<ConfirmDialogStyle>;\n};\n\nexport const getConversationStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<ConversationStyle> => {\n  return deepMerge(\n    {\n      containerStyle: {\n        backgroundColor: color.background2,\n        flex: 1,\n      },\n      titleStyle: { color: color.textPrimary, ...typography.heading1.bold },\n      typingIndicatorStyle: {\n        color: color.textHighlight,\n        ...typography.body.regular,\n      },\n      errorStateStyle: {\n        containerStyle: {\n          flex: 1,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          paddingHorizontal: \"10%\",\n          flexDirection: \"column\",\n        },\n        titleStyle: {\n          color: color.textPrimary,\n          ...typography.heading3.bold,\n          marginBottom: spacing.margin.m1,\n        },\n        subTitleStyle: {\n          color: color.textSecondary,\n          textAlign: \"center\",\n          ...typography.body.regular,\n        },\n      },\n      emptyStateStyle: {\n        containerStyle: {\n          flex: 1,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          paddingHorizontal: \"10%\",\n          flexDirection: \"column\",\n        },\n        titleStyle: {\n          color: color.textPrimary,\n          ...typography.heading3.bold,\n          marginBottom: spacing.margin.m1,\n        },\n        subTitleStyle: {\n          color: color.textSecondary,\n          textAlign: \"center\",\n          ...typography.body.regular,\n        },\n      },\n      backButtonIconStyle: {\n        tintColor: color.iconPrimary,\n        height: spacing.spacing.s6,\n        width: spacing.spacing.s6,\n      },\n      headerContainerStyle: {\n        borderBottomColor: color.borderDefault,\n        borderBottomWidth: 1,\n        alignItems: \"flex-start\",\n        justifyContent: \"center\",\n        width: \"100%\",\n        borderRadius: 0,\n      },\n    } as const,\n    {\n      itemStyle: {\n        containerStyle: {\n          flexDirection: \"row\",\n          paddingHorizontal: spacing.padding.p2,\n          paddingVertical: spacing.padding.p3,\n        },\n        titleStyle: {\n          color: color.textPrimary,\n          ...typography.heading4.medium,\n        },\n        subtitleStyle: {\n          color: color.textSecondary,\n          ...typography.body.regular,\n        },\n        avatarStyle: getAvatarStyle(color, spacing, typography),\n        trailingViewContainerStyle: {\n          gap: spacing.padding.p1,\n        },\n        titleSubtitleContainerStyle: {},\n        badgeStyle: {\n          containerStyle: {\n            backgroundColor: color.primary,\n            borderRadius: spacing.radius.max,\n            minWidth: spacing.spacing.s5,\n            maxWidth: spacing.spacing.s12,\n            height: spacing.spacing.s5,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: spacing.spacing.s1,\n            paddingVertical: spacing.spacing.s0_5,\n            alignSelf: \"flex-end\",\n          },\n        },\n      },\n      skeletonStyle: {\n        linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n        shimmerBackgroundColor: color.staticBlack,\n        shimmerOpacity: 0.01,\n        speed: 1,\n        containerBackgroundColor: color.background2,\n      },\n      statusIndicatorStyles: {},\n      dateStyle: {},\n      receiptStyles: {},\n      mentionsStyles: {\n        textStyle: {\n          ...typography.body.bold,\n          color: color.receiveBubbleTextHighlight,\n        },\n        selfTextStyle: {\n          ...typography.body.bold,\n          color: color.warning,\n        },\n      },\n      backButtonIconContainerStyle: {\n        paddingRight: spacing.spacing.s1,\n      },\n    } as const\n  );\n};\n\nexport const getConversationStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<ConversationStyle> => {\n  return deepMerge(getConversationStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n      containerBackgroundColor: color.background2,\n    },\n    mentionsStyles: {\n      textStyle: {\n        ...typography.body.bold,\n        color: color.receiveBubbleTextHighlight,\n      },\n      selfTextStyle: {\n        ...typography.body.bold,\n        color: color.warning,\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroupMembers/CometChatGroupMembers.tsx",
    "content": "import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n  ColorValue,\n  ImageSourcePropType,\n  Modal,\n  Platform,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  CometChatBottomSheet,\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIKit,\n} from \"../shared\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatList, CometChatListProps } from \"../shared\";\nimport { MessageTypeConstants } from \"../shared/constants/UIKitConstants\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport {\n  getUnixTimestamp,\n  getUnixTimestampInMilliseconds,\n} from \"../shared/utils/CometChatMessageHelper\";\nimport { CometChatStatusIndicatorInterface } from \"../shared/views/CometChatStatusIndicator\";\nimport { CometChatTooltipMenu } from \"../shared/views/CometChatTooltipMenu\";\nimport { ErrorEmptyView } from \"../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { useTheme } from \"../theme\";\nimport { Skeleton } from \"./Skeleton\";\nimport { GroupMemberStyle } from \"./style\";\nimport { MenuItemInterface } from \"../shared/views/CometChatTooltipMenu/CometChatTooltipMenu\";\nimport ChangeCircle from \"../shared/icons/components/change-circle\";\nimport Block from \"../shared/icons/components/block\";\nimport Cancel from \"../shared/icons/components/cancel\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatGroupMembers component.\n */\nexport interface CometChatGroupMembersInterface\n  extends Omit<\n    CometChatListProps,\n    | \"requestBuilder\"\n    | \"listItemKey\"\n    | \"title\"\n    | \"statusIndicatorStyle\"\n    | \"avatarStyle\"\n    | \"listItemStyle\"\n    | \"listStyle\"\n    | \"ListItemView\"\n    | \"searchRequestBuilder\"\n    | \"onSelection\"\n    | \"disableUsersPresence\"\n    | \"errorStateText\"\n    | \"emptyStateText\"\n    | \"onListFetched\"\n    | \"hideBackButton\"\n    | \"statusIndicatorType\"\n    | \"hideStickyHeader\"\n  > {\n  /**\n   * Custom view for subtitle.\n   * @param item - Object of CometChat.GroupMember.\n   * @returns JSX.Element.\n   */\n  SubtitleView?: (item: CometChat.GroupMember) => JSX.Element;\n\n  TitleView?: (item: CometChat.GroupMember) => JSX.Element;\n  /**\n   * Custom tail view.\n   * @param item - Object of CometChat.GroupMember.\n   * @returns JSX.Element.\n   */\n  TrailingView?: (item: CometChat.GroupMember) => JSX.Element;\n  /**\n   * Custom view for empty state.\n   * @returns JSX.Element.\n   */\n  EmptyView?: () => JSX.Element;\n  /**\n   * Custom view for error state.\n   * @returns JSX.Element.\n   */\n  ErrorView?: () => JSX.Element;\n  /**\n   * Custom view for loading state.\n   * @returns JSX.Element.\n   */\n  LoadingView?: () => JSX.Element;\n  /**\n   * Callback for press on ListItem.\n   * @param groupMember - Object of CometChat.GroupMember.\n   * @returns void.\n   */\n  onItemPress?: (groupMember: CometChat.GroupMember) => void;\n  /**\n   * Callback for long press on ListItem.\n   * @param groupMember - Object of CometChat.GroupMember.\n   * @returns void.\n   */\n  onItemLongPress?: (groupMember: CometChat.GroupMember) => void;\n  /**\n   * Callback for on selection of group members.\n   * @param list - Array of selected GroupMembers.\n   * @returns void.\n   */\n  onSelection?: (list: CometChat.GroupMember[]) => void;\n  /**\n   * Callback when submit selection button is pressed.\n   */\n  onSubmit?: (list: Array<CometChat.Conversation>) => void;\n  /**\n   * Pass search request builder object.\n   */\n  searchRequestBuilder?: CometChat.GroupMembersRequestBuilder;\n  /**\n   * Pass group member request builder object.\n   */\n  groupMemberRequestBuilder?: CometChat.GroupMembersRequestBuilder;\n  /**\n   * Pass CometChat SDK's group object.\n   */\n  group: CometChat.Group;\n  /**\n   * Style for group member.\n   */\n  style?: DeepPartial<GroupMemberStyle>;\n  /**\n   * Custom Item view.\n   */\n  ItemView?: (item: CometChat.GroupMember) => JSX.Element;\n  /**\n   * Custom ListItem view.\n   */\n  LeadingView?: (item: CometChat.GroupMember) => JSX.Element;\n  /**\n   * Callback triggered when the group members list is empty.\n   */\n  onEmpty?: () => void;\n  /**\n   * Callback triggered once the group members have loaded (i.e., the fetched list is not empty).\n   * Receives the array of group members.\n   */\n  onLoad?: (list: CometChat.GroupMember[]) => void;\n  /**\n   * A function to **replace** the default menu items entirely for a group member.d\n   * @param member    - The group member object\n   * @param group     - The group object\n   * @returns An array of menu items (with text, onPress, etc.)\n   */\n  options?: (member: CometChat.GroupMember, group: CometChat.Group) => MenuItemInterface[];\n  /**\n   * A function to **append** more menu items on top of the default menu items for a group member.\n   * @param member    - The group member object\n   * @param group     - The group object\n   * @returns An array of menu items that will be appended to the default list\n   */\n  addOptions?: (member: CometChat.GroupMember, group: CometChat.Group) => MenuItemInterface[];\n  /**\n   * Hide the \"Remove\" (Kick) option from the default menu.\n   */\n  hideKickMemberOption?: boolean;\n  /**\n   * Hide the \"Ban\" option from the default menu.\n   */\n  hideBanMemberOption?: boolean;\n  /**\n   * Hide the \"Change Scope\" option from the default menu.\n   */\n  hideScopeChangeOption?: boolean;\n  /**\n   * Hide the loading skeleton.\n   */\n  hideLoadingState?: boolean;\n  /**\n   * Hide the users Status.\n   */\n  usersStatusVisibility?: boolean;\n  /**\n   * Hide the Header.\n   */\n  hideHeader?: boolean;\n  /**\n   * Hide the Submit Button.\n   */\n  onError?: () => void;\n  /**\n   * search Keyword\n   */\n  searchKeyword?: string;\n  /**\n   * visibilty of back button\n   */\n  showBackButton?: boolean;\n  /**\n   * exclude owner from the list.\n   */\n  excludeOwner?: boolean;\n}\n\n/**\n * Component to render and manage the list of group members.\n *\n * This component renders a list of group members using CometChatList and provides functionality\n * to manage member roles (change scope), ban, or remove members through menus and modals.\n *\n * @param props - Props of type CometChatGroupMembersInterface.\n * @returns JSX.Element.\n */\nexport const CometChatGroupMembers = (props: CometChatGroupMembersInterface) => {\n  const {t}= useCometChatTranslation()\n  const {\n    SubtitleView,\n    ItemView,\n    AppBarOptions,\n    searchPlaceholderText = t(\"SEARCH\"),\n    showBackButton = false,\n    onSelection,\n    onSubmit,\n    hideSearch,\n    EmptyView,\n    ErrorView,\n    LoadingView,\n    groupMemberRequestBuilder,\n    searchRequestBuilder,\n    group,\n    hideError,\n    onBack,\n    selectionMode = \"none\",\n    style = {},\n    TrailingView,\n    LeadingView,\n    onEmpty,\n    onLoad,\n    options,\n    addOptions,\n    onError,\n    searchKeyword = \"\",\n    hideKickMemberOption,\n    hideBanMemberOption,\n    hideScopeChangeOption,\n    hideLoadingState,\n    usersStatusVisibility = true,\n    hideHeader = false,\n    excludeOwner,\n    hideSubmitButton = false,\n    ...newProps\n  } = props;\n\n  // Get theme information and merge with provided style overrides.\n  const theme = useTheme();\n  const mergedStyle = deepMerge(theme.groupMemberStyle, style);\n\n  // State management for UI elements\n  const [hideSearchError, setHideSearchError] = useState(false);\n  const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false);\n  const [selectedItem, setSelectedItem] = useState<CometChat.GroupMember | null>(null);\n  const tooltipPositon = React.useRef({ pageX: 0, pageY: 0 });\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const [modalType, setModalType] = useState<\"ban\" | \"kick\" | \"\">(\"\");\n  const modalVisible = modalType !== \"\";\n  const [selectedRole, setSelectedRole] = useState<string>(\"\");\n  const [currentUserRole, setCurrentUserRole] = useState<string | null>(null);\n  const listRef = useRef<any>(null);\n  const [isToolTipDismissed, setIsToolTipDismissed] = useState(false);\n  const loggedInUser = React.useRef(CometChatUIKit.loggedInUser!);\n\n  // Derived: whether selected scope equals current user scope\n  const isScopeUnchanged = React.useMemo(() => {\n    if (!selectedItem) return true;\n    return selectedRole === selectedItem.getScope();\n  }, [selectedItem, selectedRole]);\n\n\n  // Define role permissions for current user actions\n  const RolePermissions: { [key: string]: string[] } = {\n    OWNER: [\"CHANGE_SCOPE\", \"BAN\", \"KICK\"],\n    ADMIN: [\"CHANGE_SCOPE\", \"BAN\", \"KICK\"],\n    MODERATOR: [\"CHANGE_SCOPE\", \"BAN\", \"KICK\"],\n    PARTICIPANT: [],\n  };\n\n  // Determine available roles based on current user's role\n  const roles = React.useMemo(() => {\n    if (currentUserRole === \"moderator\") {\n      return [\n        { display: t(\"MODERATOR\"), value: \"moderator\" },\n        { display: t(\"PARTICIPANT\"), value: \"participant\" }\n      ];\n    }\n    return [\n      { display: t(\"ADMIN\"), value: \"admin\" },\n      { display: t(\"MODERATOR\"), value: \"moderator\" },\n      { display: t(\"PARTICIPANT\"), value: \"participant\" }\n    ];\n  }, [currentUserRole, t]);\n\n  // Set current user role based on whether the logged in user is the owner or not.\n  useEffect(() => {\n    if (CometChatUIKit.loggedInUser!.getUid() === group.getOwner()) {\n      setCurrentUserRole(\"owner\");\n    } else {\n      setCurrentUserRole(group.getScope());\n    }\n  }, [group]);\n\n  /**\n   * Renders an empty state view when no users are available.\n   * Also calls `onEmpty` if provided, so the parent can react to an empty list.\n   */\n  const EmptyStateView = useCallback(() => {\n    // Let parent know it's empty if user provided a callback\n    useEffect(() => {\n      onEmpty?.();\n    }, []);\n\n    return (\n      <View style={{ flex: 1 }}>\n        <ErrorEmptyView\n          title={t(\"NO_USERS_AVAILABLE\")}\n          subTitle={t(\"ADD_CONTACTS\")}\n          Icon={\n            <Icon\n              name='user-empty-icon'\n              icon={mergedStyle?.emptyStateStyle?.icon as ImageSourcePropType | JSX.Element}\n              color={mergedStyle?.emptyStateStyle?.iconStyle?.tintColor}\n              height={mergedStyle?.emptyStateStyle?.iconStyle?.height}\n              width={mergedStyle?.emptyStateStyle?.iconStyle?.width}\n              containerStyle={mergedStyle?.emptyStateStyle?.iconContainerStyle}\n            />\n          }\n          containerStyle={{\n            flex: 1,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: \"10%\",\n          }}\n          titleStyle={mergedStyle?.emptyStateStyle?.titleStyle as TextStyle}\n          subTitleStyle={mergedStyle?.emptyStateStyle?.subTitleStyle as TextStyle}\n        />\n      </View>\n    );\n  }, [theme]);\n\n  /**\n   * Renders an error state view when something goes wrong.\n   * Also hides the search bar when the error view is active.\n   */\n  const ErrorStateView = useCallback(() => {\n    useEffect(() => {\n      setHideSearchError(true); // Hide search when error view is active\n    }, []);\n    return (\n      <View style={{ flex: 1 }}>\n        <ErrorEmptyView\n          title={t(\"OOPS\")}\n          subTitle={t(\"SOMETHING_WENT_WRONG\")}\n          tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n          Icon={\n            <Icon\n              icon={mergedStyle?.errorStateStyle?.icon as ImageSourcePropType | JSX.Element}\n              color={mergedStyle?.errorStateStyle?.iconStyle?.tintColor}\n              height={mergedStyle?.errorStateStyle?.iconStyle?.height}\n              width={mergedStyle?.errorStateStyle?.iconStyle?.width}\n              containerStyle={mergedStyle?.errorStateStyle?.iconContainerStyle}\n            />\n          }\n          containerStyle={{\n            flex: 1,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: \"10%\",\n          }}\n          titleStyle={mergedStyle?.errorStateStyle?.titleStyle as TextStyle}\n          subTitleStyle={mergedStyle?.errorStateStyle?.subTitleStyle as TextStyle}\n        />\n      </View>\n    );\n  }, [theme]);\n\n  /**\n   * Returns an action badge (OWNER, ADMIN, MODERATOR) or no badge for participant.\n   */\n  const TailViewContent = (user: CometChat.GroupMember): JSX.Element => {\n    const userRole = user?.getScope();\n    if (userRole === \"participant\") {\n      return <></>;\n    }\n    if (user.getUid() === group.getOwner()) {\n      return (\n        <View style={{ ...mergedStyle.ownerBadgeStyle?.containerStyle }}>\n          <Text style={{ ...mergedStyle.ownerBadgeStyle?.textStyle }}>{t(\"OWNER\")}</Text>\n        </View>\n      );\n    }\n    if (userRole === \"moderator\") {\n      return (\n        <View style={{ ...mergedStyle.moderatorBadgeStyle?.containerStyle }}>\n          <Text style={{ ...mergedStyle.moderatorBadgeStyle?.textStyle }}>\n            {t(\"MODERATOR\")}\n          </Text>\n        </View>\n      );\n    }\n    if (userRole === \"admin\") {\n      return (\n        <View style={{ ...mergedStyle.adminBadgeStyle?.containerStyle }}>\n          <Text style={{ ...mergedStyle.adminBadgeStyle?.textStyle }}>{t(\"ADMIN\")}</Text>\n        </View>\n      );\n    }\n    return <></>;\n  };\n\n  /**\n   * Removes (kicks) a user from the group.\n   */\n  const removeGroupMember = async (groupId: string, user: CometChat.GroupMember) => {\n    try {\n      const removedMember = await CometChat.kickGroupMember(groupId, user.getUid());\n      console.log(\"Group member removed successfully:\", removedMember);\n\n      // Remove the user from the list view.\n      listRef.current && listRef.current.removeItemFromList(user.getUid());\n\n      // Update group's member count if possible.\n      if (group.setMembersCount && typeof group.setMembersCount === \"function\") {\n        group.setMembersCount(group.getMembersCount() - 1);\n      }\n\n      // Clear the modal and selected item state.\n      setModalType(\"\");\n      setSelectedItem(null);\n\n      // Create an action object to notify about the removal.\n      let action: CometChat.Action = new CometChat.Action(\n        group.getGuid(),\n        MessageTypeConstants.groupMember,\n        CometChat.RECEIVER_TYPE.GROUP,\n        CometChat.CATEGORY_ACTION as CometChat.MessageCategory\n      );\n\n      action.setActionBy(loggedInUser.current);\n      action.setActionOn(user);\n      action.setActionFor(group);\n      action.setMessage(`${loggedInUser.current.getName()} kicked ${user.getName()}`);\n      action.setSentAt(getUnixTimestamp());\n      action.setMuid(String(getUnixTimestampInMilliseconds()));\n      action.setSender(loggedInUser.current);\n      action.setReceiver(group);\n      action.setConversationId(\"group_\" + group.getGuid());\n      // Initialize data to prevent crash when SDK accesses getData().metadata during render\n      action.setData({ metadata: {} });\n\n      // Emit the group event for a kicked member.\n      CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupMemberKicked, {\n        message: action,\n        kickedUser: user,\n        kickedBy: loggedInUser.current,\n        kickedFrom: group,\n      });\n    } catch (error) {\n      console.error(\"Error removing user:\", error);\n    }\n  };\n\n  /**\n   * Bans a user from the group.\n   */\n  const banGroupMember = async (user: CometChat.GroupMember) => {\n    try {\n      await CometChat.banGroupMember(group.getGuid(), user.getUid());\n\n      // Remove the user from the list view.\n      listRef.current && listRef.current.removeItemFromList(user.getUid());\n\n      // Clear the modal and selected item state.\n      setModalType(\"\");\n      setSelectedItem(null);\n\n      // Create an action object to notify about the ban.\n      let action: CometChat.Action = new CometChat.Action(\n        group.getGuid(),\n        MessageTypeConstants.groupMember,\n        CometChat.RECEIVER_TYPE.GROUP,\n        CometChat.CATEGORY_ACTION as CometChat.MessageCategory\n      );\n      action.setActionBy(loggedInUser.current);\n      action.setActionOn(user);\n      action.setActionFor(group);\n      action.setMessage(`${loggedInUser.current.getName()} banned ${user.getName()}`);\n      action.setSentAt(getUnixTimestamp());\n      action.setMuid(String(getUnixTimestampInMilliseconds()));\n      action.setSender(loggedInUser.current);\n      action.setReceiver(group);\n      // Initialize data to prevent crash when SDK accesses getData().metadata during render\n      action.setData({ metadata: {} });\n      group.setMembersCount(group.getMembersCount() - 1);\n\n      // Emit the group event for a banned member.\n      CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupMemberBanned, {\n        message: action,\n        kickedUser: user,\n        kickedBy: loggedInUser.current,\n        kickedFrom: group,\n      });\n    } catch (error) {\n      console.error(\"Error banning user:\", error);\n    }\n  };\n\n  /**\n   * Generate default menu items (Change Scope, Ban, Remove).\n   * Then filter them out based on hide props (hideBanMemberOption, etc.),\n   * or based on current user role (RolePermissions).\n   */\n  const getDefaultMenuItems = (item: CometChat.GroupMember): MenuItemInterface[] => {\n    const defaultItems: MenuItemInterface[] = [\n      {\n        text: t(\"CHANGE_SCOPE\"),\n        translationKey: \"CHANGE_SCOPE\", // Added this for filtering\n        onPress: () => {\n          const currentScope = item.getScope();\n          setTooltipVisible(false);\n          setIsBottomSheetVisible(true);\n          // Preselect to current scope (lowercase) for accurate comparison\n          setSelectedRole(currentScope);\n        },\n        icon: (\n          <ChangeCircle\n            color={theme.color.textPrimary}\n            height={theme.spacing.spacing.s6}\n            width={theme.spacing.spacing.s6}\n          />\n        ),\n        textStyle: { color: theme.color.textPrimary },\n        disabled: false,\n      },\n      {\n        text: t(\"BAN\"),\n        translationKey: \"BAN\", // Added this for filtering\n        onPress: () => {\n          setTooltipVisible(false);\n          setModalType(\"ban\");\n        },\n        icon: (\n          <Block\n            color={theme.color.textPrimary}\n            height={theme.spacing.spacing.s6}\n            width={theme.spacing.spacing.s6}\n          />\n        ),\n        textStyle: { color: theme.color.textPrimary },\n        disabled: false,\n      },\n      {\n        text: t(\"KICK\"),\n        translationKey: \"KICK\", // Added this for filtering\n        onPress: () => {\n          setTooltipVisible(false);\n          setModalType(\"kick\");\n        },\n        icon: (\n          <Cancel\n            color={theme.color.textPrimary}\n            height={theme.spacing.spacing.s6}\n            width={theme.spacing.spacing.s6}\n          />\n        ),\n        textStyle: { color: theme.color.textPrimary },\n        disabled: false,\n      },\n    ];\n\n    let filteredItems = defaultItems;\n\n    // Hide Kick?\n    if (hideKickMemberOption) {\n      filteredItems = filteredItems.filter((i) => i.translationKey !== \"KICK\");\n    }\n    // Hide Ban?\n    if (hideBanMemberOption) {\n      filteredItems = filteredItems.filter((i) => i.translationKey !== \"BAN\");\n    }\n    // Hide Scope?\n    if (hideScopeChangeOption) {\n      filteredItems = filteredItems.filter((i) => i.translationKey !== \"CHANGE_SCOPE\");\n    }\n\n    // Now filter by user role permissions using the translationKey instead of text\n    filteredItems = filteredItems.filter((itemMenu) =>\n      RolePermissions[currentUserRole?.toUpperCase()!].includes(itemMenu.translationKey || \"\")\n    );\n\n    return filteredItems;\n  };\n\n  /**\n   * Merges default menu items with user-defined ones or replaces them entirely.\n   *  - If options is defined, we use that array as the entire set of menu items.\n   *  - If addOptions is defined, we append them to the default set.\n   */\n  const buildMenuItems = (item: CometChat.GroupMember): MenuItemInterface[] => {\n    // If options is provided, it completely replaces default items\n    if (options) {\n      const replaced = options(item, group);\n      return replaced;\n    }\n\n    // Else we get the default\n    let defaultItems = getDefaultMenuItems(item);\n\n    // If addOptions is provided, we append them\n    if (addOptions) {\n      const appended = addOptions(item, group);\n      defaultItems = [...defaultItems, ...appended];\n    }\n    return defaultItems;\n  };\n\n  return (\n    <View style={mergedStyle.containerStyle as ViewStyle}>\n      <CometChatList\n        ref={listRef}\n        hideHeader={hideHeader}\n        onError={onError}\n        selectionMode={selectionMode}\n        hideStickyHeader={true}\n        hideSubmitButton={hideSubmitButton}\n        onSelection={onSelection}\n        onSubmit={onSubmit}\n        hideBackButton={!showBackButton}\n        SubtitleView={SubtitleView ?? SubtitleView}\n        searchPlaceholderText={searchPlaceholderText}\n        TrailingView={TrailingView ?? TailViewContent}\n        hideSearch={hideSearch ? hideSearch : hideSearchError}\n        title={t(\"MEMBERS\")}\n        searchRequestBuilder={searchRequestBuilder}\n        requestBuilder={\n          groupMemberRequestBuilder ||\n          new CometChat.GroupMembersRequestBuilder(group[\"guid\"])\n            .setLimit(30)\n            .setSearchKeyword(searchKeyword)\n        }\n        hideError={hideError}\n        onBack={onBack}\n        ItemView={ItemView}\n        listStyle={mergedStyle}\n        LoadingView={\n          hideLoadingState\n            ? () => <></> // will not render anything if true\n            : LoadingView\n              ? LoadingView\n              : () => <Skeleton />\n        }\n        EmptyView={EmptyView ? EmptyView : () => <EmptyStateView />}\n        ErrorView={ErrorView ? ErrorView : () => <ErrorStateView />}\n        statusIndicatorType={(user: CometChat.User) =>\n          usersStatusVisibility\n            ? (user?.getStatus() as CometChatStatusIndicatorInterface[\"type\"])\n            : null\n        }\n        LeadingView={LeadingView}\n        AppBarOptions={AppBarOptions}\n        listItemKey={\"uid\"}\n        {...newProps}\n        onItemLongPress={(member, e) => {\n          // 1) If user explicitly provided a callback, call it and return\n          if (props.onItemLongPress) {\n            props.onItemLongPress(member);\n            return;\n          }\n\n          // 2) Otherwise, use the default scope-based code\n          const targetIsOwner = member.uid === group.getOwner();\n          const targetScope = member.scope;\n\n          // Role-based restrictions\n          if (currentUserRole === \"owner\") {\n            // Owner can perform any action.\n          } else if (currentUserRole === \"admin\") {\n            // Admin cannot manage owner or other admins.\n            if (targetIsOwner || targetScope === \"admin\") {\n              return;\n            }\n          } else if (currentUserRole === \"moderator\") {\n            // Moderator cannot manage owner, admin or other moderators.\n            if (targetIsOwner || targetScope === \"admin\" || targetScope === \"moderator\") {\n              return;\n            }\n          } else if (currentUserRole === \"participant\") {\n            return;\n          }\n\n          // Only open if user is not self\n          if (member.uid === CometChatUIKit.loggedInUser!.getUid()) {\n            return;\n          }\n\n          if ([\"owner\", \"admin\", \"moderator\"].includes(currentUserRole!)) {\n            if (e && e.nativeEvent) {\n              tooltipPositon.current = {\n                pageX: e.nativeEvent.pageX,\n                pageY: e.nativeEvent.pageY,\n              };\n            } else {\n              tooltipPositon.current = { pageX: 200, pageY: 100 };\n            }\n            setSelectedItem(member);\n            setTooltipVisible(true);\n            setIsToolTipDismissed(false);\n          }\n        }}\n        onItemPress={(member) => {\n          if (props.onItemPress) {\n            props.onItemPress(member);\n          }\n        }}\n        onListFetched={(fetchedList: CometChat.GroupMember[]) => {\n          const selfUid = loggedInUser.current.getUid();\n          let finalList = fetchedList;\n          if (excludeOwner) {\n            finalList = fetchedList.filter((m) => m.getUid() !== selfUid);\n            // Remove from rendered list as well (safe call)\n            listRef.current?.removeItemFromList(selfUid);\n          }\n\n          if (finalList.length === 0) {\n            onEmpty?.();\n          } else {\n            onLoad?.(finalList);\n          }\n        }}\n      />\n\n      {/* Render tooltip menu if an item is selected and selectionMode is \"none\" */}\n      {selectedItem && selectionMode === \"none\" && (\n        <View\n          style={{\n            position: \"absolute\",\n            top: tooltipPositon.current.pageY,\n            left: tooltipPositon.current.pageX,\n            zIndex: 9999,\n          }}\n        >\n          <CometChatTooltipMenu\n            visible={tooltipVisible}\n            onClose={() => {\n              setTooltipVisible(false);\n              Platform.OS === \"android\" && setIsToolTipDismissed(true);\n            }}\n            onDismiss={() => {\n              setIsToolTipDismissed(true);\n            }}\n            event={{\n              nativeEvent: tooltipPositon.current,\n            }}\n            // Build the final menu items\n            menuItems={buildMenuItems(selectedItem).map((mi) => ({\n              text: mi.text,\n              onPress: mi.onPress,\n              icon: mi.icon,\n              textStyle: mi.textStyle,\n              iconStyle: mi.iconStyle,\n              iconContainerStyle: mi.iconContainerStyle,\n              disabled: mi.disabled,\n            }))}\n          />\n        </View>\n      )}\n\n      {/* Bottom sheet for changing member's role (scope) */}\n      <CometChatBottomSheet\n        isOpen={isBottomSheetVisible}\n        doNotOccupyEntireHeight\n        onClose={() => {\n          setIsBottomSheetVisible(false);\n          setSelectedItem(null);\n          setSelectedRole(\"\");\n        }}\n        onDismiss={() => {\n          setSelectedItem(null);\n          setSelectedRole(\"\");\n        }}\n      >\n        {selectedItem ? (\n          <View style={mergedStyle?.changeScope?.container}>\n            <View style={mergedStyle.changeScope?.iconContainerStyle}>\n              <Icon\n                name='change-circle'\n                icon={mergedStyle?.changeScope?.icon}\n                color={mergedStyle?.changeScope?.iconStyle?.tintColor}\n                height={mergedStyle?.changeScope?.iconStyle?.height}\n                width={mergedStyle?.changeScope?.iconStyle?.width}\n                containerStyle={mergedStyle?.changeScope?.iconStyle}\n              />\n            </View>\n\n            <Text style={mergedStyle?.changeScope?.titleStyle}>{t(\"CHANGE_SCOPE\")}</Text>\n\n            <Text style={mergedStyle?.changeScope?.subTitleStyle}>\n              {t(\"SCOPE_CHANGE_INFO\")}\n            </Text>\n\n            <View style={mergedStyle?.changeScope?.actionBox}>\n              {roles\n                .filter((role) => {\n                  if (currentUserRole === \"moderator\") {\n                    const targetScope = selectedItem.getScope();\n                    if (targetScope === \"moderator\" && role.value === \"participant\") {\n                      return false;\n                    }\n                  }\n                  return true;\n                })\n                .map((role) => {\n                  const isDisabled = currentUserRole === \"moderator\" && role.value === \"admin\";\n                  return (\n                    <TouchableOpacity\n                      key={role.value}\n                      activeOpacity={isDisabled ? 1 : 0.7}\n                      onPress={() => {\n                        if (!isDisabled) {\n                          setSelectedRole(role.value);\n                        }\n                      }}\n                      style={[\n                        { opacity: isDisabled ? 0.5 : 1 },\n                        mergedStyle?.changeScope?.actionListStyle,\n                      ]}\n                    >\n                      <Text\n                        style={[\n                          theme.typography.heading4.medium,\n                          { color: theme.color.textPrimary },\n                        ]}\n                      >\n                        {role.display}\n                      </Text>\n                      <View\n                        style={{\n                          height: 24,\n                          width: 24,\n                          borderRadius: 12,\n                          borderWidth: 2,\n                          borderColor: theme.color.primary,\n                          justifyContent: \"center\",\n                          alignItems: \"center\",\n                        }}\n                      >\n                        {selectedRole === role.value && (\n                          <View\n                            style={{\n                              height: 12,\n                              width: 12,\n                              borderRadius: 6,\n                              backgroundColor: theme.color.primary,\n                            }}\n                          />\n                        )}\n                      </View>\n                    </TouchableOpacity>\n                  );\n                })}\n            </View>\n\n            <View style={mergedStyle.changeScope?.buttonContainer}>\n              <TouchableOpacity\n                style={mergedStyle?.changeScope?.cancelStyle?.containerStyle}\n                onPress={() => {\n                  setIsBottomSheetVisible(false);\n                  setSelectedItem(null);\n                  setSelectedRole(\"\");\n                }}\n              >\n                <Text style={mergedStyle?.changeScope?.cancelStyle?.textStyle}>\n                  {t(\"CANCEL\")}\n                </Text>\n              </TouchableOpacity>\n              <TouchableOpacity\n                style={[\n                  mergedStyle?.changeScope?.confirmStyle?.containerStyle,\n                  isScopeUnchanged ? {opacity: 0.5} : {}\n                ]}\n                disabled={isScopeUnchanged}\n                onPress={async () => {\n                  if (isScopeUnchanged) return;\n                  if (selectedItem && selectedRole) {\n                    const currentScope = selectedItem.getScope();\n                    const scopeToSet = selectedRole;\n\n                    // If no change is needed, just close the bottom sheet.\n                    if (scopeToSet === currentScope) {\n                      setIsBottomSheetVisible(false);\n                      setSelectedItem(null);\n                      setSelectedRole(\"\");\n                      return;\n                    }\n\n                    try {\n                      await CometChat.updateGroupMemberScope(\n                        group.getGuid(),\n                        selectedItem.getUid(),\n                        scopeToSet as CometChat.GroupMemberScope\n                      );\n\n                      // Update the scope of the selected item and refresh the list.\n                      selectedItem.setScope(scopeToSet as CometChat.GroupMemberScope);\n                      listRef.current?.updateList(selectedItem);\n\n                      setIsBottomSheetVisible(false);\n\n                      // Create an action object to notify about the scope change.\n                      let action: CometChat.Action = new CometChat.Action(\n                        group[\"guid\"],\n                        \"groupMember\",\n                        CometChat.RECEIVER_TYPE.GROUP,\n                        CometChat.CATEGORY_ACTION as CometChat.MessageCategory\n                      );\n                      action.setActionBy(loggedInUser.current);\n                      action.setActionOn(selectedItem);\n                      action.setActionFor(group);\n                      action.setMessage(\n                        `${loggedInUser.current.getName()} made ${selectedItem.getName()} ${scopeToSet} `\n                      );\n                      action.setSentAt(getUnixTimestamp());\n                      action.setMuid(String(getUnixTimestampInMilliseconds()));\n                      action.setSender(loggedInUser.current);\n                      action.setReceiver(group);\n                      // Initialize data to prevent crash when SDK accesses getData().metadata during render\n                      action.setData({ metadata: {} });\n\n                      // Emit the group event for a scope change.\n                      CometChatUIEventHandler.emitGroupEvent(\n                        CometChatGroupsEvents.ccGroupMemberScopeChanged,\n                        {\n                          action,\n                          updatedUser: selectedItem,\n                          scopeChangedTo: scopeToSet,\n                          scopeChangedFrom: selectedItem.getScope(),\n                          group,\n                        }\n                      );\n                      setSelectedItem(null);\n                      setSelectedRole(\"\");\n                    } catch (error) {\n                      console.error(\"Error updating user scope:\", error);\n                    }\n                  }\n                }}\n              >\n                <Text style={mergedStyle.changeScope?.confirmStyle?.textStyle}>\n                  {t(\"SAVE\")}\n                </Text>\n              </TouchableOpacity>\n            </View>\n          </View>\n        ) : null}\n      </CometChatBottomSheet>\n\n      {/* Render modal for ban or remove actions if an item is selected */}\n      <Modal\n        animationType='fade'\n        transparent={true}\n        visible={modalVisible}\n        onRequestClose={() => setModalType(\"\")}\n        onDismiss={() => {\n          setSelectedItem(null); \n        }}\n      >\n        {modalVisible && selectedItem ? (\n          <>\n            <TouchableOpacity onPress={() => setModalType(\"\")} style={{ flex: 1 }}>\n              <View style={{ flex: 1, backgroundColor: \"rgba(0, 0, 0, 0.5)\" }} />\n            </TouchableOpacity>\n\n            {modalType && (\n              <View\n                style={{\n                  ...(modalType === \"ban\"\n                    ? mergedStyle.banModalStyle?.containerStyle\n                    : mergedStyle.removeModalStyle?.containerStyle),\n                }}\n              >\n                <View\n                  style={{\n                    ...(modalType === \"ban\"\n                      ? mergedStyle.banModalStyle?.iconContainerStyle\n                      : mergedStyle.removeModalStyle?.iconContainerStyle),\n                  }}\n                >\n                  <Icon\n                    name='delete'\n                    icon={\n                      modalType === \"ban\"\n                        ? mergedStyle.banModalStyle?.icon\n                        : mergedStyle.removeModalStyle?.icon\n                    }\n                    color={\n                      modalType === \"ban\"\n                        ? mergedStyle?.banModalStyle?.iconStyle?.tintColor\n                        : mergedStyle?.removeModalStyle?.iconStyle?.tintColor\n                    }\n                    height={\n                      modalType === \"ban\"\n                        ? mergedStyle?.banModalStyle?.iconStyle?.height\n                        : mergedStyle?.removeModalStyle?.iconStyle?.height\n                    }\n                    width={\n                      modalType === \"ban\"\n                        ? mergedStyle?.banModalStyle?.iconStyle?.width\n                        : mergedStyle?.removeModalStyle?.iconStyle?.width\n                    }\n                    imageStyle={\n                      modalType === \"ban\"\n                        ? mergedStyle?.banModalStyle?.iconStyle\n                        : mergedStyle?.removeModalStyle?.iconStyle\n                    }\n                  />\n                </View>\n                <View>\n                  <Text\n                    style={\n                      modalType === \"ban\"\n                        ? mergedStyle.banModalStyle?.titleTextStyle\n                        : mergedStyle.removeModalStyle?.titleTextStyle\n                    }\n                  >\n                    {modalType === \"ban\"\n                      ? `${t(\"BAN\")} ${selectedItem.getName()} ?`\n                      : `${t(\"KICK\")} ${selectedItem.getName()} ?`}\n                  </Text>\n\n                  <Text\n                    style={\n                      modalType === \"ban\"\n                        ? mergedStyle.banModalStyle?.subTitleTextStyle\n                        : mergedStyle.removeModalStyle?.subTitleTextStyle\n                    }\n                  >\n                    {modalType === \"ban\"\n                      ? `${t(\"BAN_MEMBER_CONFIRM\")}${selectedItem.getName()}?`\n                      : `${t(\"REMOVE_MEMBER_CONFIRM\")}${selectedItem.getName()}?`}\n                  </Text>\n\n                  <View\n                    style={{\n                      ...(modalType === \"ban\"\n                        ? mergedStyle.banModalStyle?.alertContainer\n                        : mergedStyle.removeModalStyle?.alertContainer),\n                    }}\n                  >\n                    <TouchableOpacity\n                      style={{\n                        ...(modalType === \"ban\"\n                          ? mergedStyle.banModalStyle?.cancelStyle?.containerStyle\n                          : mergedStyle.removeModalStyle?.cancelStyle?.containerStyle),\n                      }}\n                      onPress={() => setModalType(\"\")}\n                    >\n                      <Text\n                        style={{\n                          ...(modalType === \"ban\"\n                            ? mergedStyle.banModalStyle?.cancelStyle?.textStyle\n                            : mergedStyle.removeModalStyle?.cancelStyle?.textStyle),\n                        }}\n                      >\n                        {t(\"NO\")}\n                      </Text>\n                    </TouchableOpacity>\n                    <TouchableOpacity\n                      style={{\n                        ...(modalType === \"ban\"\n                          ? mergedStyle.banModalStyle?.confirmStyle?.containerStyle\n                          : mergedStyle.removeModalStyle?.confirmStyle?.containerStyle),\n                      }}\n                      onPress={async () => {\n                        if (selectedItem) {\n                          if (modalType === \"ban\") {\n                            await banGroupMember(selectedItem);\n                          } else {\n                            await removeGroupMember(group.getGuid(), selectedItem);\n                          }\n                        }\n                      }}\n                    >\n                      <Text\n                        style={{\n                          ...(modalType === \"ban\"\n                            ? mergedStyle.banModalStyle?.confirmStyle?.textStyle\n                            : mergedStyle.removeModalStyle?.confirmStyle?.textStyle),\n                        }}\n                      >\n                        {t(\"YES\")}\n                      </Text>\n                    </TouchableOpacity>\n                  </View>\n                </View>\n              </View>\n            )}\n          </>\n        ) : null}\n      </Modal>\n\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroupMembers/Skeleton.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Circle, Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../theme\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\n// Constants for dimensions\nconst padding = 20;\nconst listItemHeight = 25; // Increased from 15 to 25\nconst listItemSpacing = 54; // Adjust if necessary\nconst avatarRadius = 25; // Increased from 12 to 20\nconst listItemCount = 14;\n\nconst SkeletonItemBottom = () => {\n  const theme = useTheme();\n\n  // Calculate the total height needed for the SVG\n  const totalHeight = padding + listItemCount * (listItemHeight + listItemSpacing);\n\n  return (\n    <Svg height={totalHeight} width={screenWidth} fill='none' preserveAspectRatio='xMidYMid meet'>\n      {/* List Items */}\n      {Array.from({ length: listItemCount }).map((_, index) => {\n        const itemY = padding + index * (listItemHeight + listItemSpacing) - 20;\n\n        return (\n          <React.Fragment key={index}>\n            {/* Avatar Circle */}\n            <Circle\n              cx={padding + avatarRadius}\n              cy={itemY + avatarRadius}\n              r={avatarRadius}\n              fill='url(#paint0_linear)'\n            />\n\n            {/* Username Rectangle */}\n            <Rect\n              x={padding + 2 * avatarRadius + 12} // 12 units spacing after avatar\n              y={itemY + 12}\n              width={screenWidth - (padding + 2 * avatarRadius + 12 + padding)}\n              height={listItemHeight}\n              rx={listItemHeight / 2} // Rounded corners matching height\n              fill='url(#paint0_linear)'\n            />\n          </React.Fragment>\n        );\n      })}\n\n      {/* Gradient Definition */}\n      <Defs>\n        <LinearGradient\n          id='paint0_linear'\n          x1='0'\n          y1='0'\n          x2={screenWidth}\n          y2='0'\n          gradientUnits='userSpaceOnUse'\n        >\n          <Stop stopColor={theme.groupMemberStyle.skeletonStyle.linearGradientColors[0]} />\n          <Stop\n            offset='1'\n            stopColor={theme.groupMemberStyle.skeletonStyle.linearGradientColors[1]}\n          />\n        </LinearGradient>\n      </Defs>\n    </Svg>\n  );\n};\n\nconst SkeletonItemTop = () => {\n  const theme = useTheme();\n\n  // Calculate the total height needed for the SVG\n  const totalHeight = padding + listItemCount * (listItemHeight + listItemSpacing);\n\n  return (\n    <Svg height={totalHeight} width={screenWidth} fill='none' preserveAspectRatio='xMidYMid meet'>\n      {/* List Items */}\n      {Array.from({ length: listItemCount }).map((_, index) => {\n        const itemY = padding + index * (listItemHeight + listItemSpacing) - 20;\n\n        return (\n          <React.Fragment key={index}>\n            {/* Avatar Circle */}\n            <Circle\n              cx={padding + avatarRadius}\n              cy={itemY + avatarRadius}\n              r={avatarRadius}\n              fill={theme.groupMemberStyle.skeletonStyle.backgroundColor}\n            />\n\n            {/* Username Rectangle */}\n            <Rect\n              x={padding + 2 * avatarRadius + 12} // 12 units spacing after avatar\n              y={itemY + 12}\n              width={screenWidth - (padding + 2 * avatarRadius + 12 + padding)}\n              height={listItemHeight}\n              rx={listItemHeight / 2} // Rounded corners matching height\n              fill={theme.groupMemberStyle.skeletonStyle.backgroundColor}\n            />\n          </React.Fragment>\n        );\n      })}\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const theme = useTheme();\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / theme.groupMemberStyle.skeletonStyle.speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n  }, [animatedValue, theme.groupMemberStyle.skeletonStyle.speed]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth * 2, screenWidth],\n  });\n\n  return (\n    <ScrollView\n      scrollEnabled={false}\n      showsVerticalScrollIndicator={false}\n      style={{ backgroundColor: theme.groupMemberStyle?.containerStyle?.backgroundColor }}\n    >\n      <SkeletonItemBottom />\n      <Animated.View\n        style={[\n          {\n            transform: [\n              { translateX: shimmerTranslateX },\n              { translateY: -20 },\n              { rotate: \"15deg\" },\n            ],\n          },\n          styles.animatedView,\n          {\n            backgroundColor: theme.groupMemberStyle.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.groupMemberStyle.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      />\n      <Animated.View\n        style={[\n          {\n            transform: [\n              {\n                translateX: Animated.add(shimmerTranslateX, screenWidth / 2),\n              },\n              { translateY: -20 },\n              { rotate: \"15deg\" },\n            ],\n          },\n          styles.animatedView,\n          {\n            backgroundColor: theme.groupMemberStyle.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.groupMemberStyle.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      />\n      <View\n        style={{\n          position: \"absolute\",\n          top: 0,\n          left: 0,\n          right: 0,\n          bottom: 0,\n        }}\n      >\n        <SkeletonItemTop />\n      </View>\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  animatedView: {\n    width: \"25%\",\n    top: 0,\n    bottom: 0,\n    position: \"absolute\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroupMembers/index.ts",
    "content": "export { CometChatGroupMembers } from \"./CometChatGroupMembers\";\nexport type {CometChatGroupMembersInterface} from \"./CometChatGroupMembers\";"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroupMembers/resources/index.ts",
    "content": "import banIcon from \"./banIcon.png\";\nimport checkIcon from \"./check.png\";\nimport downArrowIcon from \"./downArrowIcon.png\";\nimport kickIcon from \"./kickIcon.png\";\nimport rightTickIcon from \"./rightTick.png\";\n\nexport { banIcon, checkIcon, downArrowIcon, kickIcon, rightTickIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroupMembers/style.ts",
    "content": "import { ColorValue, ImageSourcePropType, ImageStyle, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatListStylesInterface } from \"../shared\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { JSX } from \"react\";\n\nexport type GroupMemberStyle = DeepPartial<CometChatListStylesInterface> & {\n  skeletonStyle: {\n    backgroundColor: ColorValue;\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n  };\n  headerContainerStyle: ViewStyle;\n  ownerBadgeStyle?: {\n    containerStyle?: ViewStyle;\n    textStyle?: TextStyle;\n  };\n  adminBadgeStyle?: {\n    containerStyle?: ViewStyle;\n    textStyle?: TextStyle;\n  };\n  moderatorBadgeStyle?: {\n    containerStyle?: ViewStyle;\n    textStyle?: TextStyle;\n  };\n  removeModalStyle?: {\n    containerStyle?: ViewStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ViewStyle;\n    cancelStyle?: {\n      containerStyle: ViewStyle;\n      textStyle: TextStyle;\n    };\n    confirmStyle?: {\n      containerStyle: ViewStyle;\n      textStyle: TextStyle;\n    };\n    titleTextStyle?: TextStyle;\n    subTitleTextStyle?: TextStyle;\n    alertContainer: ViewStyle;\n  };\n  banModalStyle?: {\n    containerStyle?: ViewStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ViewStyle;\n    cancelStyle?: {\n      containerStyle?: ViewStyle;\n      textStyle?: TextStyle;\n    };\n    confirmStyle?: {\n      containerStyle?: ViewStyle;\n      textStyle?: TextStyle;\n    };\n    titleTextStyle?: TextStyle;\n    subTitleTextStyle?: TextStyle;\n\n    alertContainer?: ViewStyle;\n  };\n  changeScope?: {\n    container?: ViewStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ViewStyle;\n    titleStyle?: TextStyle;\n    subTitleStyle?: TextStyle;\n    cancelStyle?: {\n      containerStyle?: ViewStyle;\n      textStyle?: TextStyle;\n    };\n    confirmStyle?: {\n      containerStyle: ViewStyle;\n      textStyle: TextStyle;\n    };\n    actionBox?: ViewStyle;\n    actionListStyle?: ViewStyle;\n    buttonContainer?: ViewStyle;\n  };\n};\n\nexport const getGroupMemberStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): GroupMemberStyle => {\n  return {\n    headerContainerStyle: {\n      alignItems: \"flex-start\",\n      justifyContent: \"center\",\n      width: \"100%\",\n      borderRadius: 0,\n      paddingHorizontal: 0,\n    },\n    titleSeparatorStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n      flexDirection: \"row\",\n      justifyContent: \"space-between\",\n      alignItems: \"center\",\n      width: \"100%\",\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      flex: 1,\n    },\n    itemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        gap: spacing.spacing.s3,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.body.regular,\n      },\n      statusIndicatorStyle: {},\n      avatarStyle: {\n        containerStyle: {},\n        textStyle: {},\n        imageStyle: {},\n      },\n      headViewContainerStyle: {},\n      titleSubtitleContainerStyle: {\n        alignSelf: \"center\",\n      },\n      trailingViewContainerStyle: {\n        alignSelf: \"center\",\n      },\n    },\n    ownerBadgeStyle: {\n      containerStyle: {\n        paddingVertical: 5,\n        paddingHorizontal: 10,\n        borderRadius: 28,\n        backgroundColor: color.primaryButtonBackground,\n        borderColor: color.borderHighlight,\n        borderWidth: 1,\n      },\n      textStyle: {\n        ...typography.caption1.regular,\n        color: color.staticWhite,\n      },\n    },\n    adminBadgeStyle: {\n      containerStyle: {\n        backgroundColor: color.extendedPrimary100,\n        borderWidth: 1,\n        borderColor: color.borderHighlight,\n        paddingVertical: 5,\n        paddingHorizontal: 10,\n        borderRadius: 28,\n      },\n      textStyle: {\n        ...typography.caption1.medium,\n        color: color.textHighlight,\n      },\n    },\n    moderatorBadgeStyle: {\n      containerStyle: {\n        paddingVertical: 5,\n        paddingHorizontal: 10,\n        borderRadius: 28,\n        backgroundColor: color.extendedPrimary100,\n      },\n      textStyle: {\n        ...typography.caption1.medium,\n        color: color.textHighlight,\n      },\n    },\n    removeModalStyle: {\n      containerStyle: {\n        backgroundColor: color.background1,\n        position: \"absolute\",\n        top: \"35%\",\n        left: \"4%\",\n        right: \"4%\",\n        borderRadius: 16,\n        padding: 20,\n        elevation: 5,\n        zIndex: 1000,\n      },\n      iconContainerStyle: {\n        backgroundColor: color.background2,\n        width: spacing.spacing.s18,\n        height: spacing.spacing.s18,\n        borderRadius: spacing.spacing.s18 / 2,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        alignSelf: \"center\",\n      },\n      iconStyle: {\n        tintColor: color.error,\n        height: 45,\n        width: 45,\n      },\n      titleTextStyle: {\n        ...typography.heading2.bold,\n        color: color.textPrimary,\n        marginBottom: 10,\n        alignSelf: \"center\",\n        marginTop: 20,\n      },\n      subTitleTextStyle: {\n        ...typography.heading4.regular,\n        color: color.textSecondary,\n        textAlign: \"center\",\n        marginBottom: 10,\n      },\n      cancelStyle: {\n        containerStyle: {\n          flex: 1,\n          paddingVertical: 10,\n          borderRadius: 5,\n          marginHorizontal: 5,\n          borderWidth: 1,\n          alignItems: \"center\",\n          borderColor: color.borderDefault,\n        },\n        textStyle: {\n          ...typography.heading4.bold,\n          color: color.secondaryButtonText,\n        },\n      },\n      confirmStyle: {\n        containerStyle: {\n          flex: 1,\n          marginHorizontal: 5,\n          paddingVertical: 10,\n          borderRadius: 5,\n          alignItems: \"center\",\n          backgroundColor: color.error,\n        },\n        textStyle: {\n          ...typography.heading4.bold,\n          color: color.primaryButtonText,\n        },\n      },\n      alertContainer: {\n        flexDirection: \"row\",\n        width: \"100%\",\n        marginTop: 10,\n        flex: 1,\n        paddingVertical: 10,\n        borderRadius: 5,\n        marginHorizontal: 5,\n        alignItems: \"center\",\n        borderColor: color.borderDefault,\n      },\n    },\n    banModalStyle: {\n      containerStyle: {\n        backgroundColor: color.background1,\n        position: \"absolute\",\n        top: \"35%\",\n        left: \"4%\",\n        right: \"4%\",\n        borderRadius: 16,\n        padding: 20,\n        elevation: 5,\n        zIndex: 1000,\n      },\n      iconContainerStyle: {\n        backgroundColor: color.background2,\n        width: spacing.spacing.s18,\n        height: spacing.spacing.s18,\n        borderRadius: spacing.spacing.s18 / 2,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        alignSelf: \"center\",\n      },\n      iconStyle: {\n        tintColor: color.error,\n        height: 45,\n        width: 45,\n      },\n      titleTextStyle: {\n        ...typography.heading2.bold,\n        color: color.textPrimary,\n        marginBottom: 10,\n        alignSelf: \"center\",\n        marginTop: 20,\n      },\n      subTitleTextStyle: {\n        ...typography.heading4.regular,\n        color: color.textSecondary,\n        textAlign: \"center\",\n        marginBottom: 10,\n      },\n      cancelStyle: {\n        containerStyle: {\n          flex: 1,\n          paddingVertical: 10,\n          borderRadius: 5,\n          marginHorizontal: 5,\n          borderWidth: 1,\n          alignItems: \"center\",\n          borderColor: color.borderDefault,\n        },\n        textStyle: {\n          ...typography.heading4.bold,\n          color: color.secondaryButtonText,\n        },\n      },\n      confirmStyle: {\n        containerStyle: {\n          flex: 1,\n          marginHorizontal: 5,\n          paddingVertical: 10,\n          borderRadius: 5,\n          alignItems: \"center\",\n          backgroundColor: color.error,\n        },\n        textStyle: {\n          ...typography.heading4.bold,\n          color: color.primaryButtonText,\n        },\n      },\n      alertContainer: {\n        flexDirection: \"row\",\n        width: \"100%\",\n        marginTop: 10,\n        flex: 1,\n        paddingVertical: 10,\n        borderRadius: 5,\n        marginHorizontal: 5,\n        alignItems: \"center\",\n        borderColor: color.borderDefault,\n      },\n    },\n    changeScope: {\n      container: { paddingHorizontal: 15, marginBottom: 15 },\n      iconContainerStyle: {\n        backgroundColor: color.background2,\n        width: spacing.spacing.s18,\n        height: spacing.spacing.s18,\n        borderRadius: spacing.spacing.s18 / 2,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        alignSelf: \"center\",\n      },\n      iconStyle: {\n        tintColor: color.primary,\n        height: 45,\n        width: 45,\n        alignSelf: \"center\",\n      },\n      titleStyle: {\n        ...typography.heading2.bold,\n        alignSelf: \"center\",\n        paddingTop: 20,\n        color: color.textPrimary,\n      },\n      subTitleStyle: {\n        ...typography.heading4.regular,\n        alignSelf: \"center\",\n        paddingTop: 10,\n        color: color.textSecondary,\n        textAlign: \"center\",\n        marginBottom: 10,\n      },\n      actionBox: {\n        borderWidth: 1,\n        borderColor: color.borderLight,\n        padding: 10,\n        borderRadius: 8,\n      },\n      confirmStyle: {\n        containerStyle: {\n          marginLeft: 2,\n          flex: 1,\n          paddingVertical: 10,\n          borderWidth: 1,\n          borderRadius: 8,\n          alignItems: \"center\",\n          borderColor: color.borderLight,\n          backgroundColor: color.primary,\n        },\n        textStyle: {\n          ...typography.heading4.regular,\n          color: color.staticWhite,\n        },\n      },\n      actionListStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        justifyContent: \"space-between\",\n        paddingVertical: 10,\n      },\n      cancelStyle: {\n        containerStyle: {\n          marginRight: 2,\n          flex: 1,\n          paddingVertical: 10,\n          borderWidth: 1,\n          borderRadius: 8,\n          alignItems: \"center\",\n          borderColor: color.borderLight,\n          backgroundColor: color.background1,\n        },\n        textStyle: {\n          ...typography.heading4.regular,\n          color: color.primary,\n        },\n      },\n      buttonContainer: {\n        flexDirection: \"row\",\n        justifyContent: \"space-between\",\n        paddingVertical: 10,\n        marginBottom: 10,\n      },\n    },\n    confirmSelectionStyle: {},\n    selectionCancelStyle: {},\n    loadingIconTint: color.primary,\n    sectionHeaderTextStyle: {\n      marginHorizontal: spacing.spacing.s5,\n      color: color.primary,\n      ...typography.heading4.medium,\n    },\n    onlineStatusColor: color.success,\n    titleViewStyle: {\n      paddingVertical: spacing.spacing.s3,\n      paddingLeft: spacing.spacing.s3,\n      margin: spacing.spacing.s0,\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      ...typography.heading1.bold,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    searchStyle: {\n      textStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.regular,\n        textAlignVertical: \"center\",\n        paddingVertical: 0,\n        height: spacing.spacing.s7,\n      },\n      containerStyle: {\n        backgroundColor: color.background3,\n        paddingVertical: spacing.spacing.s3,\n        marginTop: spacing.spacing.s3,\n        width: \"95%\",\n        gap: spacing.spacing.s1,\n        alignContent: \"space-around\",\n        alignSelf: \"center\",\n        flexDirection: \"row\",\n        alignItems: \"center\",\n      },\n      icon: undefined,\n      iconStyle: {\n        tintColor: color.iconSecondary,\n      },\n      placehodlerTextStyle: {\n        color: color.textTertiary,\n      },\n    },\n    emptyStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\" as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: \"center\",\n        display: \"none\",\n        alignItems: \"center\",\n        padding: spacing.padding.p3,\n      },\n      iconStyle: {\n        tintColor: color.neutral300,\n        height: 150,\n        width: 150,\n      },\n    },\n    errorStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\" as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        padding: spacing.padding.p3,\n      },\n      iconStyle: {\n        tintColor: color.neutral300,\n        height: 150,\n        width: 150,\n      },\n    },\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  };\n};\n\nexport const getGroupMemberStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): GroupMemberStyle => {\n  return deepMerge(getGroupMemberStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      backgroundColor: color.background3,\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroups/CometChatGroups.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from \"react\";\nimport { ImageSourcePropType, Text, View } from \"react-native\";\nimport { CometChatList, CometChatListActionsInterface, CometChatRetryButton } from \"../shared\";\nimport { SelectionMode } from \"../shared/base/Types\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { Icon, IconName } from \"../shared/icons/Icon\";\nimport { CometChatTooltipMenu, MenuItemInterface } from \"../shared/views/CometChatTooltipMenu\";\nimport { CometChatStatusIndicatorInterface } from \"../shared/views/CometChatStatusIndicator\";\nimport { ErrorEmptyView } from \"../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { useTheme } from \"../theme\";\nimport dark from \"../theme/default/resources/icons/dark_error_icon.png\";\nimport light from \"../theme/default/resources/icons/light_error_icon.png\";\nimport { useThemeInternal } from \"../theme/hook\";\nimport { GroupStyle } from \"./GroupsStyle\";\nimport { Skeleton } from \"./Skeleton\";\nimport { Style } from \"./style\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\n// Unique listener IDs for group events and UI events.\nconst groupListenerId = \"grouplist_\" + new Date().getTime();\nconst uiEventListener = \"uiEvents_\" + new Date().getTime();\n\n/**\n * Props for the CometChatGroups component.\n */\nexport interface CometChatGroupsInterface {\n  /**\n   * Custom title view for a group item.\n   * @param group - The group object.\n   * @returns JSX.Element to render as title.\n   */\n  TitleView?: (group: CometChat.Group) => JSX.Element;\n  /**\n   * Custom subtitle view for a group item.\n   */\n  SubtitleView?: (group: CometChat.Group) => JSX.Element;\n  /**\n   * Custom trailing view for a group item.\n   */\n  TrailingView?: (group: CometChat.Group) => JSX.Element;\n  /**\n   * Custom list item view for a group.\n   */\n  ItemView?: (group: CometChat.Group) => JSX.Element;\n  /**\n   * Custom component for the AppBar options.\n   */\n  AppBarOptions?: () => JSX.Element;\n  /**\n   * Hide the \"submit\" (selection) button.\n   */\n  hideSubmitButton?: boolean;\n  /**\n   * Custom style for groups.\n   */\n  style?: DeepPartial<GroupStyle>;\n  /**\n   * Placeholder text for the search input.\n   */\n  searchPlaceholderText?: string;\n  /**\n   * Toggle back button visibility.\n   */\n  showBackButton?: boolean;\n  /**\n   * Selection mode: \"none\" | \"single\" | \"multiple\".\n   */\n  selectionMode?: SelectionMode;\n  /**\n   * Callback when group selection is completed.\n   */\n  onSelection?: (list: Array<CometChat.Group>) => void;\n  /**\n   * Callback when submit selection button is pressed.\n   */\n  onSubmit?: (list: Array<CometChat.Conversation>) => void;\n  /**\n   * Hide the search box.\n   */\n  hideSearch?: boolean;\n  /**\n   * Custom view for the empty state.\n   */\n  EmptyView?: () => JSX.Element;\n  /**\n   * Custom view for the error state.\n   */\n  ErrorView?: () => JSX.Element;\n  /**\n   * Custom view for the loading state.\n   */\n  LoadingView?: () => JSX.Element;\n  /**\n   * Request builder to fetch groups.\n   */\n  groupsRequestBuilder?: CometChat.GroupsRequestBuilder;\n  /**\n   * Request builder for search functionality.\n   */\n  searchRequestBuilder?: CometChat.GroupsRequestBuilder;\n  /**\n   * Icon to be used for private groups.\n   */\n  privateGroupIcon?: ImageSourcePropType;\n  /**\n   * Icon to be used for password-protected groups.\n   */\n  passwordGroupIcon?: ImageSourcePropType;\n  /**\n   * Toggle error view visibility.\n   */\n  hideError?: boolean;\n  /**\n   * Callback when a group item is pressed.\n   */\n  onItemPress?: (item: CometChat.Group) => void;\n  /**\n   * Callback when a group item is long pressed.\n   */\n  onItemLongPress?: (item: CometChat.Group) => void;\n  /**\n   * Callback when an error occurs.\n   */\n  onError?: (e: CometChat.CometChatException) => void;\n  /**\n   * Callback when the back button is pressed.\n   */\n  onBack?: () => void;\n  /**\n   * Hide the header of the group list.\n   */\n  hideHeader?: boolean;\n  /**\n   * Custom leading view for a group item.\n   */\n  LeadingView?: (group: CometChat.Group) => JSX.Element;\n  /**\n   * Search Keyword.\n   */\n  searchKeyword?: string;\n  /**\n   * Callback triggered when the fetched list is empty.\n   */\n  onEmpty?: () => void;\n  /**\n   * Callback triggered once the groups have loaded and are not empty.\n   */\n  onLoad?: (list: CometChat.GroupMember[]) => void;\n  /**\n   * Hide the loading skeleton.\n   */\n  hideLoadingState?: boolean;\n  /**\n   * Hide the Group type (public/private/password).\n   */\n  groupTypeVisibility?: boolean;\n  /**\n   * A function to **append** more menu items on top of the default menu items for a group.\n   * @param group - The group object.\n   * @returns An array of menu items that will be appended to the default list\n   *          (note: if you have no defaults, these become the entire set).\n   */\n  addOptions?: (group: CometChat.Group) => MenuItemInterface[];\n  /**\n   * A function to **replace** the default menu items entirely for a group.\n   * @param group - The group object.\n   * @returns An array of menu items (with text, onPress, etc.).\n   */\n  options?: (group: CometChat.Group) => MenuItemInterface[];\n}\n\n/**\n * CometChatGroups renders a list of groups with search, selection mode,\n * error/empty/loading views, and a long-press tooltip menu (if you provide menu items).\n */\nexport const CometChatGroups = React.forwardRef((props: CometChatGroupsInterface, ref: any) => {\n  const { t } = useCometChatTranslation();\n\n  const {\n    AppBarOptions,\n    style = {},\n    searchPlaceholderText = t(\"SEARCH\"),\n    showBackButton = false,\n    selectionMode = \"none\",\n    onSelection = () => {},\n    onSubmit,\n    hideSearch = false,\n    EmptyView,\n    ErrorView,\n    LoadingView,\n    groupsRequestBuilder,\n    searchRequestBuilder,\n    onError,\n    onBack,\n    onItemPress,\n    onItemLongPress,\n    SubtitleView,\n    ItemView,\n    hideError = false,\n    searchKeyword = \"\",\n    hideLoadingState,\n    groupTypeVisibility = true,\n    addOptions,\n    options,\n    onEmpty,\n    onLoad,\n    hideHeader,\n    ...newProps\n  } = props;\n\n  // Theme references.\n  const theme = useTheme();\n  const { mode } = useThemeInternal();\n  \n  // Internal ref to CometChatList methods.\n  const groupListRef = useRef<CometChatListActionsInterface>(null);\n\n  // States\n  const [hideSearchError, setHideSearchError] = useState(false);\n  const [selectedGroups, setSelectedGroups] = useState<CometChat.Group[]>([]);\n\n  // Tooltip state\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const [selectedGroup, setSelectedGroup] = useState<CometChat.Group | null>(null);\n  const tooltipPosition = useRef({ pageX: 0, pageY: 0 });\n\n  // Merge theme styles with any overrides.\n  const mergedStyle = deepMerge(theme.groupStyles, style);\n\n  /**\n   * Expose imperative methods via ref.\n   */\n  useImperativeHandle(ref, () => ({\n    addGroup,\n    updateGroup,\n    removeGroup,\n    getSelectedItems,\n  }));\n\n  /**\n   * Returns the currently selected group items.\n   */\n  const getSelectedItems = () => {\n    return selectedGroups;\n  };\n\n  /**\n   * Renders an empty state view when no groups are available.\n   * Also triggers `onEmpty` if provided.\n   */\n  const EmptyStateView = useCallback(() => {\n    useEffect(() => {\n      onEmpty?.();\n    }, []);\n    return (\n      <View style={{ flex: 1 }}>\n        <ErrorEmptyView\n          title={t(\"NO_GROUPS_AVAILABLE\")}\n          subTitle={t(\"ADD_CONTACTS\")}\n          Icon={\n            <Icon\n              name='user-empty-icon'\n              icon={mergedStyle.emptyStateStyle.icon}\n              color={theme.color.neutral300}\n              size={theme.spacing.spacing.s15 << 1}\n              containerStyle={{\n                marginBottom: theme.spacing.spacing.s5,\n              }}\n            />\n          }\n          containerStyle={{\n            flex: 1,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: \"10%\",\n          }}\n          titleStyle={mergedStyle.emptyStateStyle.titleStyle}\n          subTitleStyle={mergedStyle.emptyStateStyle.subTitleStyle}\n        />\n      </View>\n    );\n  }, [mergedStyle, theme]);\n\n  /**\n   * Renders the error state view.\n   * Also hides the search box while this is active.\n   */\n  const ErrorStateView = useCallback(() => {\n    useEffect(() => {\n      setHideSearchError(true); // Hide search while showing error\n    }, []);\n    if (hideError) return null;\n    return (\n      <View style={{ flex: 1 }}>\n        <ErrorEmptyView\n          title={t(\"OOPS\")}\n          subTitle={t(\"SOMETHING_WENT_WRONG\")}\n          tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n          Icon={\n            <Icon\n              name='error-state'\n              size={theme.spacing.margin.m15 << 1}\n              containerStyle={{\n                marginBottom: theme.spacing.margin.m5,\n              }}\n              icon={mergedStyle.errorStateStyle.icon}\n            />\n          }\n          containerStyle={{\n            flex: 1,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: \"10%\",\n          }}\n          titleStyle={mergedStyle.errorStateStyle.titleStyle}\n          subTitleStyle={mergedStyle.errorStateStyle.subTitleStyle}\n          RetryView={<CometChatRetryButton onPress={() => groupListRef.current?.reload()} />}\n        />\n      </View>\n    );\n  }, [mergedStyle, mode, theme, hideError]);\n\n  /**\n   * Build final list of menu items for a given group:\n   *  - If `options` is provided, it overrides everything.\n   *  - Otherwise, if `addOptions` is provided, it returns those items only as no default as of now\n   */\n  const buildMenuItems = (group: CometChat.Group): MenuItemInterface[] => {\n    if (options) {\n      return options(group);\n    }\n    if (addOptions) {\n      return addOptions(group);\n    }\n    // No default menu items, so return empty if no user-defined items.\n    return [];\n  };\n\n  /**\n   * Invoked when a group item is long pressed.\n   * If the developer passed `onItemLongPress`, call that and stop.\n   * Otherwise, show the tooltip if there are any menu items for that group.\n   */\n  const handleItemLongPress = (group: CometChat.Group, e?: any) => {\n    // Call developer callback if provided\n    if (onItemLongPress) {\n      onItemLongPress(group);\n      return;\n    }\n\n    // If user has no options / addOptions, no tooltip to show\n    const items = buildMenuItems(group);\n    if (items.length === 0) return;\n\n    // Save position for the tooltip\n    if (e && e.nativeEvent) {\n      tooltipPosition.current = {\n        pageX: e.nativeEvent.pageX,\n        pageY: e.nativeEvent.pageY,\n      };\n    } else {\n      // fallback if event coords are missing\n      tooltipPosition.current = { pageX: 200, pageY: 100 };\n    }\n\n    // Show tooltip\n    setSelectedGroup(group);\n    setTooltipVisible(true);\n  };\n\n  /**\n   * Methods below let you update/manipulate groups in the list.\n   */\n  const addGroup = (group: CometChat.Group) => {\n    groupListRef.current!.addItemToList(\n      (grp: CometChat.Group) => grp.getGuid() === group.getGuid(),\n      0\n    );\n  };\n\n  const updateGroup = (group: CometChat.Group) => {\n    groupListRef.current!.updateList((grp: CometChat.Group) => grp.getGuid() === group.getGuid());\n  };\n\n  const removeGroup = (group: CometChat.Group) => {\n    groupListRef.current?.removeItemFromList(group.getGuid());\n  };\n\n  /**\n   * Group listener callbacks below, to keep the UI synced with group changes.\n   */\n  const handleGroupMemberRemoval = (...options: any) => {\n    const group = options[3];\n    groupListRef.current!.updateList(group);\n  };\n\n  const handleGroupMemberBan = (...options: any) => {\n    const group = options[3];\n    groupListRef.current!.updateList(group);\n  };\n\n  const handleGroupMemberAddition = (...options: any) => {\n    const group = options[3];\n    groupListRef.current!.updateList(group);\n  };\n\n  const handleGroupMemberScopeChange = (...options: any) => {\n    const group = options[4];\n    groupListRef.current!.updateList(group);\n  };\n\n  /**\n   * Set up group listeners when the component mounts.\n   */\n  useEffect(() => {\n    CometChat.addGroupListener(\n      groupListenerId,\n      new CometChat.GroupListener({\n        onGroupMemberScopeChanged: (\n          message: CometChat.Action,\n          changedUser: CometChat.User,\n          newScope: CometChat.GroupMemberScope,\n          oldScope: CometChat.GroupMemberScope,\n          changedGroup: CometChat.Group\n        ) => {\n          handleGroupMemberScopeChange(message, changedUser, newScope, oldScope, changedGroup);\n        },\n        onGroupMemberKicked: (\n          message: CometChat.Action,\n          kickedUser: CometChat.User,\n          kickedBy: CometChat.User,\n          kickedFrom: CometChat.Group\n        ) => {\n          handleGroupMemberRemoval(message, kickedUser, kickedBy, kickedFrom);\n        },\n        onGroupMemberLeft: (\n          message: CometChat.Action,\n          leavingUser: CometChat.User,\n          group: CometChat.Group\n        ) => {\n          handleGroupMemberRemoval(message, leavingUser, null, group);\n        },\n        onGroupMemberBanned: (\n          message: CometChat.Action,\n          bannedUser: CometChat.User,\n          bannedBy: CometChat.User,\n          bannedFrom: CometChat.Group\n        ) => {\n          handleGroupMemberBan(message, bannedUser, bannedBy, bannedFrom);\n        },\n        onMemberAddedToGroup: (\n          message: CometChat.Action,\n          userAdded: CometChat.User,\n          userAddedBy: CometChat.User,\n          userAddedIn: CometChat.Group\n        ) => {\n          handleGroupMemberAddition(message, userAdded, userAddedBy, userAddedIn);\n        },\n        onGroupMemberJoined: (\n          message: CometChat.Action,\n          joinedUser: CometChat.User,\n          joinedGroup: CometChat.Group\n        ) => {\n          handleGroupMemberAddition(message, joinedUser, null, joinedGroup);\n        },\n      })\n    );\n\n    CometChatUIEventHandler.addGroupListener(uiEventListener, {\n      ccGroupCreated: ({ group }: { group: CometChat.Group }) => {\n        groupListRef.current?.addItemToList(group, 0);\n      },\n      ccGroupDeleted: ({ group }: { group: CometChat.Group }) => {\n        groupListRef.current?.removeItemFromList(group.getGuid());\n      },\n      ccGroupLeft: ({ leftGroup }: { leftGroup: CometChat.Group }) => {\n        leftGroup.setHasJoined(false);\n        leftGroup.setMembersCount(leftGroup.getMembersCount() - 1);\n        if (leftGroup.getType() === CometChat.GROUP_TYPE.PRIVATE) {\n          groupListRef.current?.removeItemFromList(leftGroup.getGuid());\n        } else {\n          groupListRef.current?.updateList(leftGroup);\n        }\n      },\n      ccGroupMemberKicked: ({ kickedFrom }: { kickedFrom: CometChat.Group }) => {\n        if (kickedFrom?.getType() === CometChat.GROUP_TYPE.PRIVATE) {\n          groupListRef.current?.removeItemFromList(kickedFrom.getGuid());\n        } else {\n          kickedFrom?.setHasJoined(false);\n          groupListRef.current?.updateList(kickedFrom);\n        }\n      },\n      ccOwnershipChanged: ({ group }: { group: CometChat.Group }) => {\n        groupListRef.current?.updateList(group);\n      },\n      ccGroupMemberAdded: ({ userAddedIn }: { userAddedIn: CometChat.Group }) => {\n        groupListRef.current?.updateList(userAddedIn);\n      },\n      ccGroupMemberJoined: ({ joinedGroup }: { joinedGroup: CometChat.Group }) => {\n        joinedGroup.setScope(\"participant\");\n        joinedGroup.setHasJoined(true);\n        groupListRef.current?.updateList(joinedGroup);\n      },\n    });\n\n    return () => {\n      CometChat.removeGroupListener(groupListenerId);\n      CometChatUIEventHandler.removeGroupListener(uiEventListener);\n    };\n  }, []);\n\n  return (\n    <View style={[Style.container, theme.groupStyles.containerStyle]}>\n      <CometChatList\n        hideHeader={hideHeader ?? hideHeader}\n        onItemPress={onItemPress}\n        onItemLongPress={handleItemLongPress}\n        SubtitleView={\n          SubtitleView\n            ? SubtitleView\n            : (group: CometChat.Group) => (\n                <Text\n                  style={[\n                    style.itemStyle?.subtitleStyle,\n                    theme.groupStyles.itemStyle?.subtitleStyle,\n                  ]}\n                >\n                  {group.getMembersCount() +\n                    \" \" +\n                    t(group.getMembersCount() === 1 ? \"MEMBER\" : \"MEMBERS\")}\n                </Text>\n              )\n        }\n        statusIndicatorType={(group: CometChat.Group) =>\n          !groupTypeVisibility\n            ? null\n            : (group.getType() as CometChatStatusIndicatorInterface[\"type\"])\n        }\n        title={t(\"GROUPS\")}\n        hideSearch={hideSearch ? hideSearch : hideSearchError}\n        listStyle={mergedStyle}\n        LoadingView={\n          hideLoadingState\n            ? () => <></> // will not render anything if true\n            : LoadingView\n              ? LoadingView\n              : () => <Skeleton style={mergedStyle.skeletonStyle} />\n        }\n        EmptyView={EmptyView ? EmptyView : () => <EmptyStateView />}\n        ErrorView={ErrorView ? ErrorView : () => <ErrorStateView />}\n        searchPlaceholderText={searchPlaceholderText}\n        ref={groupListRef}\n        listItemKey='guid'\n        requestBuilder={\n          (groupsRequestBuilder && groupsRequestBuilder.setSearchKeyword(searchKeyword)) ||\n          new CometChat.GroupsRequestBuilder().setLimit(30).setSearchKeyword(searchKeyword)\n        }\n        searchRequestBuilder={searchRequestBuilder}\n        AppBarOptions={AppBarOptions}\n        hideBackButton={!showBackButton}\n        selectionMode={selectionMode}\n        onSelection={onSelection}\n        onSubmit={onSubmit}\n        ItemView={ItemView}\n        onError={onError}\n        hideError={hideError}\n        onListFetched={(fetchedList: CometChat.GroupMember[]) => {\n          if (fetchedList.length === 0) {\n            onEmpty?.();\n          } else {\n            onLoad?.(fetchedList);\n          }\n        }}\n        onBack={onBack}\n        {...newProps}\n      />\n\n      {/* Tooltip Menu: only shows if selectionMode is \"none\" and items exist */}\n      {selectedGroup && selectionMode === \"none\" && tooltipVisible && (\n        <View\n          style={{\n            position: \"absolute\",\n            top: tooltipPosition.current.pageY,\n            left: tooltipPosition.current.pageX,\n            zIndex: 9999,\n          }}\n        >\n          <CometChatTooltipMenu\n            visible={tooltipVisible}\n            onClose={() => setTooltipVisible(false)}\n            onDismiss={() => setTooltipVisible(false)}\n            event={{\n              nativeEvent: tooltipPosition.current,\n            }}\n            menuItems={buildMenuItems(selectedGroup).map((menuItem) => ({\n              text: menuItem.text,\n              onPress: () => {\n                // Perform the user-defined action,\n                // then close the tooltip.\n                menuItem.onPress();\n                setTooltipVisible(false);\n              },\n              icon: menuItem?.icon,\n              textStyle: menuItem?.textStyle,\n              iconStyle: menuItem?.iconStyle,\n              iconContainerStyle: menuItem?.iconContainerStyle,\n              disabled: menuItem.disabled,\n            }))}\n          />\n        </View>\n      )}\n    </View>\n  );\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroups/GroupsStyle.ts",
    "content": "import { ColorValue, ImageSourcePropType, ViewStyle } from \"react-native\";\nimport { CometChatListStylesInterface } from \"../shared\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\n\nexport type GroupStyle = CometChatListStylesInterface & {\n  skeletonStyle: {\n    backgroundColor?: ColorValue;\n    linearGradientColors?: [string, string];\n    shimmerBackgroundColor?: ColorValue;\n    shimmerOpacity?: number;\n    speed?: number;\n    containerBackgroundColor?: ColorValue;\n  };\n  headerContainerStyle: ViewStyle;\n};\n\nexport const getGroupListStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<GroupStyle> => {\n  return {\n    headerContainerStyle: {\n      alignItems: \"flex-start\",\n      justifyContent: \"center\",\n      width: \"100%\",\n      borderRadius: 0,\n      paddingHorizontal: 0,\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      flex: 1,\n    },\n    titleSeparatorStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n      flexDirection: \"row\",\n      justifyContent: \"space-between\",\n      alignItems: \"center\",\n      width: \"100%\",\n    },\n    itemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        gap: spacing.spacing.s3,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.body.regular,\n      },\n      statusIndicatorStyle: {},\n      avatarStyle: {\n        containerStyle: {},\n        textStyle: {},\n        imageStyle: {},\n      },\n      headViewContainerStyle: {},\n      titleSubtitleContainerStyle: {\n        alignSelf: \"center\",\n      },\n      trailingViewContainerStyle: {},\n    },\n    selectionCancelStyle: {},\n    confirmSelectionStyle: {},\n    loadingIconTint: color.primary,\n    sectionHeaderTextStyle: {\n      display: \"none\",\n    },\n    onlineStatusColor: \"green\",\n    titleViewStyle: {\n      paddingVertical: spacing.spacing.s3,\n      paddingLeft: spacing.spacing.s3,\n      margin: spacing.spacing.s0,\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      ...typography.heading1.bold,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    searchStyle: {\n      textStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.regular,\n        textAlignVertical: \"center\",\n        paddingVertical: 0,\n        height: spacing.spacing.s7,\n      },\n      containerStyle: {\n        backgroundColor: color.background3,\n        paddingVertical: spacing.spacing.s3,\n        marginTop: spacing.spacing.s3,\n        width: \"95%\",\n        gap: spacing.spacing.s1,\n        alignContent: \"space-around\",\n        alignSelf: \"center\",\n        flexDirection: \"row\",\n        alignItems: \"center\",\n      },\n      icon: undefined,\n      iconStyle: {\n        tintColor: color.iconSecondary,\n      },\n      placehodlerTextStyle: {\n        color: color.textTertiary,\n      },\n    },\n    emptyStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\" as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: \"center\",\n        display: \"none\",\n        alignItems: \"center\",\n        padding: spacing.padding.p3,\n      },\n    },\n    errorStateStyle: {\n      containerStyle: {\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n        flexDirection: \"column\",\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    },\n    skeletonStyle: {\n      containerBackgroundColor: color.background1,\n      backgroundColor: \"transparent\",\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n    },\n  };\n};\n\nexport const getUserGroupStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<GroupStyle> => {\n  return deepMerge(getGroupListStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      backgroundColor: \"transparent\",\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n      containerBackgroundColor: color.background1,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroups/Skeleton.tsx",
    "content": "import React, { useEffect, useMemo, useRef } from \"react\";\nimport {\n  Animated,\n  Dimensions,\n  Easing,\n  ScrollView,\n  StyleSheet,\n  View,\n} from \"react-native\";\nimport Svg, { Circle, Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../theme\";\nimport { CometChatTheme } from \"../theme/type\";\n\n/**\n * React‑Native skeleton list with animated shimmer.\n *\n * @remarks\n * The component respects the brand/theme defaults defined in\n * `theme.groupStyles.skeletonStyle` but allows **per‑instance overrides** via the\n * `style` prop.\n *\n * @example\n * ```tsx\n * <Skeleton\n *   style={{\n *     linearGradientColors: [\"#eee\", \"#ddd\"],\n *     backgroundColor: \"#444\",\n *   }}\n * />\n * ```\n */\nexport interface SkeletonProps {\n  /**\n   * Partial style overrides.  Any omitted property falls back to the theme\n   * default.\n   */\n  style?: Partial<SkeletonStyle>;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Theme typing helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Convenience alias for the skeleton style slice in the theme object. */\ntype SkeletonStyle = CometChatTheme[\"groupStyles\"][\"skeletonStyle\"];\n\n/**\n * Utility to resolve a style value with **theme‑fallback**.\n *\n * @param key – The style property being resolved.\n * @param overrides – Optional user overrides (`props.style`).\n * @param theme – Current theme object.\n * @returns The resolved style value.\n */\nfunction resolveStyleValue<K extends keyof SkeletonStyle>(\n  key: K,\n  overrides: Partial<SkeletonStyle> | undefined,\n  theme: CometChatTheme\n): SkeletonStyle[K] {\n  return (overrides?.[key] as SkeletonStyle[K]) ?? theme.groupStyles.skeletonStyle[key];\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Layout constants – tweak here if the design changes\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst { width: SCREEN_WIDTH } = Dimensions.get(\"window\");\n\nconst PADDING = 20;\nconst AVATAR_RADIUS = 25;\nconst LIST_ITEM_HEIGHT = 25;\nconst LIST_ITEM_SUBTITLE_HEIGHT = 20;\nconst LIST_ITEM_SUBTITLE_SPACING = 10;\nconst LIST_ITEM_SPACING = 30;\nconst LIST_ITEM_COUNT = 14;\n\n/** Total SVG height required to render all placeholder rows. */\nconst TOTAL_HEIGHT =\n  PADDING +\n  LIST_ITEM_COUNT *\n    (LIST_ITEM_HEIGHT + LIST_ITEM_SUBTITLE_SPACING + LIST_ITEM_SUBTITLE_HEIGHT + LIST_ITEM_SPACING);\n\n// ──────────────────────────────────────────────────────────────────────────────\n// SVG building blocks\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Generates the repetitive row shapes used by both bottom and top layers.\n *\n * @param fill – The fill used for rectangles & circles in this layer.\n */\nconst useRowShapes = (fill: string) =>\n  useMemo(\n    () =>\n      Array.from({ length: LIST_ITEM_COUNT }).map((_, index) => {\n        const baseY =\n          PADDING +\n          index *\n            (LIST_ITEM_HEIGHT + LIST_ITEM_SUBTITLE_SPACING + LIST_ITEM_SUBTITLE_HEIGHT + LIST_ITEM_SPACING) -\n          10;\n\n        return (\n          <React.Fragment key={index}>\n            <Circle\n              cx={PADDING + AVATAR_RADIUS}\n              cy={baseY + AVATAR_RADIUS}\n              r={AVATAR_RADIUS}\n              fill={fill}\n            />\n            <Rect\n              x={PADDING + 2 * AVATAR_RADIUS + 12}\n              y={baseY}\n              width={SCREEN_WIDTH - (PADDING + 2 * AVATAR_RADIUS + 12 + PADDING)}\n              height={LIST_ITEM_HEIGHT}\n              rx={LIST_ITEM_HEIGHT / 2}\n              fill={fill}\n            />\n            <Rect\n              x={PADDING + 2 * AVATAR_RADIUS + 12}\n              y={baseY + LIST_ITEM_HEIGHT + LIST_ITEM_SUBTITLE_SPACING}\n              width={\n                (SCREEN_WIDTH - (PADDING + 2 * AVATAR_RADIUS + 12 + PADDING)) * 0.6 /* 60% width */\n              }\n              height={LIST_ITEM_SUBTITLE_HEIGHT}\n              rx={LIST_ITEM_SUBTITLE_HEIGHT / 2}\n              fill={fill}\n            />\n          </React.Fragment>\n        );\n      }),\n    [fill]\n  );\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Component implementation\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport const Skeleton: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n\n  /** Resolved style helpers (with theme‑fallback). */\n  const get = <K extends keyof SkeletonStyle>(key: K) =>\n    resolveStyleValue(key, style, theme);\n\n  // Animated shimmer setup -----------------------------------------------------\n  const shimmerTranslate = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const duration = 1000 / get(\"speed\")!;\n    const loop = Animated.loop(\n      Animated.timing(shimmerTranslate, {\n        toValue: 1,\n        duration,\n        easing: Easing.linear,\n        useNativeDriver: false, // SVG cannot use native driver\n      })\n    );\n\n    loop.start();\n    return () => loop.stop(); // <‑‑ Prevent memory leaks on unmount\n  }, [get(\"speed\"), shimmerTranslate]);\n\n  // Interpolated translation across the screen width\n  const translateX = shimmerTranslate.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-SCREEN_WIDTH * 2, SCREEN_WIDTH],\n  });\n\n  // SVG layers ---------------------------------------------------------------\n  const rowShapesBottom = useRowShapes(\"url(#gradient)\" /* gradient fill */);\n  const rowShapesTop = useRowShapes(get(\"backgroundColor\") as string);\n\n  // ──────────────────────────────────────────────────────────────────────────\n  // Render\n  // ──────────────────────────────────────────────────────────────────────────\n\n  // Note: Providing a backgroundColor override will be used for the top mask layer,\n  // which may hide the gradient effect defined by linearGradientColors.\n  return (\n    <ScrollView\n      scrollEnabled={false}\n      showsVerticalScrollIndicator={false}\n      style={{ backgroundColor: get(\"containerBackgroundColor\") }}\n    >\n      {/* Bottom gradient layer */}\n      <Svg height={TOTAL_HEIGHT} width={SCREEN_WIDTH} fill=\"none\" preserveAspectRatio=\"xMidYMid meet\">\n        {/* Reusable linear gradient */}\n        <Defs>\n          <LinearGradient id=\"gradient\" x1=\"0\" y1=\"0\" x2={SCREEN_WIDTH} y2=\"0\" gradientUnits=\"userSpaceOnUse\">\n            <Stop stopColor={get(\"linearGradientColors\")![0]} />\n            <Stop offset=\"1\" stopColor={get(\"linearGradientColors\")![1]} />\n          </LinearGradient>\n        </Defs>\n        {rowShapesBottom}\n      </Svg>\n\n      {/* Shimmer highlight (runs twice for smoother effect) */}\n      {[0, SCREEN_WIDTH / 2].map((offset) => (\n        <Animated.View\n          // eslint‑disable‑next‑line react/no-array-index-key\n          key={offset}\n          style={[\n            styles.shimmer,\n            {\n              transform: [\n                { translateX: Animated.add(translateX, offset) },\n                { translateY: -20 },\n                { rotate: \"15deg\" },\n              ],\n              backgroundColor: get(\"shimmerBackgroundColor\"),\n              opacity: get(\"shimmerOpacity\"),\n            },\n          ]}\n        />\n      ))}\n\n      {/* Top solid layer (masks shimmer to placeholder shapes) */}\n      <View style={StyleSheet.absoluteFill} pointerEvents=\"none\">\n        <Svg height={TOTAL_HEIGHT} width={SCREEN_WIDTH} fill=\"none\" preserveAspectRatio=\"xMidYMid meet\">\n          {rowShapesTop}\n        </Svg>\n      </View>\n    </ScrollView>\n  );\n};\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Styles\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst styles = StyleSheet.create({\n  shimmer: {\n    position: \"absolute\",\n    width: \"25%\", // Narrow highlight bar\n    top: 0,\n    bottom: 0,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroups/index.ts",
    "content": "import { CometChatGroups, CometChatGroupsInterface } from \"./CometChatGroups\";\nimport { getGroupListStyleLight, getUserGroupStyleDark } from \"./GroupsStyle\";\n\nexport { CometChatGroups, getGroupListStyleLight, getUserGroupStyleDark };\nexport type { CometChatGroupsInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroups/resources/index.ts",
    "content": "import backIcon from \"./back.png\";\nimport checkIcon from \"./check.png\";\nimport closeIcon from \"./close.png\";\nimport createIcon from \"./create.png\";\nimport moreIcon from \"./more.png\";\nimport passwordGroupIcon from \"./password.png\";\nimport privateGroupIcon from \"./private.png\";\nimport rightTickIcon from \"./rightTick.png\";\nimport searchIcon from \"./search.png\";\nimport loadingIcon from \"./spineer.png\";\n\nexport {\n  backIcon,\n  checkIcon,\n  closeIcon,\n  createIcon,\n  loadingIcon,\n  moreIcon,\n  passwordGroupIcon,\n  privateGroupIcon,\n  rightTickIcon,\n  searchIcon,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatGroups/style.ts",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const Style = StyleSheet.create({\n  container: {\n    height: \"100%\",\n  },\n  listContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  listStyle: {\n    flex: 1,\n    backgroundColor: \"transparent\",\n    marginStart: 8,\n    marginEnd: 8,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageComposer/CometChatMessageComposer.tsx",
    "content": "import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from \"react\";\nimport { Keyboard } from \"react-native\";\nimport {\n  ColorValue,\n  Dimensions,\n  ImageSourcePropType,\n  ImageStyle,\n  InteractionManager,\n  KeyboardAvoidingView,\n  KeyboardAvoidingViewProps,\n  Modal,\n  NativeModules,\n  Platform,\n  Text,\n  TouchableOpacity,\n  TouchableWithoutFeedbackProps,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { startStreamingForRunId, stopStreamingForRunId, streamingState$ } from \"../shared/services/stream-message.service\";\nimport {\n  CometChatActionSheet,\n  CometChatBottomSheet,\n  CometChatMentionsFormatter,\n  CometChatMessageInput,\n  CometChatMessagePreview,\n  CometChatSuggestionList,\n  CometChatTextFormatter,\n  CometChatUIKit,\n  CometChatUrlsFormatter,\n  SuggestionItem,\n} from \"../shared\";\nimport { Style } from \"./styles\";\n//@ts-ignore\nimport { CheckPropertyExists } from \"../shared/helper/functions\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { ChatConfigurator, CometChatSoundManager } from \"../shared\";\nimport { commonVars } from \"../shared/base/vars\";\nimport {\n  ConversationOptionConstants,\n  MentionsTargetElement,\n  MentionsVisibility,\n  MessageTypeConstants,\n  ReceiverTypeConstants,\n  ViewAlignment,\n} from \"../shared/constants/UIKitConstants\";\nimport { MessageEvents } from \"../shared/events\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CometChatMessageEvents } from \"../shared/events/CometChatMessageEvents\";\nimport { CometChatMessageComposerAction, DeepPartial } from \"../shared/helper/types\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { CometChatSendButtonView } from \"../shared/views/CometChatSendButtonView/CometChatSendButtonView\";\nimport {\n  getUnixTimestampInMilliseconds,\n  messageStatus,\n} from \"../shared/utils/CometChatMessageHelper\";\nimport { CommonUtils } from \"../shared/utils/CommonUtils\";\nimport { isCursorWithinMentionRange, getMentionRangeAtCursor } from \"../shared/utils/MentionUtils\";\nimport { permissionUtil } from \"../shared/utils/PermissionUtil\";\nimport { CometChatMediaRecorder } from \"../shared/views/CometChatMediaRecorder\";\nimport { useTheme } from \"../theme\";\nimport { ICONS } from \"./resources\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\ntype MentionOverlap = {\n  key: string;\n  value: SuggestionItem;\n  start: number;\n  end: number;\n};\n\nconst { FileManager, CommonUtil } = NativeModules;\n\nconst uiEventListenerShow = \"uiEvent_show_\" + new Date().getTime();\nconst uiEventListenerHide = \"uiEvent_hide_\" + new Date().getTime();\n\n\n\nconst AttachIconButton = (props: {\n  onPress: TouchableWithoutFeedbackProps[\"onPress\"];\n  icon: ImageSourcePropType | JSX.Element;\n  iconStyle: ImageStyle;\n}) => {\n  return (\n    <TouchableOpacity onPress={props.onPress}>\n      <Icon\n        name='add-circle'\n        icon={props.icon}\n        color={props.iconStyle.tintColor}\n        height={props.iconStyle.height}\n        width={props.iconStyle.width}\n        imageStyle={props.iconStyle}\n      />\n    </TouchableOpacity>\n  );\n};\n\nconst ActionSheetBoard = (props: any) => {\n  const { shouldShow = false, onClose = () => {}, options = [], sheetRef, style } = props;\n  return (\n    <CometChatBottomSheet\n      style={{ maxHeight: Dimensions.get(\"window\").height * 0.49 }}\n      ref={sheetRef}\n      onClose={onClose}\n      isOpen={shouldShow}\n      doNotOccupyEntireHeight\n    >\n      <CometChatActionSheet actions={options} style={style} />\n    </CometChatBottomSheet>\n  );\n};\n\nconst RecordAudio = (props: any) => {\n  const {\n    shouldShow = false,\n    onClose = () => {},\n    options = [],\n    cometChatBottomSheetStyle = {},\n    sheetRef,\n    onPause = () => {},\n    onPlay = () => {},\n    onSend = (recordedFile: String) => {},\n    onStop = (recordedFile: String) => {},\n    onStart = () => {},\n    mediaRecorderStyle,\n    ...otherProps\n  } = props;\n  return (\n    <CometChatBottomSheet\n      ref={sheetRef}\n      onClose={onClose}\n      style={cometChatBottomSheetStyle}\n      isOpen={shouldShow}\n      doNotOccupyEntireHeight\n    >\n      <CometChatMediaRecorder\n        onClose={onClose}\n        onPause={onPause}\n        onPlay={onPlay}\n        onSend={onSend}\n        onStop={onStop}\n        onStart={onStart}\n        style={mediaRecorderStyle}\n      />\n    </CometChatBottomSheet>\n  );\n};\n\ntype Enumerate<N extends number, Acc extends number[] = []> = Acc[\"length\"] extends N\n  ? Acc[number]\n  : Enumerate<N, [...Acc, Acc[\"length\"]]>;\n\ntype IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;\n\n/**\n * Properties for the CometChat message composer component.\n */\nexport interface CometChatMessageComposerInterface {\n  /**\n   * Message composer identifier.\n   *\n   * @type {string | number}\n   */\n  id?: string | number;\n\n  /**\n   * CometChat SDK's user object.\n   *\n   * @type {CometChat.User}\n   */\n  user?: CometChat.User;\n\n  /**\n   * CometChat SDK's group object.\n   *\n   * @type {CometChat.Group}\n   */\n  group?: CometChat.Group;\n\n  /**\n   * Flag to turn off sound for outgoing messages.\n   *\n   * @type {boolean}\n   */\n  disableSoundForOutgoingMessages?: boolean;\n\n  /**\n   * Custom audio sound to be played while sending messages.\n   *\n   * @type {*}\n   */\n  customSoundForOutgoingMessage?: any;\n\n  /**\n   * Flag to disable typing events.\n   *\n   * @type {boolean}\n   */\n  disableTypingEvents?: boolean;\n\n  /**\n   * Initial text to be displayed in the composer.\n   *\n   * @type {string}\n   */\n  initialComposertext?: string;\n\n  /**\n   * Renders a preview section at the top of the composer.\n   *\n   * @param {Object} props - The component properties.\n   * @param {CometChat.User} [props.user] - The user object.\n   * @param {CometChat.Group} [props.group] - The group object.\n   * @returns {JSX.Element} The header view element.\n   */\n  HeaderView?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => JSX.Element;\n\n  /**\n   * Callback triggered when the input text changes.\n   *\n   * @param {string} text - The updated text.\n   */\n  onTextChange?: (text: string) => void;\n\n  /**\n   * Returns the attachment options for the composer.\n   *\n   * @param {Object} props - The function properties.\n   * @param {CometChat.User} [props.user] - The user object.\n   * @param {CometChat.Group} [props.group] - The group object.\n   * @param {Map<any, any>} props.composerId - The composer identifier as a Map.\n   * @returns {CometChatMessageComposerAction[]} An array of composer actions.\n   */\n  attachmentOptions?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: Map<any, any>;\n  }) => CometChatMessageComposerAction[];\n\n  /**\n   * Replaces the default Auxiliary Button.\n   *\n   * @param {Object} props - The function properties.\n   * @param {CometChat.User} [props.user] - The user object.\n   * @param {CometChat.Group} [props.group] - The group object.\n   * @param {string | number} props.composerId - The composer identifier.\n   * @returns {JSX.Element} A custom auxiliary button component.\n   */\n  AuxiliaryButtonView?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: string | number;\n  }) => JSX.Element;\n\n  /**\n   * Replaces the default Send Button.\n   *\n   * @param {Object} props - The function properties.\n   * @param {CometChat.User} [props.user] - The user object.\n   * @param {CometChat.Group} [props.group] - The group object.\n   * @param {string | number} props.composerId - The composer identifier.\n   * @returns {JSX.Element} A custom send button component.\n   */\n  SendButtonView?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: string | number;\n  }) => JSX.Element;\n\n  /**\n   * Message id required for threaded messages.\n   *\n   * @type {string | number}\n   */\n  parentMessageId?: string | number;\n\n  /**\n   * Custom styles for the message composer component.\n   */\n  style?: DeepPartial<CometChatTheme[\"messageComposerStyles\"]>;\n\n  /**\n   * Flag to hide the voice recording button.\n   *\n   * @type {boolean}\n   */\n  hideVoiceRecordingButton?: boolean;\n\n  /**\n   * Callback triggered when the send button is pressed.\n   *\n   * @param {CometChat.BaseMessage} message - The base message object.\n   */\n  onSendButtonPress?: (message: CometChat.BaseMessage) => void;\n\n  /**\n   * Callback triggered when an error occurs.\n   *\n   * @param {CometChat.CometChatException} error - The error object.\n   */\n  onError?: (error: CometChat.CometChatException) => void;\n\n  /**\n   * Override properties for the KeyboardAvoidingView.\n   */\n  keyboardAvoidingViewProps?: KeyboardAvoidingViewProps;\n\n  /**\n   * Collection of text formatter classes to apply custom formatting.\n   *\n   * @type {Array<CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter>}\n   */\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n\n  /**\n   * Flag to disable mention functionality.\n   */\n  disableMentions?: boolean;\n\n  /**\n   * Flag to disable the special group mention (@all / @channel etc.).\n   * When true, the alias suggestion will not appear.\n   * @default false\n   */\n  disableMentionAll?: boolean;\n\n  /**\n   * Custom alias label for the group-wide mention.\n   * Rendered as @Alias in composer and bubbles.\n   * Internally stored as <@all:Alias>.\n   * @default \"all\"\n   */\n  mentionAllLabel?: string;\n\n  /**\n   * Controls image quality when taking pictures from the camera.\n   * A value of 100 means no compression.\n   *\n   * @default 20\n   * @type {IntRange<1, 100>}\n   */\n  imageQuality?: IntRange<1, 100>;\n\n  /**\n   * If true, hides the camera option from the attachment options.\n   */\n  hideCameraOption?: boolean;\n\n  /**\n   * If true, hides the image attachment option from the attachment options.\n   */\n  hideImageAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the video attachment option from the attachment options.\n   */\n  hideVideoAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the audio attachment option from the attachment options.\n   */\n  hideAudioAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the file/document attachment option from the attachment options.\n   */\n  hideFileAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the polls option from the attachment options.\n   */\n  hidePollsAttachmentOption?: boolean;\n\n  /**\n   * If true, hides the collaborative document option (e.g., shared document editing).\n   */\n  hideCollaborativeDocumentOption?: boolean;\n\n  /**\n   * If true, hides the collaborative whiteboard option.\n   */\n  hideCollaborativeWhiteboardOption?: boolean;\n\n  /**\n   * If true, hides the entire attachment button from the composer.\n   */\n  hideAttachmentButton?: boolean;\n\n  /**\n   * If true, hides the stickers button from the composer.\n   */\n  hideStickersButton?: boolean;\n\n  /**\n   * If true, hides the send button from the composer.\n   */\n  hideSendButton?: boolean;\n\n  /**\n   * If true, hides all auxiliary buttons (such as voice input, GIFs, or other plugin buttons).\n   */\n  hideAuxiliaryButtons?: boolean;\n  /**\n   * Returns the attachment options for the composer.\n   *\n   * @param {Object} props - The function properties.\n   * @param {CometChat.User} [props.user] - The user object.\n   * @param {CometChat.Group} [props.group] - The group object.\n   * @param {Map<any, any>} props.composerId - The composer identifier as a Map.\n   * @returns {CometChatMessageComposerAction[]} An array of composer actions.\n   */\n  addAttachmentOptions?: ({\n    user,\n    group,\n    composerId,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    composerId: Map<any, any>;\n  }) => CometChatMessageComposerAction[];\n  /**\n   * Determines the alignment of auxiliary buttons (e.g., sticker button).\n   * Can be either \"left\" or \"right\".\n   *\n   * @default \"left\"\n   */\n  auxiliaryButtonsAlignment?: \"left\" | \"right\";\n  /**\n   * Custom send button view for AI agents (only applies to @agentic users)\n   */\n  AgentSendButtonView?: React.ComponentType<{\n    isButtonDisabled: boolean;\n    composerRef: any;\n  }>;\n}\n\nexport const CometChatMessageComposer = React.forwardRef(\n  (props: CometChatMessageComposerInterface, ref) => {\n    const editMessageListenerID = \"editMessageListener_\" + new Date().getTime();\n    const replyMessageListenerID = \"replyMessageListener_\" + new Date().getTime();\n    const UiEventListenerID = \"UiEventListener_\" + new Date().getTime();\n\n    const theme = useTheme();\n    const {t} = useCometChatTranslation()\n    const {\n      id,\n      user,\n      group,\n      disableSoundForOutgoingMessages = true,\n      customSoundForOutgoingMessage,\n      disableTypingEvents: propDisableTypingEvents,\n      initialComposertext,\n      HeaderView,\n      onTextChange,\n      attachmentOptions,\n      AuxiliaryButtonView,\n      SendButtonView,\n      parentMessageId,\n      style = {},\n      onSendButtonPress,\n      onError,\n      hideVoiceRecordingButton: propHideVoiceRecordingButton,\n      keyboardAvoidingViewProps,\n      textFormatters,\n      disableMentions: propDisableMentions,\n      disableMentionAll = false,\n      mentionAllLabel = \"all\",\n      imageQuality = 20,\n      hideCameraOption = false,\n      hideImageAttachmentOption = false,\n      hideVideoAttachmentOption = false,\n      hideAudioAttachmentOption = false,\n      hideFileAttachmentOption = false,\n      hidePollsAttachmentOption = false,\n      hideCollaborativeDocumentOption = false,\n      hideCollaborativeWhiteboardOption = false,\n      hideAttachmentButton: propHideAttachmentButton = false,\n      hideStickersButton: propHideStickersButton = false,\n      hideSendButton = false,\n      hideAuxiliaryButtons = false,\n      addAttachmentOptions,\n      auxiliaryButtonsAlignment = \"left\",\n      AgentSendButtonView,\n    } = props;\n\n    // Helper function to check if user is agentic\n    const isAgenticUser = useCallback(() => {\n      return user?.getRole?.() === '@agentic';\n    }, [user]);\n\n    // Apply automatic hiding for @agentic users\n    const disableTypingEvents = isAgenticUser() ? true : propDisableTypingEvents;\n    const disableMentions = isAgenticUser() ? true : propDisableMentions;\n    const hideAttachmentButton = isAgenticUser() ? true : propHideAttachmentButton;\n    const hideStickersButton = isAgenticUser() ? true : propHideStickersButton;\n    const hideVoiceRecordingButton = isAgenticUser() ? true : propHideVoiceRecordingButton;\n\n    const composerIdMap = new Map().set(\"parentMessageId\", parentMessageId);\n    const parentMessageIdRef = useRef<string | null>(null);\n\n    const mergedComposerStyle: CometChatTheme[\"messageComposerStyles\"] = useMemo(() => {\n      const mergedStyle = deepMerge(theme.messageComposerStyles, style);\n      if (isAgenticUser()) {\n        return {\n          ...mergedStyle,\n          messageInputStyles: {\n            ...mergedStyle.messageInputStyles,\n            dividerStyle: {\n              display: 'none'\n            }\n          }\n        };\n      }\n      return mergedStyle;\n    }, [theme, style, isAgenticUser]);\n\n\n\n    const loggedInUser = React.useRef<any>({});\n    const chatWith = React.useRef<any>(null);\n    const chatWithId = React.useRef<any>(null);\n    const messageInputRef = React.useRef<any>(null);\n    const chatRef = React.useRef<any>(chatWith);\n    const inputValueRef = React.useRef<any>(null);\n    const plainTextInput = React.useRef<string>(initialComposertext || \"\");\n    let mentionMap = React.useRef<Map<string, SuggestionItem>>(new Map());\n    let trackingCharacters = React.useRef<string[]>([]);\n    let allFormatters = React.useRef<\n      Map<string, CometChatTextFormatter | CometChatMentionsFormatter>\n    >(new Map());\n    let activeCharacter = React.useRef(\"\");\n    let searchStringRef = React.useRef(\"\");\n\n    const [selectionPosition, setSelectionPosition] = React.useState<any>({});\n    const [inputMessage, setInputMessage] = React.useState<string | JSX.Element>(\n      initialComposertext || \"\"\n    );\n    const [isStreaming, setIsStreaming] = React.useState(false);\n\n    useEffect(() => {\n      const sub = streamingState$.subscribe((streaming) => {\n        setIsStreaming(streaming);\n        if (!streaming) setShowStopButton(false);\n      });\n      return () => sub.unsubscribe();\n    }, []);\n    const [showActionSheet, setShowActionSheet] = React.useState(false);\n    const [showRecordAudio, setShowRecordAudio] = React.useState(false);\n    const [actionSheetItems, setActionSheetItems] = React.useState<any[]>([]);\n    const [messagePreview, setMessagePreview] = React.useState<any>();\n    const [replyMessage, setReplyMessage] = React.useState<any>();\n    // Ref to hold current replyMessage value to avoid stale closures in attachment handlers\n    const replyMessageRef = React.useRef(replyMessage);\n    const [CustomView, setCustomView] = React.useState(null);\n    const [CustomViewHeader, setCustomViewHeader] = React.useState<React.FC | React.ReactNode>(\n      null\n    );\n    const [CustomViewFooter, setCustomViewFooter] = React.useState<React.FC | React.ReactNode>();\n    const [isVisible, setIsVisible] = React.useState(false);\n    const [kbOffset, setKbOffset] = React.useState(59);\n    const [showMentionList, setShowMentionList] = React.useState(false);\n    const [mentionsSearchData, setMentionsSearchData] = React.useState<Array<SuggestionItem>>([]);\n    const [suggestionListLoader, setSuggestionListLoader] = React.useState(false);\n    const [warningMessage, setWarningMessage] = React.useState(\"\");\n    const [originalText, setOriginalText] = React.useState<string>(\"\");\n    const [hasEdited, setHasEdited] = React.useState<boolean>(false);\n    const [plainText, setPlainText] = React.useState(initialComposertext ?? \"\");\n    const bottomSheetRef = React.useRef<any>(null);\n    const [showStopButton, setShowStopButton] = React.useState(false);\n    const [isSendButtonDisabledForDelay, setIsSendButtonDisabledForDelay] = React.useState(false);\n    const sendButtonDelayTimer = React.useRef<NodeJS.Timeout | null>(null);\n\n    const closeReplyPreview = useCallback(() => {\n      setReplyMessage(null);\n    }, []);\n\n    // Reset streaming state when component mounts \n    const defaultAuxiliaryButtonOptions = useMemo(() => {\n      if (hideAuxiliaryButtons) return [];\n      return ChatConfigurator.getDataSource().getAuxiliaryOptions(user, group, composerIdMap, {\n        stickerIcon: mergedComposerStyle.stickerIcon as ImageSourcePropType | JSX.Element,\n        stickerIconStyle: mergedComposerStyle.stickerIconStyle as {\n          active: ImageStyle;\n          inactive: ImageStyle;\n        },\n        hideStickersButton,\n        replyToMessage: replyMessage?.message,\n        closeReplyPreview,\n      });\n    }, [mergedComposerStyle, hideStickersButton, hideAuxiliaryButtons, replyMessage, closeReplyPreview]);\n\n    useEffect(() => {\n      setShowStopButton(false);\n      setIsStreaming(false);\n\n      // Cleanup function to clear the delay timer on unmount\n      return () => {\n        if (sendButtonDelayTimer.current) {\n          clearTimeout(sendButtonDelayTimer.current);\n        }\n      };\n    }, []);\n\n    useLayoutEffect(() => {\n      if (Platform.OS === \"ios\") {\n        if (Number.isInteger(commonVars.safeAreaInsets.top)) {\n          setKbOffset(commonVars.safeAreaInsets.top ?? 0);\n          return;\n        }\n        CommonUtil.getSafeAreaInsets().then((res: any) => {\n          if (Number.isInteger(res.top)) {\n            commonVars.safeAreaInsets.top = res.top;\n            commonVars.safeAreaInsets.bottom = res.bottom;\n            setKbOffset(res.top);\n          }\n        });\n      }\n    }, []);\n\n    const isTyping = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n    /**\n     * Event callback\n     */\n    React.useImperativeHandle(ref, () => ({\n      previewMessageForEdit: previewMessage,\n      sendTextMessage,\n      getText: () => messageInputRef.current?.getText?.() ?? \"\",\n      clear: () => messageInputRef.current?.clear?.(),\n      resetStreaming: () => {\n        setShowStopButton(false);\n        setIsStreaming(false);\n        try {\n          stopStreamingForRunId();\n        } catch (error) {\n          // console.log(error); // Removed debug log\n        }\n      }\n    }));\n\n    useLayoutEffect(() => {\n      if (warningMessage) {\n        setCustomViewHeader(\n          <View\n            style={\n              {\n                flexDirection: \"row\",\n                alignItems: \"center\",\n                padding: theme.spacing.padding.p2,\n                borderRadius: mergedComposerStyle?.containerStyle?.borderRadius,\n                backgroundColor: mergedComposerStyle?.containerStyle?.backgroundColor,\n                borderColor: mergedComposerStyle?.containerStyle?.borderColor,\n                borderWidth: mergedComposerStyle?.containerStyle?.borderWidth,\n                marginBottom: 2,\n              } as ViewStyle\n            }\n          >\n            <Icon name='info-fill' color={theme.color.error}></Icon>\n            <Text\n              style={{\n                marginLeft: 5,\n                color: theme.color.error,\n                ...theme.typography.caption1.regular,\n              }}\n            >\n              {warningMessage}\n            </Text>\n          </View>\n        );\n        return;\n      }\n      setCustomViewHeader(null);\n    }, [warningMessage, theme]);\n\n    // Keep replyMessageRef in sync with replyMessage state\n    useEffect(() => {\n      replyMessageRef.current = replyMessage;\n    }, [replyMessage]);\n\n    const previewMessage = ({ message, status }: any) => {\n      if (status === messageStatus.inprogress) {\n        // Don't clear reply preview here - it needs to remain available for sendMediaMessage\n        // The reply preview will be cleared after successful send in sendMediaMessage (line 1127)\n\n        let textComponents = message?.text;\n\n        let rawText = message?.text;\n\n        let users: any = {};\n        let regexes: Array<RegExp> = [];\n\n        allFormatters.current.forEach((formatter: CometChatTextFormatter, key) => {\n          formatter.handleComposerPreview(message);\n          if (!regexes.includes(formatter.getRegexPattern())) {\n            regexes.push(formatter.getRegexPattern());\n          }\n          let suggestionUsers = formatter.getSuggestionItems();\n          suggestionUsers.forEach((item) => (users[item.underlyingText] = item));\n          let resp = formatter.getFormattedText(textComponents);\n          if (formatter instanceof CometChatMentionsFormatter) {\n            getMentionLimitView(formatter);\n          }\n\n          textComponents = resp;\n        });\n\n        let edits: any = [];\n\n        regexes.forEach((regex) => {\n          let match: any;\n          while ((match = regex.exec(rawText)) !== null) {\n            const user = users[match[0]];\n            if (user) {\n              edits.push({\n                startIndex: match.index,\n                endIndex: regex.lastIndex,\n                replacement: user.promptText,\n                user,\n              });\n            }\n          }\n        });\n\n        // Sort edits by startIndex to apply them in order\n        edits.sort((a: any, b: any) => a.startIndex - b.startIndex);\n\n        plainTextInput.current = getPlainString(message?.text, edits);\n        setPlainText(plainTextInput.current);\n        setOriginalText(plainTextInput.current.trim());\n        setHasEdited(false);\n        const hashMap = new Map();\n        let offset = 0; // Tracks shift in position due to replacements\n\n        edits.forEach((edit: any) => {\n          const adjustedStartIndex = edit.startIndex + offset;\n          rawText =\n            rawText.substring(0, adjustedStartIndex) +\n            edit.replacement +\n            rawText.substring(edit.endIndex);\n          offset += edit.replacement.length - (edit.endIndex - edit.startIndex);\n          const rangeKey = `${adjustedStartIndex}_${adjustedStartIndex + edit.replacement.length}`;\n          hashMap.set(rangeKey, edit.user);\n        });\n\n        mentionMap.current = hashMap;\n        setMessagePreview({\n          message: message,\n          mode: ConversationOptionConstants.edit,\n        });\n\n        inputValueRef.current = textComponents ?? \"\";\n        setInputMessage(textComponents ?? \"\");\n        messageInputRef.current.focus();\n      }\n    };\n\n    const previewReplyMessage = (message: any) => {\n      // Clear edit preview if it exists\n      if (messagePreview) {\n        setMessagePreview(null);\n        mentionMap.current = new Map();\n        plainTextInput.current = \"\";\n        setOriginalText(\"\");\n      }\n\n      setReplyMessage({\n        message: message,\n        mode: ConversationOptionConstants.reply,\n      });\n\n      try {\n        messageInputRef.current?.focus();\n      } catch (error) {\n        // console.log(error); // Removed debug log\n      }\n    };\n\n    const cameraCallback = async (cameraImage: any) => {\n      if (CheckPropertyExists(cameraImage, \"error\")) {\n        return;\n      }\n      const { name, uri, type } = cameraImage;\n      let file = {\n        name,\n        type,\n        uri,\n      };\n      sendMediaMessage(chatWithId.current, file, MessageTypeConstants.image, chatWith.current);\n    };\n\n    const fileInputHandler = async (fileType: string) => {\n      if (fileType === MessageTypeConstants.takePhoto) {\n        if (!(await permissionUtil.startResourceBasedTask([\"camera\"]))) {\n          return;\n        }\n        let quality = imageQuality;\n        if (isNaN(imageQuality) || imageQuality < 1 || imageQuality > 100) {\n          quality = 20;\n        }\n        if (Platform.OS === \"android\") {\n          FileManager.openCamera(fileType, Math.round(quality), cameraCallback);\n        } else {\n          FileManager.openCamera(fileType, cameraCallback);\n        }\n      } else if (Platform.OS === \"ios\" && fileType === MessageTypeConstants.video) {\n        NativeModules.VideoPickerModule.pickVideo((file: any) => {\n          if (file.uri)\n            sendMediaMessage(\n              chatWithId.current,\n              file,\n              MessageTypeConstants.video,\n              chatWith.current\n            );\n        });\n      } else\n        FileManager.openFileChooser(fileType, async (fileInfo: any) => {\n          if (CheckPropertyExists(fileInfo, \"error\")) {\n            return;\n          }\n          let { name, uri, type } = fileInfo;\n          let file = {\n            name,\n            type,\n            uri,\n          };\n          sendMediaMessage(chatWithId.current, file, fileType, chatWith.current);\n        });\n    };\n\n    const playAudio = () => {\n      if (customSoundForOutgoingMessage) {\n        CometChatSoundManager.play(\n          CometChatSoundManager.SoundOutput.outgoingMessage,\n          customSoundForOutgoingMessage\n        );\n      } else {\n        CometChatSoundManager.play(CometChatSoundManager.SoundOutput.outgoingMessage);\n      }\n    };\n\n    const clearInputBox = () => {\n      inputValueRef.current = \"\";\n      setPlainText(\"\");\n      setHasEdited(false);\n      setInputMessage(\"\");\n      setWarningMessage(\"\");\n      setReplyMessage(null);\n    };\n\n    const clearEditPreview = () => {\n      inputValueRef.current = \"\";\n      setPlainText(\"\");\n      setHasEdited(false);\n      setInputMessage(\"\");\n      setWarningMessage(\"\");\n    };\n\n    const sendTextMessage = () => {\n      setShowStopButton(true);\n\n      //ignore sending new message\n      if (messagePreview != null) {\n        editMessage(messagePreview.message);\n        return;\n      }\n\n      if (Platform.OS === \"ios\" && messageInputRef.current) {\n        InteractionManager.runAfterInteractions(() => {\n          let textToProcess = plainTextInput.current;\n          if (typeof inputMessage === 'string' && inputMessage.trim().length > 0) {\n            textToProcess = inputMessage;\n          }\n\n          processAndSendMessage(textToProcess);\n        });\n        return; // Exit early, processAndSendMessage will handle sending\n      }\n\n      // For non-iOS platforms, proceed normally\n      processAndSendMessage(plainTextInput.current);\n    };\n\n    const processAndSendMessage = (textToProcess: string) => {\n      let finalTextInput = getRegexString(textToProcess);\n      let trimmedTextInput = finalTextInput.trim();\n\n      if (trimmedTextInput.trim().length === 0) {\n        return;\n      }\n      const currentReplyMessage = replyMessage;\n      const replyMessageId = currentReplyMessage?.message?.getId?.() ?? null;\n\n      let textMessage = new CometChat.TextMessage(\n        chatWithId.current,\n        trimmedTextInput,\n        chatWith.current\n      );\n\n      textMessage.setSender(loggedInUser.current);\n      textMessage.setReceiver(chatWith.current);\n      textMessage.setText(trimmedTextInput);\n      textMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n\n      // Handle parent message ID for threaded messages\n      if (parentMessageId) {\n        textMessage.setParentMessageId(parentMessageId as number);\n      } else if (isAgenticUser() && parentMessageIdRef.current) {\n        textMessage.setParentMessageId(Number(parentMessageIdRef.current));\n      }\n\n      // Handle reply message - set quoted message for replies\n      if (replyMessageId && currentReplyMessage?.message) {\n        try {\n          // Use only the SDK's built-in quoted message functionality\n          if (typeof textMessage.setQuotedMessage === 'function') {\n            textMessage.setQuotedMessage(currentReplyMessage.message);\n          }\n          if (typeof textMessage.setQuotedMessageId === 'function') {\n            textMessage.setQuotedMessageId(replyMessageId);\n          }\n        } catch (error) {\n          // console.log(error); // Removed debug log\n        }\n      }\n\n      allFormatters.current.forEach((item) => {\n        textMessage = item.handlePreMessageSend(textMessage);\n      });\n\n      setMentionsSearchData([]);\n      plainTextInput.current = \"\";\n\n      // Clear input and reply message immediately for better UX\n      inputValueRef.current = \"\";\n      setPlainText(\"\");\n      setHasEdited(false);\n      setInputMessage(\"\");\n      setWarningMessage(\"\");\n      setReplyMessage(null);\n\n      // Clear the TextInput on iOS to remove auto-corrected text\n      if (Platform.OS === \"ios\" && messageInputRef.current) {\n        InteractionManager.runAfterInteractions(() => {\n          try {\n            messageInputRef.current?.setNativeProps?.({ text: \"\" });\n            messageInputRef.current?.clear?.();\n          } catch (error) {\n            // Ignore errors when clearing\n          }\n        });\n      }\n\n      if (onSendButtonPress) {\n        onSendButtonPress(textMessage);\n        return;\n      }\n\n      CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n        message: textMessage,\n        status: messageStatus.inprogress,\n      });\n\n      if (!disableSoundForOutgoingMessages) playAudio();\n\n      // Enable 1-second delay for agentic users\n      if (isAgenticUser()) {\n        setIsSendButtonDisabledForDelay(true);\n        if (sendButtonDelayTimer.current) {\n          clearTimeout(sendButtonDelayTimer.current);\n        }\n        sendButtonDelayTimer.current = setTimeout(() => {\n          setIsSendButtonDisabledForDelay(false);\n        }, 1000);\n      }\n\n      CometChat.sendMessage(textMessage)\n        .then((message: any) => {\n          if (isAgenticUser() && !parentMessageId && message?.getId) {\n            const messageId = typeof message.getId() === 'string' ? Number(message.getId()) : message.getId();\n            if (!isNaN(messageId) && !parentMessageIdRef.current) {\n              parentMessageIdRef.current = messageId;\n            }\n            if (messageId && !isNaN(messageId)) {\n              startStreamingForRunId(String(messageId));\n            }\n          }\n\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: message,\n            status: messageStatus.success,\n          });\n        })\n        .catch((error: any) => {\n          onError && onError(error);\n          textMessage.setMetadata({ error: true });\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: textMessage,\n            status: messageStatus.error,\n          });\n        });\n    };\n\n    /** edit message */\n    const editMessage = (message: any) => {\n      endTyping(null, null);\n\n      let finalTextInput = getRegexString(plainTextInput.current);\n\n      let messageText = finalTextInput.trim();\n      let textMessage = new CometChat.TextMessage(\n        chatWithId.current,\n        messageText,\n        chatWith.current\n      );\n      textMessage.setId(message.id);\n      parentMessageId && textMessage.setParentMessageId(parentMessageId as number);\n\n      inputValueRef.current = \"\";\n      clearInputBox();\n      messageInputRef.current.textContent = \"\";\n\n      setMessagePreview(null);\n\n      if (onSendButtonPress) {\n        onSendButtonPress(textMessage);\n        return;\n      }\n\n      if (!disableSoundForOutgoingMessages) playAudio();\n\n      CometChat.editMessage(textMessage)\n        .then((editedMessage: any) => {\n          inputValueRef.current = \"\";\n          setInputMessage(\"\");\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n            message: editedMessage,\n            status: messageStatus.success,\n          });\n        })\n        .catch((error: any) => {\n          onError && onError(error);\n        });\n    };\n\n    /** send media message */\n    const sendMediaMessage = (\n      receiverId?: any,\n      messageInput?: any,\n      messageType?: any,\n      receiverType?: any\n    ) => {\n      setShowActionSheet(false);\n      let mediaMessage = new CometChat.MediaMessage(\n        receiverId,\n        messageInput,\n        messageType,\n        receiverType\n      );\n\n      mediaMessage.setSender(loggedInUser.current);\n      mediaMessage.setReceiver(receiverType);\n      mediaMessage.setType(messageType);\n      mediaMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n      mediaMessage.setData({\n        type: messageType,\n        category: CometChat.CATEGORY_MESSAGE,\n        name: messageInput[\"name\"],\n        file: messageInput,\n        url: messageInput[\"uri\"],\n        sender: loggedInUser.current,\n      });\n      // Use ref to get current replyMessage value (avoids stale closure)\n      const currentReplyMessage = replyMessageRef.current;\n      const replyMessageId = currentReplyMessage?.message?.getId?.() ?? null;\n      if (replyMessageId) {\n        if (currentReplyMessage?.message) {\n          if (typeof mediaMessage.setQuotedMessage === 'function') {\n            mediaMessage.setQuotedMessage(currentReplyMessage.message);\n          }\n          if (typeof mediaMessage.setQuotedMessageId === 'function') {\n            mediaMessage.setQuotedMessageId(replyMessageId);\n          }\n          // Store full message object directly on the message (not in metadata)\n          (mediaMessage as any).quotedMessage = currentReplyMessage.message;\n          (mediaMessage as any).quotedMessageId = replyMessageId;\n\n          // Don't store in metadata - it exceeds size limit and SDK handles it\n        }\n      } else if (parentMessageId) {\n        mediaMessage.setParentMessageId(parentMessageId as number);\n      }\n\n      let localMessage = new CometChat.MediaMessage(\n        receiverId,\n        messageInput,\n        messageType,\n        receiverType\n      );\n\n      localMessage.setSender(loggedInUser.current);\n      localMessage.setReceiver(receiverType);\n      localMessage.setType(messageType);\n      localMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n      localMessage.setData({\n        type: messageType,\n        category: CometChat.CATEGORY_MESSAGE,\n        name: messageInput[\"name\"],\n        file: messageInput,\n        url: messageInput[\"uri\"],\n        sender: loggedInUser.current,\n      });\n      if (replyMessageId) {\n        if (currentReplyMessage?.message) {\n          if (typeof localMessage.setQuotedMessage === 'function') {\n            localMessage.setQuotedMessage(currentReplyMessage.message);\n          }\n          if (typeof localMessage.setQuotedMessageId === 'function') {\n            localMessage.setQuotedMessageId(replyMessageId);\n          }\n          (localMessage as any).quotedMessage = currentReplyMessage.message;\n          (localMessage as any).quotedMessageId = replyMessageId;\n          const currentMetadata = localMessage.getMetadata() || {};\n          localMessage.setMetadata({\n            ...currentMetadata,\n            quotedMessage: currentReplyMessage.message,\n            quotedMessageId: replyMessageId\n          });\n        }\n      } else if (parentMessageId) {\n        localMessage.setParentMessageId(parentMessageId as number);\n      }\n      localMessage.setData({\n        type: messageType,\n        category: CometChat.CATEGORY_MESSAGE,\n        name: messageInput[\"name\"],\n        file: messageInput,\n        url: messageInput[\"uri\"],\n        sender: loggedInUser.current,\n        attachments: [messageInput],\n        entities: {\n          sender: {\n            entity: loggedInUser.current,\n          },\n        },\n      });\n\n      CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n        message: localMessage,\n        status: messageStatus.inprogress,\n      });\n\n      if (!disableSoundForOutgoingMessages) playAudio();\n      CometChat.sendMediaMessage(mediaMessage)\n        .then((message: any) => {\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: message,\n            status: messageStatus.success,\n          });\n          if (replyMessageId) {\n            CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n              message: message,\n              status: messageStatus.success,\n            });\n          }\n          setReplyMessage(null);\n          setWarningMessage(\"\");\n          setShowRecordAudio(false);\n        })\n        .catch((error: any) => {\n          setShowRecordAudio(false);\n          if (error?.code === \"ERR_PERMISSION_DENIED\") {\n            onError && onError(error);\n            // Set error in data.metaData where the message list receipt logic checks\n            const currentData = localMessage.getData() || {};\n            localMessage.setData({ ...currentData, metaData: { error: true } });\n            localMessage.setMetadata({ error: true });\n            CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n              message: localMessage,\n              status: messageStatus.error,\n            });\n            setReplyMessage(null);\n            return;\n          }\n          onError && onError(error);\n          // Set error in data.metaData where the message list receipt logic checks\n          const currentData = localMessage.getData() || {};\n          localMessage.setData({ ...currentData, metaData: { error: true } });\n          localMessage.setMetadata({ error: true });\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, {\n            message: localMessage,\n            status: messageStatus.error,\n          });\n          setReplyMessage(null);\n        });\n    };\n\n    const startTyping = (endTypingTimeout?: any, typingMetadata?: any) => {\n      //if typing is disabled\n      if (disableTypingEvents) {\n        return false;\n      }\n\n      //if typing is in progress, clear the previous timeout and set new timeout\n      if (isTyping.current) {\n        clearTimeout(isTyping.current);\n        isTyping.current = null;\n      } else {\n        let metadata = typingMetadata || undefined;\n\n        let typingNotification = new CometChat.TypingIndicator(\n          chatWithId.current,\n          chatWith.current,\n          metadata\n        );\n        CometChat.startTyping(typingNotification);\n      }\n\n      let typingInterval = endTypingTimeout || 500;\n      isTyping.current = setTimeout(() => {\n        endTyping(null, typingMetadata);\n      }, typingInterval);\n      return false;\n    };\n\n    const endTyping = (event: any, typingMetadata: any) => {\n      if (event) {\n        event.persist();\n      }\n\n      if (disableTypingEvents) {\n        return false;\n      }\n\n      let metadata = typingMetadata || undefined;\n\n      let typingNotification = new CometChat.TypingIndicator(\n        chatWithId.current,\n        chatWith.current,\n        metadata\n      );\n      CometChat.endTyping(typingNotification);\n\n      clearTimeout(isTyping.current!);\n      isTyping.current = null;\n      return false;\n    };\n\n    const SecondaryButtonViewElem = useMemo(() => {\n      if (hideAttachmentButton || !actionSheetItems.length) return <></>;\n      return (\n        <AttachIconButton\n          onPress={() => {\n            Keyboard.dismiss();\n            setTimeout(() => setShowActionSheet(true), 50);\n          }}\n          icon={mergedComposerStyle.attachmentIcon as JSX.Element | ImageSourcePropType}\n          iconStyle={mergedComposerStyle.attachmentIconStyle as ImageStyle}\n        />\n      );\n    }, [mergedComposerStyle, actionSheetItems, hideAttachmentButton]);\n\n    const RecordAudioButtonView = ({\n      icon,\n      iconStyle,\n    }: {\n      icon: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n    }) => {\n      return (\n        <TouchableOpacity\n          onPress={() => {\n            Keyboard.dismiss();\n            setTimeout(() => setShowRecordAudio(true), 50);\n          }}\n        >\n          <Icon\n            name='mic'\n            height={24}\n            width={24}\n            icon={icon}\n            color={iconStyle?.tintColor ?? theme.color.iconSecondary}\n            imageStyle={iconStyle}\n          />\n        </TouchableOpacity>\n      );\n    };\n\n    const voiceRecoringButtonElem = useMemo(() => {\n      const isAgenticUser = user?.getRole?.() === '@agentic';\n\n      return (hideVoiceRecordingButton || isAgenticUser) ? undefined : (\n        <RecordAudioButtonView\n          icon={mergedComposerStyle.voiceRecordingIcon as ImageSourcePropType | JSX.Element}\n          iconStyle={mergedComposerStyle.voiceRecordingIconStyle as ImageStyle}\n        />\n      );\n    }, [hideVoiceRecordingButton, mergedComposerStyle, user]);\n\n    const AuxiliaryButtonViewElem = useCallback(() => {\n      const isAgenticUser = user?.getRole?.() === '@agentic';\n\n      if (AuxiliaryButtonView)\n        return <AuxiliaryButtonView user={user} group={group} composerId={id!} />;\n\n      if (isAgenticUser) {\n        return <></>;\n      }\n\n      if (defaultAuxiliaryButtonOptions)\n        return (\n          <View\n            style={{\n              flexDirection: \"row\",\n              alignItems: \"center\",\n              gap: theme.spacing.spacing.s2,\n            }}\n          >\n            {defaultAuxiliaryButtonOptions}\n          </View>\n        );\n\n      return <></>;\n    }, [defaultAuxiliaryButtonOptions, user, AuxiliaryButtonView, group, id, theme]);\n\n    const DefaultAgentSendButtonView = useCallback(\n      ({ isButtonDisabled, composerRef }: { isButtonDisabled: boolean; composerRef: any }) => (\n        <CometChatSendButtonView\n          isButtonDisabled={isButtonDisabled}\n          composerRef={composerRef}\n          isStreaming={isStreaming}\n          showStopButton={showStopButton}\n          setShowStopButton={setShowStopButton}\n        />\n      ),\n      [isStreaming, showStopButton]\n    );\n\n    const SendButtonViewElem = useCallback(() => {\n      if (hideSendButton) return <></>;\n\n      const isAgenticUserCheck = isAgenticUser();\n\n      if (isAgenticUserCheck) {\n        const disabled = isStreaming || plainTextInput.current.trim().length === 0|| (messagePreview && !hasEdited) || isSendButtonDisabledForDelay;\n        const SendButtonComponent =  DefaultAgentSendButtonView;\n        // Create a ref-like object that matches what CometChatSendButtonView expects\n        const composerRef = {\n          current: {\n            sendTextMessage: () => {\n              if (!disabled) {\n                sendTextMessage();\n              }\n            }\n          }\n        };\n        return <SendButtonComponent isButtonDisabled={disabled} composerRef={composerRef} />;\n      }\n\n      if (SendButtonView) return <SendButtonView user={user} group={group} composerId={id!} />;\n      const disabled = isStreaming || plainText.trim().length === 0 || (messagePreview && !hasEdited);\n      return (\n        <TouchableOpacity\n          onPress={sendTextMessage}\n          style={[\n            {\n              borderRadius: theme.spacing.radius.max,\n              padding: 4,\n              backgroundColor: disabled ? theme.color.background4 : theme.color.fabButtonBackground,\n            },\n            mergedComposerStyle.sendIconContainerStyle as ViewStyle,\n          ]}\n          disabled={disabled}\n        >\n          <Icon\n            name='send-fill'\n            icon={mergedComposerStyle.sendIcon as ImageSourcePropType | JSX.Element}\n            color={\n              (mergedComposerStyle.sendIconStyle?.tintColor ??\n                theme.color.fabButtonIcon) as ColorValue\n            }\n            imageStyle={mergedComposerStyle.sendIconStyle as ImageStyle}\n            height={24}\n            width={24}\n          />\n        </TouchableOpacity>\n      );\n    }, [mergedComposerStyle, inputMessage, plainText, messagePreview, hasEdited, isStreaming, DefaultAgentSendButtonView, user, isAgenticUser, sendTextMessage, hideSendButton, SendButtonView, isSendButtonDisabledForDelay]);\n\n    //fetch logged in user\n    useEffect(() => {\n      CometChat.getLoggedinUser().then((user) => (loggedInUser.current = user));\n      let _formatter = [...(textFormatters || [])];\n\n      if (!disableMentions) {\n        let mentionsFormatter = ChatConfigurator.getDataSource().getMentionsFormatter();\n        mentionsFormatter.setLoggedInUser(CometChatUIKit.loggedInUser!);\n        mentionsFormatter.setContext(\"composer\");\n        mentionsFormatter.setMentionsStyle(\n          mergedComposerStyle.mentionsStyle as CometChatTheme[\"mentionsStyle\"]\n        );\n        mentionsFormatter.setTargetElement(MentionsTargetElement.textinput);\n\n        if (mentionAllLabel) mentionsFormatter.setMentionAllLabel(mentionAllLabel);\n        mentionsFormatter.setDisableMentionAll(disableMentionAll);\n\n        if (user) mentionsFormatter.setUser(user);\n        if (group) mentionsFormatter.setGroup(group);\n\n        _formatter.unshift(mentionsFormatter);\n      }\n\n      _formatter.forEach((formatter) => {\n        formatter.setComposerId(id!);\n        if (user) formatter.setUser(user);\n        if (group) formatter.setGroup(group);\n        let trackingCharacter = formatter.getTrackingCharacter();\n        trackingCharacters.current.push(trackingCharacter);\n\n        let newFormatter = CommonUtils.clone(formatter);\n        allFormatters.current.set(trackingCharacter, newFormatter);\n      });\n    }, []);\n\n    useEffect(() => {\n      //update receiver user\n      if (user && user.getUid()) {\n        chatRef.current = {\n          chatWith: ReceiverTypeConstants.user,\n          chatWithId: user.getUid(),\n        };\n        chatWith.current = ReceiverTypeConstants.user;\n        chatWithId.current = user.getUid();\n      } else if (group && group.getGuid()) {\n        chatRef.current = {\n          chatWith: ReceiverTypeConstants.group,\n          chatWithId: group.getGuid(),\n        };\n        chatWith.current = ReceiverTypeConstants.group;\n        chatWithId.current = group.getGuid();\n      }\n    }, [user, group, chatRef]);\n\n    const handleOnClick = (CustomView: any) => {\n      let view = CustomView(\n        user,\n        group,\n        {\n          uid: user?.getUid(),\n          guid: group?.getGuid(),\n          parentMessageId: parentMessageId,\n        },\n        {\n          onClose: () => setIsVisible(false),\n        }\n      );\n      bottomSheetRef.current?.togglePanel();\n      setShowActionSheet(false);\n      setTimeout(() => {\n        setCustomView(() => view);\n        setIsVisible(true);\n      }, 200);\n    };\n\n    useEffect(() => {\n      const defaultAttachmentOptions = ChatConfigurator.dataSource.getAttachmentOptions(\n        theme,\n        user,\n        group,\n        composerIdMap,\n        {\n          hideCameraOption,\n          hideImageAttachmentOption,\n          hideVideoAttachmentOption,\n          hideAudioAttachmentOption,\n          hideFileAttachmentOption,\n          hidePollsAttachmentOption,\n          hideCollaborativeDocumentOption,\n          hideCollaborativeWhiteboardOption,\n          replyToMessage: replyMessage?.message,\n          closeReplyPreview: () => setReplyMessage(null),\n        }\n      );\n      setActionSheetItems(() =>\n        attachmentOptions && typeof attachmentOptions === \"function\"\n          ? attachmentOptions({ user, group, composerId: composerIdMap })?.map((item) => {\n            if (typeof item.CustomView === \"function\")\n              return {\n                ...item,\n                onPress: () => handleOnClick(item.CustomView),\n              };\n            if (typeof item.onPress == \"function\")\n              return {\n                ...item,\n                onPress: () => {\n                  setShowActionSheet(false);\n                  item.onPress?.(user, group);\n                },\n              };\n            return {\n              ...item,\n              onPress: () => fileInputHandler(item.id),\n            };\n          })\n          : [\n            ...defaultAttachmentOptions.map((item: any) => {\n              if (typeof item.CustomView === \"function\")\n                return {\n                  ...item,\n                  onPress: () => handleOnClick(item.CustomView),\n                };\n              if (typeof item.onPress === \"function\")\n                return {\n                  ...item,\n                  onPress: () => {\n                    setShowActionSheet(false);\n                    item.onPress?.(user, group);\n                  },\n                };\n              return {\n                ...item,\n                onPress: () => fileInputHandler(item.id),\n              };\n            }),\n            ...(addAttachmentOptions && typeof addAttachmentOptions === \"function\"\n              ? addAttachmentOptions({ user, group, composerId: composerIdMap })?.map((item) => {\n                if (typeof item.CustomView === \"function\")\n                  return {\n                    ...item,\n                    onPress: () => handleOnClick(item.CustomView),\n                  };\n                if (typeof item.onPress == \"function\")\n                  return {\n                    ...item,\n                    onPress: () => {\n                      setShowActionSheet(false);\n                      item.onPress?.(user, group);\n                    },\n                  };\n                return {\n                  ...item,\n                  onPress: () => fileInputHandler(item.id),\n                };\n              })\n              : []),\n          ]\n      );\n    }, [\n      user,\n      group,\n      id,\n      parentMessageId,\n      hideCameraOption,\n      hideImageAttachmentOption,\n      hideVideoAttachmentOption,\n      hideAudioAttachmentOption,\n      hideFileAttachmentOption,\n      hidePollsAttachmentOption,\n      hideCollaborativeDocumentOption,\n      hideCollaborativeWhiteboardOption,\n      addAttachmentOptions,\n      replyMessage,\n    ]);\n\n    useEffect(() => {\n      CometChatUIEventHandler.addMessageListener(editMessageListenerID, {\n        ccMessageEdited: (item: any) => {\n          const incomingParentId = item?.message?.getParentMessageId?.() ?? null;\n          const myParentId = parentMessageId ?? null;\n\n          if (incomingParentId === myParentId) {\n            previewMessage(item);\n          }\n        },\n      });\n      CometChatMessageEvents.addListener(\n        CometChatMessageEvents.ccReplyToMessage,\n        replyMessageListenerID,\n        (data: any) => {\n          if (data.status === messageStatus.inprogress) {\n            previewReplyMessage(data.message);\n          } else if (data.status === messageStatus.success) {\n          }\n        }\n      );\n      CometChatUIEventHandler.addUIListener(UiEventListenerID, {\n        ccToggleBottomSheet: (item) => {\n          if (item?.bots) {\n            // let newAiOptions = _getAIOptions(item.bots);\n            // setAIOptionItems(newAiOptions);\n            // setShowAIOptions(true);\n            return;\n          } else if (item?.botView) {\n            setCustomView(() => item.child);\n            return;\n          }\n          setIsVisible(false);\n          bottomSheetRef.current?.togglePanel();\n        },\n        ccComposeMessage: (text) => {\n          setIsVisible(false);\n          bottomSheetRef.current?.togglePanel();\n\n          inputValueRef.current = text?.text;\n          setInputMessage(text?.text);\n          plainTextInput.current = text?.text || \"\";\n          setPlainText(text?.text || \"\");\n        },\n        ccSuggestionData(item: { id: string | number; data: Array<SuggestionItem> }) {\n          if (activeCharacter.current && id === item?.id) {\n            const warningView = getMentionLimitView();\n            if (warningView) {\n              return;\n            }\n            setMentionsSearchData(item?.data);\n            setSuggestionListLoader(false);\n          }\n        },\n      });\n      return () => {\n        CometChatUIEventHandler.removeMessageListener(editMessageListenerID);\n        CometChatMessageEvents.removeListener(CometChatMessageEvents.ccReplyToMessage, replyMessageListenerID);\n        CometChatUIEventHandler.removeUIListener(UiEventListenerID);\n      };\n    }, []);\n\n    const handlePannel = (item: any) => {\n      if (item.child) {\n        if (item.alignment === ViewAlignment.composerTop) setCustomViewHeader(() => item.child);\n        else if (item.alignment === ViewAlignment.composerBottom)\n          setCustomViewFooter(() => item.child);\n      } else {\n        if (item.alignment === ViewAlignment.composerTop) setCustomViewHeader(null);\n        else if (item.alignment === ViewAlignment.composerBottom) setCustomViewFooter(undefined);\n      }\n    };\n    useEffect(() => {\n      CometChatUIEventHandler.addUIListener(uiEventListenerShow, {\n        showPanel: (item) => handlePannel(item),\n      });\n      CometChatUIEventHandler.addUIListener(uiEventListenerHide, {\n        hidePanel: (item) => handlePannel(item),\n      });\n      return () => {\n        CometChatUIEventHandler.removeUIListener(uiEventListenerShow);\n        CometChatUIEventHandler.removeUIListener(uiEventListenerHide);\n      };\n    }, []);\n\n    const _sendRecordedAudio = (recordedFile: String) => {\n      let fileObj = {\n        name: \"audio-recording\" + recordedFile.split(\"/audio-recording\")[1],\n        type: \"audio/mp4\",\n        uri: recordedFile,\n      };\n      // console.log(\"fileObj\", fileObj); // Removed debug log\n      sendMediaMessage(chatWithId.current, fileObj, MessageTypeConstants.audio, chatWith.current);\n      // console.log(\"Send Audio\"); // Removed debug log\n    };\n\n    function shouldOpenList(\n      selection: {\n        start: number;\n        end: number;\n      },\n      searchString: string,\n      tracker: string\n    ) {\n      return (\n        selection.start === selection.end &&\n        !isCursorWithinMentionRange(mentionMap.current, selection.start - searchString.length) &&\n        trackingCharacters.current.includes(tracker) &&\n        (searchString === \"\"\n          ? (plainTextInput.current[selection.start - 2]?.length === 1 &&\n            plainTextInput.current[selection.start - 2]?.trim()?.length === 0) ||\n          plainTextInput.current[selection.start - 2] === undefined\n          : true) &&\n        (plainTextInput.current[selection.start - 1]?.length === 1 &&\n          plainTextInput.current[selection.start - 1]?.trim()?.length === 0\n          ? searchString.length > 0\n          : true)\n      );\n    }\n\n    let timeoutId: ReturnType<typeof setTimeout>;\n    const openList = (selection: { start: number; end: number }) => {\n      clearTimeout(timeoutId);\n      timeoutId = setTimeout(() => {\n        let searchString = extractTextFromCursor(plainTextInput.current, selection.start);\n        let tracker = searchString\n          ? plainTextInput.current[selection.start - (searchString.length + 1)]\n          : plainTextInput.current[selection.start - 1];\n\n        if (shouldOpenList(selection, searchString, tracker)) {\n          activeCharacter.current = tracker;\n          searchStringRef.current = searchString;\n          // Show the suggestion list immediately (even while data is loading) to avoid layout jumps\n          setShowMentionList(true);\n          setSuggestionListLoader(true);\n\n          let formatter = allFormatters.current.get(tracker);\n          if (formatter instanceof CometChatMentionsFormatter) {\n            let shouldShowMentionList =\n              formatter.getVisibleIn() === MentionsVisibility.both ||\n              (formatter.getVisibleIn() === MentionsVisibility.usersConversationOnly && user) ||\n              (formatter.getVisibleIn() === MentionsVisibility.groupsConversationOnly && group);\n            if (shouldShowMentionList) {\n              formatter?.search(searchString);\n            }\n          } else {\n            formatter?.search(searchString);\n          }\n        } else {\n          activeCharacter.current = \"\";\n          searchStringRef.current = \"\";\n          setShowMentionList(false);\n          setMentionsSearchData([]);\n          setSuggestionListLoader(false);\n        }\n      }, 100);\n    };\n\n    const getRegexString = (str: string) => {\n      // Get an array of the entries in the map using the spread operator\n      const entries = [...mentionMap.current.entries()].reverse();\n\n      let uidInput = str;\n\n      // Iterate over the array in reverse order\n      entries.forEach(([key, value]) => {\n        let [start, end] = key.split(\"_\").map(Number);\n\n        let pre = uidInput.substring(0, start);\n        let post = uidInput.substring(end);\n\n        uidInput = pre + value.underlyingText + post;\n      });\n\n      return uidInput;\n    };\n\n    const getPlainString = (\n      str: string,\n      edits: Array<{\n        endIndex: number;\n        replacement: string;\n        startIndex: number;\n        user: SuggestionItem;\n      }>\n    ) => {\n      // Get an array of the entries in the map using the spread operator\n      const entries = [...edits].reverse();\n\n      let _plainString = str;\n\n      // Iterate over the array in reverse order\n      entries.forEach(({ endIndex, replacement, startIndex, user }) => {\n        let pre = _plainString.substring(0, startIndex);\n        let post = _plainString.substring(endIndex);\n\n        _plainString = pre + replacement + post;\n      });\n\n      return _plainString;\n    };\n\n    const parseMentionKey = (key: string): { start: number; end: number } | undefined => {\n      const [startStr, endStr] = key.split(\"_\");\n      const start = Number(startStr);\n      const end = Number(endStr);\n\n      const isValid = Number.isFinite(start) && Number.isFinite(end);\n\n      if (typeof __DEV__ !== \"undefined\" && __DEV__ && !isValid) {\n        throw new Error(`Invalid mention key: \"${key}\" (expected \"start_end\")`);\n      }\n\n      return isValid ? { start, end } : undefined;\n    };\n\n    const calcDeletionRange = (\n      selection: { start: number; end: number },\n      deletionLength: number\n    ) => {\n      return selection.start === selection.end\n        ? {\n          start: Math.max(0, selection.start - deletionLength),\n          end: selection.start,\n        }\n        : { start: selection.start, end: selection.end };\n    };\n\n    const collectOverlappingMentions = (\n      range: { start: number; end: number },\n      mentionMap: Map<string, SuggestionItem>\n    ): MentionOverlap[] => {\n      const overlaps: MentionOverlap[] = [];\n\n      mentionMap.forEach((value, key) => {\n        const mentionRange = parseMentionKey(key);\n        if (!mentionRange) return;\n\n        const { start, end } = mentionRange;\n\n        // compare against the *deletion* range, not the mention itself\n        if (range.start < end && range.end > start) {\n          overlaps.push({ key, value, start, end });\n        }\n      });\n\n      overlaps.sort((a, b) => a.start - b.start);\n      return overlaps;\n    };\n\n    const removeMentionsFromTextAndMap = (\n      text: string,\n      overlaps: MentionOverlap[],\n      map: Map<string, SuggestionItem>\n    ) => {\n      let adjustment = 0;\n      let newText = text;\n\n      overlaps.forEach(({ key, value, start, end }) => {\n        const adjStart = start + adjustment;\n        const adjEnd = end + adjustment;\n\n        newText = newText.slice(0, adjStart) + newText.slice(adjEnd);\n        map.delete(key);\n        adjustment -= adjEnd - adjStart;\n\n        // keep formatter in sync\n        if (!ifIdExists(value.id, map)) {\n          const fmt = allFormatters.current.get(value.trackingCharacter!);\n          if (fmt instanceof CometChatMentionsFormatter) {\n            const users = fmt.getSuggestionItems().filter((u) => u.id !== value.id);\n            fmt.setSuggestionItems(users);\n            if (!getMentionLimitView(fmt)) setWarningMessage(\"\");\n          }\n        }\n      });\n\n      return { newText, totalShift: adjustment };\n    };\n\n    const shiftRemainingMentionKeys = (\n      map: Map<string, SuggestionItem>,\n      shiftStart: number,\n      delta: number\n    ) => {\n      if (delta === 0) return;\n      const shifted = new Map<string, SuggestionItem>();\n\n      map.forEach((val, key) => {\n        const range = parseMentionKey(key);\n        if (!range) {\n          // key was malformed → skip or handle as you wish\n          return;\n        }\n\n        const { start, end } = range;\n        if (start > shiftStart) {\n          shifted.set(`${start + delta}_${end + delta}`, val);\n        } else {\n          shifted.set(key, val);\n        }\n      });\n\n      map.clear();\n      shifted.forEach((v, k) => map.set(k, v));\n    };\n\n    const deleteMentionHelper = (oldText: string, newText: string) => {\n      const deletionLen = oldText.length - newText.length;\n      const range = calcDeletionRange(selectionPosition, deletionLen);\n\n      const deletionMentionMap = new Map(mentionMap.current);\n      const overlaps = collectOverlappingMentions(range, deletionMentionMap);\n\n      if (overlaps.length === 0) return false;\n\n      const { newText: finalText, totalShift } = removeMentionsFromTextAndMap(\n        oldText,\n        overlaps,\n        deletionMentionMap\n      );\n\n      shiftRemainingMentionKeys(deletionMentionMap, range.start, totalShift);\n\n      plainTextInput.current = finalText;\n      setPlainText(finalText);\n      onTextChange?.(finalText);\n      mentionMap.current = deletionMentionMap;\n\n      const firstStart = overlaps[0].start;\n      InteractionManager.runAfterInteractions(() => {\n        messageInputRef.current?.setNativeProps({\n          selection: { start: firstStart, end: firstStart },\n        });\n      });\n\n      setFormattedInputMessage();\n      return true;\n    };\n\n    const textChangeHandler = (txt: string) => {\n      if (messagePreview) {\n        setHasEdited(txt.trim() !== originalText.trim());\n      }\n\n      const oldText = plainTextInput.current ?? \"\";\n      const newText = txt;\n\n      startTyping();\n\n      // Check if this is a deletion\n      if (oldText.length > newText.length) {\n        const handled = deleteMentionHelper(oldText, newText);\n        if (handled) return;\n      }\n\n      // Existing handling for non-deletion cases\n      const removing = (plainTextInput.current?.length ?? 0) > txt.length;\n      const adding = plainTextInput.current?.length < txt.length;\n      const textDiff = txt.length - (plainTextInput.current?.length ?? 0);\n      const notAtLast = selectionPosition.start + textDiff < txt.length;\n\n      let decr = 0;\n\n      plainTextInput.current = newText;\n      setPlainText(newText);\n\n      const newMentionMap: Map<string, SuggestionItem> = new Map(mentionMap.current);\n\n      mentionMap.current.forEach((value, key) => {\n        const range = parseMentionKey(key);\n        if (!range) {\n          // key was malformed → skip or handle as you wish\n          return;\n        }\n\n        const { start, end } = range;\n        let position = { start, end };\n\n        if (\n          notAtLast &&\n          (selectionPosition.start - 1 <= position.start ||\n            selectionPosition.start - textDiff <= position.start)\n        ) {\n          if (removing) {\n            decr = selectionPosition.end - selectionPosition.start - textDiff;\n            position.start -= decr;\n            position.end -= decr;\n          } else if (adding) {\n            decr = selectionPosition.end - selectionPosition.start + textDiff;\n            position.start += decr;\n            position.end += decr;\n          }\n          if (removing || adding) {\n            const newKey = `${position.start}_${position.end}`;\n            if (position.start >= 0) newMentionMap.set(newKey, value);\n            newMentionMap.delete(key);\n          }\n        }\n\n        /* delete mention that was edited/over-typed */\n        const expected = plainTextInput.current?.substring(position.start, position.end);\n        if (expected !== value.promptText) {\n          newMentionMap.delete(`${position.start}_${position.end}`);\n\n          if (!ifIdExists(value.id, newMentionMap)) {\n            const targetedFormatter = allFormatters.current.get(value.trackingCharacter!);\n            if (!targetedFormatter) return;\n            const users = [...targetedFormatter.getSuggestionItems()];\n            const idx = users.findIndex((u) => u.id === value.id);\n            if (idx !== -1) users.splice(idx, 1);\n\n            if (targetedFormatter instanceof CometChatMentionsFormatter) {\n              targetedFormatter.setSuggestionItems(users);\n              const warn = getMentionLimitView(targetedFormatter);\n              if (!warn) setWarningMessage(\"\");\n            }\n          }\n        }\n      });\n\n      mentionMap.current = newMentionMap;\n      onTextChange?.(plainTextInput.current);\n      setFormattedInputMessage();\n    };\n\n    const onMentionPress = (item: SuggestionItem) => {\n      setShowMentionList(false);\n      setMentionsSearchData([]);\n\n      let notAtLast = selectionPosition.start < (plainTextInput.current?.length ?? 0);\n\n      let textDiff =\n        (plainTextInput.current?.length ?? 0) +\n        (item.promptText?.length ?? 0) -\n        searchStringRef.current.length -\n        (plainTextInput.current?.length ?? 0);\n\n      let incr = 0;\n      let mentionPos = 0;\n\n      let newMentionMap = new Map(mentionMap.current);\n\n      let targetedFormatter = allFormatters.current.get(activeCharacter.current);\n      if (!targetedFormatter) return;\n      let existingCCUsers = [...targetedFormatter.getSuggestionItems()];\n      let userAlreadyExists = existingCCUsers.find(\n        (existingUser: SuggestionItem) => existingUser.id === item.id\n      );\n      if (!userAlreadyExists) {\n        let cometchatUIUserArray: Array<SuggestionItem> = [...existingCCUsers];\n        cometchatUIUserArray.push(item);\n        (targetedFormatter as CometChatMentionsFormatter).setSuggestionItems(cometchatUIUserArray);\n      }\n      mentionMap.current.forEach((value, key) => {\n        let position = { start: parseInt(key.split(\"_\")[0]), end: parseInt(key.split(\"_\")[1]) };\n\n        if (!(selectionPosition.start <= position.start)) {\n          mentionPos += 1;\n        }\n\n        if (\n          position.end === selectionPosition.end ||\n          (selectionPosition.start > position.start && selectionPosition.end <= position.end)\n        ) {\n          let newKey = `${position.start}_${position.end}`;\n          newMentionMap.delete(newKey);\n          mentionPos -= 1;\n        }\n\n        if (notAtLast && selectionPosition.start - 1 <= position.start) {\n          incr = selectionPosition.end - selectionPosition.start + textDiff;\n          let newKey = `${position.start + incr}_${position.end + incr}`;\n          newMentionMap.set(newKey, value);\n          newMentionMap.delete(key);\n        }\n      });\n      mentionMap.current = newMentionMap;\n\n      // When updating the input text, just get the latest plain text input and replace the selected text with the new mention\n      const updatedPlainTextInput = `${plainTextInput.current?.substring(\n        0,\n        selectionPosition.start - (1 + searchStringRef.current.length)\n      )}${item.promptText + \" \"}${plainTextInput.current?.substring(\n        selectionPosition.end,\n        plainTextInput.current?.length\n      )}`;\n      plainTextInput.current = updatedPlainTextInput;\n\n      let key =\n        selectionPosition.start -\n        (1 + searchStringRef.current.length) +\n        \"_\" +\n        (selectionPosition.start -\n          (searchStringRef.current.length + 1) +\n          (item.promptText?.length ?? 0));\n\n      let updatedMap = insertMentionAt(mentionMap.current, mentionPos, key, {\n        ...item,\n        trackingCharacter: activeCharacter.current,\n      });\n      mentionMap.current = updatedMap;\n      \n      // Calculate cursor position after the mention + space\n      // Add 1 to account for the space character added after the mention\n      const newCursorPosition =\n        selectionPosition.start -\n        (searchStringRef.current.length + 1) +\n        (item.promptText?.length ?? 0) +\n        1; // +1 for the space after mention\n      \n      // On iOS, use InteractionManager to ensure selection is applied after UI updates\n      if (Platform.OS === \"ios\") {\n        InteractionManager.runAfterInteractions(() => {\n          setSelectionPosition({\n            start: newCursorPosition,\n            end: newCursorPosition,\n          });\n        });\n      } else {\n        setSelectionPosition({\n          start: newCursorPosition,\n          end: newCursorPosition,\n        });\n      }\n      setFormattedInputMessage();\n    };\n\n    const setFormattedInputMessage = () => {\n      let textComponents: any = getRegexString(plainTextInput.current);\n\n      allFormatters.current.forEach((formatter: CometChatTextFormatter, key) => {\n        let resp = formatter.getFormattedText(textComponents);\n        textComponents = resp;\n      });\n\n      inputValueRef.current = textComponents;\n      setInputMessage(textComponents);\n    };\n\n    function escapeRegExp(string: string) {\n      return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n    }\n\n    function extractTextFromCursor(inputText: string, cursorPosition: number) {\n      const leftText = inputText.substring(0, cursorPosition);\n\n      // Escape the mentionPrefixes to safely use them in a regex pattern\n      const escapedPrefixes = trackingCharacters.current.map(escapeRegExp).join(\"|\");\n\n      // Build a dynamic regex pattern that matches any of the mention prefixes.\n      // This pattern will match a prefix followed by any combination of word characters\n      // and spaces, including a trailing space.\n      const mentionRegex = new RegExp(\n        `(?:^|\\\\s)(${escapedPrefixes})([^${escapedPrefixes}\\\\s][^${escapedPrefixes}]*)$`\n      );\n      const match = leftText.match(mentionRegex);\n\n      // If a match is found, return the first capturing group, which is the username\n      return (match && substringUpToNthSpace(match[2], 4)) || \"\";\n    }\n\n    function substringUpToNthSpace(str: string, n: number) {\n      // Split the string by spaces, slice to the (n-1) elements, and then rejoin with spaces\n      return str.split(\" \", n).join(\" \");\n    }\n\n    const insertMentionAt = (\n      mentionMap: Map<string, SuggestionItem>,\n      insertAt: number,\n      key: string,\n      value: SuggestionItem\n    ): Map<string, SuggestionItem> => {\n      // Convert the hashmap to an array of [key, value] pairs\n      let mentionsArray = Array.from(mentionMap);\n\n      // Insert the new mention into the array at the calculated index\n      mentionsArray.splice(insertAt, 0, [key, value]);\n\n      return new Map(mentionsArray);\n    };\n\n    /**\n     * Function to check if the id exists in the mentionMap\n     */\n    const ifIdExists = (id: string, hashmap: Map<string, SuggestionItem>) => {\n      let exists = false;\n      hashmap.forEach((value, key) => {\n        if (value.id === id) {\n          exists = true;\n        }\n      });\n      return exists;\n    };\n\n    const onSuggestionListEndReached = () => {\n      let targetedFormatter = allFormatters.current.get(activeCharacter.current);\n      if (!targetedFormatter) return;\n      let fetchingNext = targetedFormatter.fetchNext();\n      fetchingNext !== null && setSuggestionListLoader(true);\n    };\n\n    const getMentionLimitView = (targettedFormatterParam?: CometChatMentionsFormatter) => {\n      let targetedFormatter =\n        allFormatters.current.get(activeCharacter.current) ?? targettedFormatterParam;\n      if (!(targetedFormatter instanceof CometChatMentionsFormatter)) {\n        return false;\n      }\n      let shouldWarn;\n      let limit;\n      if (targetedFormatter?.getLimit && targetedFormatter?.getLimit()) {\n        limit = targetedFormatter?.getLimit();\n        if (\n          targetedFormatter.getUniqueUsersList &&\n          targetedFormatter.getUniqueUsersList()?.size >= limit\n        ) {\n          shouldWarn = true;\n        }\n      }\n      if (!shouldWarn) {\n        setWarningMessage(\"\");\n        return false;\n      }\n      setWarningMessage(\n        targetedFormatter?.getErrorString\n          ? targetedFormatter?.getErrorString()\n          : `${t(\"MENTION_UPTO\")} ${limit} ${\n              limit === 1 ? t(\"TIME\") : t(\"TIMES\")\n          } ${t(\"AT_A_TIME\")}.`\n      );\n      return true;\n    };\n\n    return (\n      <>\n        {/* {!isVisible && typeof CustomView === \"function\" && <CustomView />} TODOM */}\n        <Modal\n          transparent={true}\n          // animationType='slide'\n          visible={isVisible}\n          onRequestClose={() => {\n            setIsVisible(false);\n          }}\n        >\n          {CustomView && CustomView}\n        </Modal>\n        <KeyboardAvoidingView\n          behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n          keyboardVerticalOffset={Platform.select({ ios: kbOffset })}\n          {...keyboardAvoidingViewProps}\n        >\n          <View\n            style={[\n              Style.container,\n              {\n                paddingTop: CustomViewHeader ? theme.spacing.padding.p0 : theme.spacing.padding.p2,\n                paddingHorizontal: theme.spacing.padding.p2,\n              },\n              {\n                backgroundColor: theme.messageListStyles.containerStyle.backgroundColor,\n              },\n              mergedComposerStyle.containerStyle as ViewStyle,\n            ]}\n          >\n            <ActionSheetBoard\n              sheetRef={bottomSheetRef}\n              options={actionSheetItems}\n              shouldShow={showActionSheet}\n              onClose={() => setShowActionSheet(false)}\n              style={mergedComposerStyle.attachmentOptionsStyles}\n            />\n            <RecordAudio\n              sheetRef={bottomSheetRef}\n              options={actionSheetItems}\n              shouldShow={showRecordAudio}\n              onClose={() => {\n                setShowRecordAudio(false);\n              }}\n              cometChatBottomSheetStyle={{\n                maxHeight: Dimensions.get(\"window\").height * 0.4,\n              }}\n              onSend={_sendRecordedAudio}\n              mediaRecorderStyle={mergedComposerStyle.mediaRecorderStyle}\n            />\n\n            {showMentionList && (plainTextInput.current?.length ?? 0) > 0 && mentionsSearchData.length > 0 && (\n              <View\n                style={[\n                  theme.mentionsListStyle.containerStyle,\n                  // Keep height stable to reduce flicker when list data loads/empties\n                  { maxHeight: Dimensions.get(\"window\").height * (messagePreview ? 0.2 : (Platform.OS === \"ios\" ? 0.15 : 0.22)) },\n                ]}\n              >\n                <CometChatSuggestionList\n                  data={mentionsSearchData}\n                  listStyle={theme.mentionsListStyle}\n                  onPress={onMentionPress}\n                  onEndReached={onSuggestionListEndReached}\n                  loading={suggestionListLoader}\n                />\n              </View>\n            )}\n\n            <View\n              style={[\n                {\n                  flexDirection: \"column\",\n                  ...(messagePreview || replyMessage ? {\n                    backgroundColor: theme.color.background1,\n                    borderTopRightRadius: 8,\n                    borderTopLeftRadius: 8,\n                    paddingHorizontal: theme.spacing.padding.p1,\n                    // paddingTop: theme.spacing.padding.p1,\n                  } : {}),\n                },\n              ]}\n            >\n              {HeaderView\n                ? HeaderView({ user, group })\n                : CustomViewHeader &&\n                (typeof CustomViewHeader === \"function\" ? (\n                  <CustomViewHeader /> // Invoke CustomViewHeader if it's a functional component\n                ) : (\n                  CustomViewHeader // Render it directly if it's a React node\n                ))}\n              {messagePreview && (\n                <CometChatMessagePreview\n                  messagePreviewTitle={t(\"EDIT_MESSAGE\")}\n                  message={messagePreview?.message}\n                  showCloseIcon={true}\n                  closeIconURL={ICONS.CLOSE}\n                  onCloseClick={() => {\n                    setMessagePreview(null);\n                    clearEditPreview();\n                    mentionMap.current = new Map();\n                    plainTextInput.current = \"\";\n                    setOriginalText(\"\");\n                  }}\n                  style={{\n                    borderRadius: 8,\n                    borderWidth: 0,\n                    borderLeftWidth: 3,\n                    borderLeftColor: theme.color.borderHighlight,\n                    margin:theme.spacing.padding.p1,\n                  }}\n                />\n              )}\n              {replyMessage && replyMessage.message && (\n                <CometChatMessagePreview\n                  message={replyMessage.message}\n                  showCloseIcon={true}\n                  closeIconURL={ICONS.CLOSE}\n                  onCloseClick={() => {\n                    setReplyMessage(null);\n                  }}\n                  titleStyle={{ color: theme.color.textHighlight }}\n                  style={{\n                    borderRadius: 8,\n                    borderLeftWidth: 3,\n                    borderLeftColor: theme.color.borderHighlight,\n                    margin:theme.spacing.padding.p1,\n                  }}\n                />\n              )}\n            </View>\n            <CometChatMessageInput\n              messageInputRef={messageInputRef}\n              text={inputMessage as string}\n              placeHolderText={isAgenticUser()? t(\"ASK_ANYTHING\") : t(\"ENTER_YOUR_MESSAGE_HERE\")}\n              style={{\n                ...mergedComposerStyle.messageInputStyles,\n                containerStyle: {\n                  ...mergedComposerStyle.messageInputStyles?.containerStyle,\n                  ...(messagePreview || replyMessage ? {\n                    borderTopLeftRadius: 0,\n                    borderTopRightRadius: 0,\n                  } : {}),\n                },\n              }}\n              onSelectionChange={({ nativeEvent: { selection } }) => {\n                const cursorPos = selection.start;\n                const mentionRange = getMentionRangeAtCursor(mentionMap.current, cursorPos);\n\n                if (mentionRange) {\n                  console.log('Mention boundary positions:', mentionRange);\n                  const distanceToStart = cursorPos - mentionRange.start;\n                  const distanceToEnd = mentionRange.end - cursorPos;\n                  let targetPosition: number;\n                  if (distanceToStart <= distanceToEnd) {\n                    targetPosition = mentionRange.start;\n                  } else {\n                    targetPosition = mentionRange.end;\n                  }\n                  if (targetPosition !== cursorPos) {\n                    InteractionManager.runAfterInteractions(() => {\n                      setSelectionPosition({ start: targetPosition, end: targetPosition });\n                    });\n                    return;\n                  }\n                }\n\n                setSelectionPosition(selection);\n                openList(selection);\n              }}\n              selection={selectionPosition}\n              onChangeText={textChangeHandler}\n              VoiceRecordingButtonView={voiceRecoringButtonElem}\n              SecondaryButtonView={SecondaryButtonViewElem}\n              AuxiliaryButtonView={AuxiliaryButtonViewElem()}\n              PrimaryButtonView={SendButtonViewElem}\n              auxiliaryButtonAlignment={auxiliaryButtonsAlignment}\n            />\n          </View>\n        </KeyboardAvoidingView>\n        {CustomViewFooter ? (\n          // If CustomViewFooter is a function component (React.FC)\n          typeof CustomViewFooter === \"function\" ? (\n            <CustomViewFooter /> // Invoke the function component\n          ) : (\n            CustomViewFooter // Render it directly if it's a React node (JSX or element)\n          )\n        ) : null}\n      </>\n    );\n  }\n);\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageComposer/index.ts",
    "content": "import {\n  CometChatMessageComposer,\n  CometChatMessageComposerInterface,\n} from \"./CometChatMessageComposer\";\n\nexport {\n  CometChatMessageComposer,\n};\n\nexport type {\n  CometChatMessageComposerInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageComposer/resources/index.ts",
    "content": "import AI from \"./ai.png\";\nimport ADD from \"./circle_add.png\";\nimport CLOSE from \"./close.png\";\nimport EMOJI from \"./emoji.png\";\nimport HEART from \"./heart.png\";\nimport IMAGE from \"./image.png\";\nimport INFO from \"./Info.png\";\nimport MICROPHONE from \"./microphone.png\";\nimport SEND from \"./send_message.png\";\nimport STICKER from \"./stickers.png\";\n\nexport const ICONS = {\n  HEART,\n  SEND,\n  EMOJI,\n  ADD,\n  IMAGE,\n  CLOSE,\n  STICKER,\n  MICROPHONE,\n  AI,\n  INFO,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageComposer/styles.tsx",
    "content": "import {\n  ColorValue,\n  ImageSourcePropType,\n  ImageStyle,\n  StyleSheet,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { ActionSheetStyle, CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { JSX } from \"react\";\n\nexport const Style = StyleSheet.create({\n  container: {\n    paddingVertical: 8,\n  },\n  padding: {\n    paddingStart: 8,\n    paddingEnd: 8,\n  },\n  buttonContainerStyle: {\n    justifyContent: \"space-between\",\n  },\n  rowDirection: {\n    flexDirection: \"row\",\n  },\n  imageStyle: {},\n});\n\nconst getAttachmentOptionsStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ActionSheetStyle => {\n  return {\n    optionsItemStyle: {\n      containerStyle: {\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p4,\n        backgroundColor: color.background1,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 5,\n      },\n      titleStyle: {\n        ...typography.body.regular,\n        color: color.textPrimary,\n        paddingLeft: spacing.padding.p1,\n      },\n      iconStyle: {\n        height: 24,\n        width: 24,\n      },\n      iconContainerStyle: {},\n    },\n  };\n};\n\nexport type MessageComposerStyle = {\n  containerStyle: ViewStyle;\n  sendIcon?: ImageSourcePropType | JSX.Element;\n  sendIconStyle: ImageStyle;\n  sendIconContainerStyle: ViewStyle;\n  attachmentIcon?: ImageSourcePropType | JSX.Element;\n  attachmentIconStyle: ImageStyle;\n  voiceRecordingIcon: ImageSourcePropType | JSX.Element;\n  voiceRecordingIconStyle: ImageStyle;\n  messageInputStyles: {\n    containerStyle: ViewStyle;\n    textStyle: TextStyle;\n    placeHolderTextColor?: ColorValue | undefined;\n    dividerStyle: ViewStyle;\n  };\n  mentionsStyle: CometChatTheme[\"mentionsStyle\"];\n  stickerIcon: {\n    active: ImageSourcePropType | JSX.Element;\n    inactive: ImageSourcePropType | JSX.Element;\n  };\n  stickerIconStyle: {\n    active: ImageStyle;\n    inactive: ImageStyle;\n  };\n  mediaRecorderStyle: CometChatTheme[\"mediaRecorderStyle\"];\n  attachmentOptionsStyles: ActionSheetStyle;\n};\n\nexport const getComposerStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<MessageComposerStyle> => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      width: \"100%\",\n    },\n    messageInputStyles: {\n      containerStyle: {\n        borderRadius: spacing.radius.r2,\n        borderWidth: spacing.spacing.s0_5 / 2,\n        borderColor: color.borderDefault,\n        backgroundColor: color.background1,\n        borderTopWidth:0,\n      },\n      textStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p2,\n        color: color.textPrimary,\n        maxHeight: spacing.spacing.s20,\n        ...typography.body.regular,\n      },\n      placeHolderTextColor: color.textTertiary,\n      dividerStyle: {\n        height: 1,\n        backgroundColor: color.borderLight,\n        marginVertical: spacing.margin.m1,\n      },\n    },\n    attachmentIconStyle: {\n      tintColor: color.iconSecondary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    voiceRecordingIconStyle: {\n      tintColor: color.iconSecondary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    mentionsStyle: {\n      textStyle: {\n        ...typography.body.regular,\n        color: color.textHighlight,\n      },\n      selfTextStyle: {\n        ...typography.body.regular,\n        color: color.warning,\n      },\n    },\n    stickerIconStyle: {\n      inactive: {\n        tintColor: color.iconSecondary,\n      },\n      active: {\n        tintColor: color.primary,\n      },\n    },\n    attachmentOptionsStyles: getAttachmentOptionsStyle(color, spacing, typography),\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageHeader/CometChatMessageHeader.tsx",
    "content": "import React, { JSX, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Text, TouchableOpacity, View } from \"react-native\";\nimport { ChatConfigurator, getLastSeenTime } from \"../shared\";\nimport { listners } from \"./listners\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { GroupTypeConstants, UserStatusConstants } from \"../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CometChatUIEvents } from \"../shared/events/CometChatUIEvents\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { CometChatAvatar } from \"../shared/views/CometChatAvatar\";\nimport { CometChatStatusIndicator } from \"../shared/views/CometChatStatusIndicator\";\nimport { useTheme } from \"../theme\";\nimport { MessageHeaderStyle } from \"./styles\";\nimport { CommonUtils } from \"../shared/utils/CommonUtils\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\nimport { CometChatAIAssistantChatHistory } from \"../CometChatAIAssistantChatHistory/CometChatAIAssistantChatHistory\";\nimport { CometChatTooltipMenu, MenuItemInterface } from \"../shared/views/CometChatTooltipMenu\";\n\nexport type CometChatMessageHeaderInterface = {\n  /**\n   * Custom item view. Receives { user, group }.\n   */\n  ItemView?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => JSX.Element;\n  /**\n   * Custom leading view. Receives { user, group }.\n   */\n  LeadingView?: ({\n    user,\n    group,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  }) => JSX.Element;\n  /**\n   * Custom title view. Receives { user, group }.\n   */\n  TitleView?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => JSX.Element;\n  /**\n   * Custom subtitle view. Receives { user, group }.\n   */\n  SubtitleView?: ({\n    user,\n    group,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  }) => JSX.Element;\n  /**\n   * Custom trailing view. Receives { user, group }.\n   */\n  TrailingView?: ({\n    user,\n    group,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  }) => JSX.Element;\n  /**\n   * Custom auxiliary button view. Receives { user, group }.\n   */\n  AuxiliaryButtonView?: ({\n    user,\n    group,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n  }) => JSX.Element;\n  /**\n   * User object.\n   */\n  user?: CometChat.User;\n  /**\n   * Group object.\n   */\n  group?: CometChat.Group;\n  /**\n   * Hide the back button.\n   */\n  showBackButton?: boolean;\n  /**\n   * Callback when back button is pressed.\n   */\n  onBack?: () => void;\n  /**\n   * Custom styles.\n   */\n  style?: DeepPartial<MessageHeaderStyle>;\n  /**\n   * Error callback.\n   */\n  onError?: (error: CometChat.CometChatException) => void;\n  /**\n   * toggle visibilty of voice call button.\n   */\n  hideVoiceCallButton?: boolean;\n  /**\n   * toggle visibilty of video call button.\n   */\n  hideVideoCallButton?: boolean;\n  /**\n   * toggle visibilty of user status.\n   */\n  usersStatusVisibility?: boolean;\n  /**\n   * Flag to hide the new chat button for AI agents (only applies to @agentic users)\n   */\n  hideNewChatButton?: boolean;\n  /**\n   * Flag to hide the chat history button for AI agents (only applies to @agentic users)\n   */\n  hideChatHistoryButton?: boolean;\n  /**\n   * Callback when agent new chat button is clicked (only applies to @agentic users)\n   */\n  onNewChatButtonClick?: () => void;\n  /**\n   * Callback when agent chat history button is clicked (only applies to @agentic users)\n   */\n  onChatHistoryButtonClick?: () => void;\n  /**\n   * A function to **replace** the default menu items entirely.\n   */\n  options?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => MenuItemInterface[];\n};\n\ninterface Translations {\n  lastSeen: string;\n  minutesAgo: (minutes: number) => string;\n  hoursAgo: (hours: number) => string;\n}\n\n/** CometChatMessageHeader renders the header for a conversation. */\nexport const CometChatMessageHeader = (props: CometChatMessageHeaderInterface) => {\n  const userStatusListenerId = \"user_status_\" + new Date().getTime();\n  const msgTypingListenerId = \"message_typing_\" + new Date().getTime();\n  const groupListenerId = \"head_group_\" + new Date().getTime();\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const {\n    TitleView,\n    SubtitleView = null,\n    AuxiliaryButtonView,\n    user,\n    group,\n    showBackButton = false,\n    onBack,\n    style = {},\n    ItemView,\n    LeadingView,\n    TrailingView,\n    onError,\n    hideVoiceCallButton = false,\n    hideVideoCallButton = false,\n    usersStatusVisibility = true,\n    hideNewChatButton = false,\n    hideChatHistoryButton = false,\n    onNewChatButtonClick,\n    onChatHistoryButtonClick,\n    options,\n  } = props;\n\n  const [groupObj, setGroupObj] = useState(group);\n  const [userObj, setUserObj] = useState<CometChat.User | undefined>(user);\n  const [userStatus, setUserStatus] = useState(user && user.getStatus ? user.getStatus() : \"\");\n  const [typingText, setTypingText] = useState(\"\");\n  const [showHistoryModal, setShowHistoryModal] = useState(false);\n  const [showOptionsMenu, setShowOptionsMenu] = useState(false);\n  const [tooltipPosition, setTooltipPosition] = useState({ pageX: 0, pageY: 0 });\n  const receiverTypeRef = useRef(\n    user ? CometChat.RECEIVER_TYPE.USER : group ? CometChat.RECEIVER_TYPE.GROUP : null\n  );\n\n  // Helper function to check if user is agentic\n  const isAgenticUser = useCallback(() => {\n    return userObj?.getRole?.() === '@agentic';\n  }, [userObj]);\n\n  // Build menu items following CometChat pattern\n  const buildMenuItems = useCallback((): MenuItemInterface[] => {\n    if (options) return options({ user: userObj, group: groupObj });\n    \n    return [];\n  }, [options, userObj, groupObj, isAgenticUser]);\n\n  // Handle option selection\n  const handleOptionSelect = useCallback((item: MenuItemInterface) => {\n    setShowOptionsMenu(false);\n    if (item.onPress) {\n      item.onPress();\n    }\n  }, []);\n\n\n  useEffect(() => {\n    setGroupObj(group);\n  }, [group]);\n\n  useEffect(() => {\n    setUserStatus(userObj ? userObj.getStatus() : \"\");\n  }, [userObj]);\n\n  const messageHeaderStyles = useMemo(() => {\n    return deepMerge(theme.messageHeaderStyles, style);\n  }, [theme.messageHeaderStyles, style]);\n\n  const translations: Translations = {\n    lastSeen: \"Last seen\",\n    minutesAgo: (minutes: number) => `${minutes} minute${minutes === 1 ? \"\" : \"s\"} ago`,\n    hoursAgo: (hours: number) => `${hours} hour${hours === 1 ? \"\" : \"s\"} ago`,\n  };\n\n  /**\n   * Renders the back button.\n   */\n  const BackButton = useCallback(\n    () => (\n      <TouchableOpacity style={[messageHeaderStyles.backButtonStyle]} onPress={onBack}>\n        <Icon\n          name='arrow-back-fill'\n          size={messageHeaderStyles.backButtonIconStyle.width}\n          height={messageHeaderStyles.backButtonIconStyle.height}\n          width={messageHeaderStyles.backButtonIconStyle.width}\n          color={messageHeaderStyles.backButtonIconStyle.tintColor}\n          icon={messageHeaderStyles.backButtonIcon}\n          imageStyle={messageHeaderStyles.backButtonIconStyle}\n        />\n      </TouchableOpacity>\n    ),\n    [onBack, messageHeaderStyles]\n  );\n\n  const statusIndicatorType = useMemo(() => {\n    if (groupObj?.getType() === GroupTypeConstants.password) return \"password\";\n    if (groupObj?.getType() === GroupTypeConstants.private) return \"private\";\n    if (userStatus === \"online\") return \"online\";\n    return \"offline\";\n  }, [userStatus, groupObj]);\n\n  /**\n   * Renders the avatar with a status indicator.\n   */\n  const AvatarWithStatusView = useCallback(() => {\n    try {\n      return (\n        <View>\n          <CometChatAvatar\n            style={messageHeaderStyles.avatarStyle}\n            image={\n              userObj\n                ? userObj.getAvatar()\n                  ? { uri: userObj.getAvatar() }\n                  : undefined\n                : groupObj\n                  ? groupObj.getIcon()\n                    ? { uri: groupObj.getIcon() }\n                    : undefined\n                  : undefined\n            }\n            name={(userObj?.getName() ?? groupObj?.getName())!}\n          />\n        </View>\n      );\n    } catch (e) {\n      errorHandler(e);\n      return <></>;\n    }\n  }, [userObj, groupObj, statusIndicatorType, messageHeaderStyles]);\n\n  /**\n   * Renders subtitle view content.\n   */\n  const SubtitleViewFnc = useCallback(() => {\n    try {\n      if (typingText !== \"\")\n        return (\n          <Text\n            numberOfLines={1}\n            ellipsizeMode='tail'\n            style={[messageHeaderStyles.typingIndicatorTextStyle]}\n          >\n            {typingText}\n          </Text>\n        );\n      let subtitle = \"\";\n\n      if (groupObj) {\n        const count = groupObj?.[\"membersCount\"];\n        if (count || count === 0) {\n          subtitle = `${count} ${t(count === 1 ? \"MEMBER\" : \"MEMBERS\")}`;\n        }\n      }\n\n      if (\n        userObj &&\n        !(userObj.getBlockedByMe() || userObj.getHasBlockedMe()) &&\n        usersStatusVisibility &&\n        userStatus\n      ) {\n        subtitle =\n          userStatus === UserStatusConstants.online\n            ? t(\"ONLINE\")\n            : getLastSeenTime(userObj.getLastActiveAt()); // Updated to use getLastSeenTime function\n      }\n\n      if (subtitle) {\n        return <Text style={[messageHeaderStyles.subtitleTextStyle]}>{subtitle}</Text>;\n      }\n\n      return <></>;\n    } catch (error) {\n      errorHandler(error);\n      return <></>;\n    }\n  }, [userObj, groupObj, messageHeaderStyles, usersStatusVisibility, userStatus]);\n\n  /**\n   * Error handler to call onError with a proper CometChatException.\n   */\n  const errorHandler = (error: any) => {\n    if (error instanceof CometChat.CometChatException) {\n      onError && onError(error);\n    } else if (error instanceof Error) {\n      onError &&\n        onError(\n          new CometChat.CometChatException({\n            code: \"ERR_SYSTEM\",\n            details: error.stack,\n            message: error.message,\n          })\n        );\n    }\n  };\n\n  const handleUserStatus = (userDetails: CometChat.User) => {\n    if (userDetails.getUid() === userObj?.getUid()) {\n      setUserObj(userDetails); \n      setUserStatus(userDetails.getStatus());\n    }\n  };\n\n  const msgTypingIndicator = (typist: CometChat.TypingIndicator, status: string) => {\n    if (\n      receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&\n      receiverTypeRef.current === typist.getReceiverType() &&\n      groupObj?.getGuid() === typist.getReceiverId()\n    ) {\n      setTypingText(\n        status === \"typing\" ? `${typist.getSender().getName()}: ${t(\"IS_TYPING\")}` : \"\"\n      );\n    } else if (\n      receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER &&\n      receiverTypeRef.current === typist.getReceiverType() &&\n      userObj?.getUid() === typist.getSender().getUid() &&\n      !(userObj.getBlockedByMe() || userObj.getHasBlockedMe())\n    ) {\n      setTypingText(status === \"typing\" ? t(\"TYPING\") : \"\");\n    }\n  };\n\n  const handleGroupListener = (groupDetails: CometChat.Group) => {\n    if (groupDetails?.getGuid() === groupObj?.getGuid() && groupDetails.getMembersCount()) {\n      setGroupObj(CommonUtils.clone(groupDetails));\n    }\n  };\n\n  useEffect(() => {\n    try {\n      if (userObj) {\n        listners.addListener.userListener({ userStatusListenerId, handleUserStatus });\n        receiverTypeRef.current = CometChat.RECEIVER_TYPE.USER;\n\n        CometChatUIEventHandler.addUserListener(userStatusListenerId, {\n          ccUserBlocked: (item: { user: CometChat.User }) => handleccUserBlocked(item),\n          ccUserUnBlocked: (item: { user: CometChat.User }) => handleccUserUnBlocked(item),\n        });\n      }\n      if (groupObj) {\n        listners.addListener.groupListener({ groupListenerId, handleGroupListener });\n        receiverTypeRef.current = CometChat.RECEIVER_TYPE.GROUP;\n      }\n      listners.addListener.messageListener({ msgTypingListenerId, msgTypingIndicator });\n    } catch (error) {\n      errorHandler(error);\n    }\n    return () => {\n      try {\n        if (groupObj) listners.removeListner.removeGroupListener({ groupListenerId });\n        if (userObj) {\n          listners.removeListner.removeUserListener({ userStatusListenerId });\n          CometChatUIEventHandler.removeUserListener(userStatusListenerId);\n        }\n        listners.removeListner.removeMessageListener({ msgTypingListenerId });\n      } catch (cleanupError) {\n        errorHandler(cleanupError);\n      }\n    };\n  }, [userObj]);\n\n  const handleccUserBlocked = ({ user: blockedUser }: { user: CometChat.User }) => {\n    if (userObj && userObj.getUid() === blockedUser.getUid()) {\n      const tempUser = CommonUtils.clone(userObj);\n      tempUser.setBlockedByMe(true);\n      setUserObj(tempUser);\n    }\n  };\n\n  const handleccUserUnBlocked = ({ user: unBlockedUser }: { user: CometChat.User }) => {\n    if (userObj && userObj.getUid() === unBlockedUser.getUid()) {\n      setUserObj(unBlockedUser);\n    }\n  };\n\n  const handleGroupMemberKicked = ({ kickedFrom }: { kickedFrom: CometChat.Group }) => {\n    setGroupObj(CommonUtils.clone(kickedFrom));\n  };\n  const handleGroupMemberBanned = ({ kickedFrom }: { kickedFrom: CometChat.Group }) => {\n    setGroupObj(CommonUtils.clone(kickedFrom));\n  };\n  const handleGroupMemberAdded = ({ userAddedIn }: { userAddedIn: CometChat.Group }) => {\n    setGroupObj(CommonUtils.clone(userAddedIn));\n  };\n  const handleOwnershipChanged = ({ group }: { group: CometChat.Group }) => {\n    setGroupObj(CommonUtils.clone(group));\n  };\n\n  useEffect(() => {\n    try {\n      CometChatUIEventHandler.addGroupListener(groupListenerId, {\n        ccGroupMemberKicked: (item: any) => handleGroupMemberKicked(item),\n        ccGroupMemberBanned: (item: any) => handleGroupMemberBanned(item),\n        ccGroupMemberAdded: (item: any) => handleGroupMemberAdded(item),\n        ccOwnershipChanged: (item: any) => handleOwnershipChanged(item),\n      });\n    } catch (e) {\n      errorHandler(e);\n    }\n    return () => {\n      try {\n        CometChatUIEventHandler.removeGroupListener(groupListenerId);\n      } catch (e) {\n        errorHandler(e);\n      }\n    };\n  }, []);\n\n  /**\n   * Renders AI agent auxiliary buttons (new chat, history, close)\n   */\n  const renderAgentAuxiliaryView = () => {\n    const iconSecondary = theme.color.iconSecondary;\n\n    const handleNewChat = () => {\n      if (onNewChatButtonClick) {\n        onNewChatButtonClick();\n      }\n    };\n\n    const handleChatHistory = () => {\n      if (onChatHistoryButtonClick) {\n        onChatHistoryButtonClick();\n      }\n    };\n\n    return (\n      <View style={{ flexDirection: \"row\", alignItems: \"center\" }}>\n        {!hideNewChatButton && (\n          <TouchableOpacity\n            style={{\n              borderRadius: 12,\n              padding: 8,\n              alignItems: \"center\",\n              justifyContent: \"center\",\n              flexDirection: \"row\",\n            }}\n            onPress={handleNewChat}\n          >\n            <Icon name=\"ai-new-chat\" width={24} height={24} color={iconSecondary} />\n          </TouchableOpacity>\n        )}\n\n        {!hideChatHistoryButton && (\n          <TouchableOpacity\n            style={{\n              borderRadius: 12,\n              padding: 8,\n              alignItems: \"center\",\n              justifyContent: \"center\",\n              flexDirection: \"row\",\n            }}\n            onPress={handleChatHistory}\n          >\n            <Icon name=\"ai-chat-history\" width={24} height={24} color={iconSecondary} />\n          </TouchableOpacity>\n        )}\n      </View>\n    );\n  };\n\n  const menuItems = buildMenuItems();\n\n  return (\n    <>\n      {ItemView ? (\n        ItemView({ user: userObj, group })\n      ) : (\n        <View style={[messageHeaderStyles.containerStyle]}>\n          {showBackButton === true && <BackButton />}\n          {LeadingView ? LeadingView({ user: userObj, group }) : <AvatarWithStatusView />}\n          <View style={{ flex: 1, justifyContent: \"center\" }}>\n            {TitleView ? (\n              TitleView({ user: userObj, group })\n            ) : (\n              <Text\n                numberOfLines={1}\n                ellipsizeMode='tail'\n                style={[messageHeaderStyles.titleTextStyle]}\n              >\n                {userObj ? userObj.getName() : groupObj ? groupObj.getName() : \"\"}\n              </Text>\n            )}\n            {SubtitleView ? SubtitleView({ user: userObj, group }) : <SubtitleViewFnc />}\n          </View>\n          <View style={{ \n            flex: isAgenticUser() ? 0 : 1, \n            flexDirection: \"row\",\n            alignItems: \"center\"\n          }}>\n            <View style={{ marginLeft: \"auto\", flexDirection: \"row\" }}>\n                {(() => {\n                  const isAgenticUser = userObj?.getRole?.() === '@agentic';\n                  \n                  if (isAgenticUser && !AuxiliaryButtonView) {\n                    return renderAgentAuxiliaryView();\n                  }\n                  \n                  return AuxiliaryButtonView\n                    ? AuxiliaryButtonView({ user: userObj, group })\n                    : ChatConfigurator.getDataSource().getAuxiliaryHeaderAppbarOptions(userObj, group, {\n                        callButtonStyle: messageHeaderStyles.callButtonStyle,\n                        hideVideoCallButton,\n                        hideVoiceCallButton,\n                      });\n                })()}\n              {TrailingView && !isAgenticUser() && (\n                <View style={{ marginLeft: theme.spacing.padding.p4 }}>\n                  {TrailingView({ user: userObj, group })}\n                </View>\n              )}\n              {menuItems.length > 0 && (\n                <TouchableOpacity\n                style={{ marginLeft: theme.spacing.padding.p2 }}\n                  onPress={(e) => {\n                    if (e.nativeEvent) {\n                      setTooltipPosition({\n                        pageX: e.nativeEvent.pageX || 200,\n                        pageY: e.nativeEvent.pageY || 100,\n                      });\n                    }\n                    setShowOptionsMenu(true);\n                  }}\n                >\n                  <Icon\n                    name=\"more-vert\"\n                    width={24}\n                    height={24}\n                    color={messageHeaderStyles.backButtonIconStyle.tintColor}\n                  />\n                </TouchableOpacity>\n              )}\n            </View>\n          </View>\n        </View>\n      )}\n\n      {menuItems.length > 0 && (\n        <CometChatTooltipMenu\n          visible={showOptionsMenu}\n          onClose={() => setShowOptionsMenu(false)}\n          event={{\n            nativeEvent: tooltipPosition,\n          }}\n          menuItems={menuItems.map((item) => ({\n            text: item.text,\n            onPress: () => {\n              handleOptionSelect(item);\n            },\n            icon: item.icon,\n            textStyle: item.textStyle,\n          }))}\n        />\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageHeader/index.ts",
    "content": "import { CometChatMessageHeader, CometChatMessageHeaderInterface } from \"./CometChatMessageHeader\";\nexport { CometChatMessageHeader };\nexport type {CometChatMessageHeaderInterface};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageHeader/listners.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatUIEventHandler, CometChatUIKit } from \"../shared\";\n\nexport const listners = {\n  addListener: {\n    userListener: ({ userStatusListenerId, handleUserStatus }: any) =>\n      CometChat.addUserListener(\n        userStatusListenerId,\n        new CometChat.UserListener({\n          onUserOnline: (onlineUser: CometChat.User) => {\n            handleUserStatus(onlineUser);\n            /* when someuser/friend comes online, user will be received here */\n          },\n          onUserOffline: (offlineUser: CometChat.User) => {\n            handleUserStatus(offlineUser);\n            /* when someuser/friend went offline, user will be received here */\n          },\n        })\n      ),\n    messageListener: ({ msgTypingListenerId, msgTypingIndicator }: any) =>\n      CometChatUIEventHandler.addMessageListener(msgTypingListenerId, {\n        onTypingStarted: (typistDetails: CometChat.TypingIndicator) => {\n          msgTypingIndicator(typistDetails, \"typing\");\n        },\n        onTypingEnded: (typistDetails: CometChat.TypingIndicator) => {\n          msgTypingIndicator(typistDetails, \"\");\n        },\n      }),\n    groupListener: ({ groupListenerId, handleGroupListener }: any) =>\n      CometChat.addGroupListener(\n        groupListenerId,\n        new CometChat.GroupListener({\n          onGroupMemberKicked: (\n            message: any,\n            kickedUser: CometChat.User,\n            kickedBy: CometChat.User,\n            kickedFrom: CometChat.Group\n          ) => {\n            handleGroupListener(kickedFrom);\n          },\n          onGroupMemberBanned: (\n            message: any,\n            bannedUser: CometChat.User,\n            bannedBy: CometChat.User,\n            bannedFrom: CometChat.Group\n          ) => {\n            handleGroupListener(bannedFrom);\n          },\n          onMemberAddedToGroup: (\n            message: any,\n            userAdded: CometChat.User,\n            userAddedBy: CometChat.User,\n            userAddedIn: CometChat.Group\n          ) => {\n            console.log(\"onMemberAddedToGroup\", userAddedIn);\n            handleGroupListener(userAddedIn);\n          },\n          onGroupMemberLeft: (\n            message: any,\n            leavingUser: CometChat.User,\n            group: CometChat.Group\n          ) => {\n            handleGroupListener(group);\n          },\n          onGroupMemberJoined: (\n            message: any,\n            joinedUser: CometChat.User,\n            joinedGroup: CometChat.Group\n          ) => {\n            handleGroupListener(joinedGroup);\n          },\n          onGroupMemberScopeChanged: (\n            message: CometChat.Action,\n            changedUser: CometChat.GroupMember,\n            newScope: CometChat.GroupMemberScope,\n            oldScope: CometChat.GroupMemberScope,\n            changedGroup: CometChat.Group\n          ) => {\n            if (changedUser.getUid() !== CometChatUIKit.loggedInUser?.getUid()) return;\n            changedGroup.setScope(newScope);\n            handleGroupListener(changedGroup);\n          },\n        })\n      ),\n  },\n  removeListner: {\n    removeUserListener: ({ userStatusListenerId }: any) =>\n      CometChat.removeUserListener(userStatusListenerId),\n\n    removeMessageListener: ({ msgTypingListenerId }: any) =>\n      CometChatUIEventHandler.removeMessageListener(msgTypingListenerId),\n\n    removeGroupListener: ({ groupListenerId }: any) =>\n      CometChat.removeGroupListener(groupListenerId),\n  },\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageHeader/styles.ts",
    "content": "import { ImageSourcePropType, ImageStyle, StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CallButtonStyle } from \"../calls/CometChatCallButtons\";\nimport { AvatarStyle } from \"../shared/views/CometChatAvatar\";\nimport { StatusIndicatorStyles } from \"../shared/views/CometChatStatusIndicator\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { JSX } from \"react\";\n\nexport const styles = StyleSheet.create({\n  container: { flexDirection: \"row\", width: \"100%\" },\n  backButtonIconStyle: {\n    height: 27,\n    width: 27,\n    resizeMode: \"contain\",\n  },\n});\n\nexport type MessageHeaderStyle = {\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  subtitleTextStyle: TextStyle;\n  backButtonStyle: ViewStyle;\n  backButtonIcon?: ImageSourcePropType | JSX.Element;\n  backButtonIconStyle: ImageStyle;\n  typingIndicatorTextStyle: TextStyle;\n  callButtonStyle: Partial<CallButtonStyle>;\n  avatarStyle: Partial<AvatarStyle>;\n  statusIndicatorStyle: Partial<StatusIndicatorStyles>;\n};\n\nexport const getMessageHeaderStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): MessageHeaderStyle => ({\n  containerStyle: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    backgroundColor: color.background1,\n    gap: 8,\n    paddingHorizontal: spacing.padding.p4,\n    paddingVertical: spacing.padding.p3,\n  },\n  backButtonStyle: {marginHorizontal: 2},\n  titleTextStyle: {\n    color: color.textPrimary,\n    ...typography.heading4.medium,\n  },\n  subtitleTextStyle: {\n    color: color.textSecondary,\n    ...typography.caption1.regular,\n  },\n  backButtonIconStyle: {\n    height: 27,\n    width: 27,\n    tintColor: color.iconPrimary,\n  },\n  typingIndicatorTextStyle: {\n    color: color.textHighlight,\n    ...typography.caption1.regular,\n  },\n  callButtonStyle: {},\n  avatarStyle: {},\n  statusIndicatorStyle: {},\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageInformation/CometChatMessageInformation.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX, useCallback, useEffect, useMemo, useState } from \"react\";\nimport { FlatList, ScrollView, Text, View } from \"react-native\";\nimport {\n  ChatConfigurator,\n  CometChatListItem,\n  CometChatMessageTemplate,\n  CometChatUIEventHandler,\n  CometChatUiKitConstants,\n} from \"../shared\";\nimport { MessageUtils } from \"../shared/utils/MessageUtils\";\nimport { Style } from \"./styles\";\nimport { useTheme } from \"../theme\";\nimport { CometChatDate } from \"../shared/views/CometChatDate\";\nimport { CometChatReceipt } from \"../shared/views\";\nimport { Skeleton } from \"./Skeleton\";\nimport { ErrorEmptyView } from \"../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { MessageReceipt } from \"../shared/constants/UIKitConstants\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\nconst listenerId = \"uiEvents_\" + new Date().getTime();\n\ntype Recipient = {\n  sender: CometChat.User | CometChat.Group;\n  deliveredAt: number;\n  readAt: number;\n  sentAt: number;\n};\n\nexport interface CometChatMessageInformationInterface {\n  title?: string;\n  message: CometChat.BaseMessage;\n  template?: CometChatMessageTemplate;\n  BubbleView?: (message: CometChat.BaseMessage) => JSX.Element;\n  ListItemView?: (message: CometChat.BaseMessage, receipt: Recipient) => JSX.Element;\n  receiptDatePattern?: (timestamp: any) => string;\n  onBack?: () => void;\n  onError?: (e: CometChat.CometChatException) => void;\n  EmptyStateView?: () => JSX.Element;\n  emptyStateText?: string;\n  ErrorStateView?: () => JSX.Element;\n  errorStateText?: string;\n  LoadingStateView?: () => JSX.Element;\n  style?: DeepPartial<CometChatTheme[\"messageInformationStyles\"]>;\n}\n\n/**\n * CometChatMessageInformation component renders the message information view along with message receipts.\n *\n * @param props - Properties for configuring the component.\n * @returns A JSX.Element containing the rendered message information.\n */\nexport const CometChatMessageInformation = (props: CometChatMessageInformationInterface) => {\n  const {t} = useCometChatTranslation()\n  const {\n    title = t(\"MESSAGE_INFORMATION\"),\n    message,\n    template,\n    BubbleView,\n    ListItemView,\n    receiptDatePattern,\n    onBack,\n    onError,\n    EmptyStateView,\n    emptyStateText,\n    ErrorStateView,\n    errorStateText,\n    LoadingStateView,\n    style,\n  } = props;\n\n  const defaultReceiptTimestampForUser = \"----\";\n  const theme = useTheme();\n  const mergedStyle = useMemo(() => {\n    return deepMerge(theme.messageInformationStyles, style ?? {});\n  }, [theme, style]);\n\n  const [receipts, setReceipts] = useState<\n    Array<\n      | {\n          sender: CometChat.User | CometChat.Group;\n          sentAt: number;\n          readAt: number;\n          deliveredAt: number;\n        }\n      | CometChat.MessageReceipt\n    >\n  >([]);\n  const [listState, setListState] = useState<\"loading\" | \"error\" | \"done\">(\"loading\");\n\n  /**\n   * Renders a receipt view for a given status and time.\n   * Captures variables from the component scope (e.g. message, mergedStyle, theme).\n   *\n   * @param receipt - The receipt data or status identifier.\n   * @param status - The receipt status (e.g. MessageReceipt.READ).\n   * @param time - The timestamp for the receipt.\n   * @returns A JSX.Element representing the receipt view.\n   */\n  const receipt = (receipt: any, status: MessageReceipt, time: any) => {\n    // If a custom ListItemView is provided in props, use it.\n    if (ListItemView) {\n      return ListItemView(message, receipt);\n    }\n    const receiverTypeIsUser =\n      message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.user;\n    const statusTextStyle = receiverTypeIsUser\n      ? mergedStyle.receiptItemStyle.titleStyle\n      : mergedStyle.receiptItemStyle.subtitleStyle;\n    return (\n      <View style={{ flexDirection: receiverTypeIsUser ? \"column\" : \"row\" }}>\n        <View\n          style={{\n            flexDirection: \"row\",\n            flex: 1,\n            gap:\n              message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.user\n                ? theme.spacing.padding.p1\n                : 0,\n          }}\n        >\n          {receiverTypeIsUser && (\n            <CometChatReceipt receipt={status} style={mergedStyle.receiptItemStyle.receiptStyles} />\n          )}\n          <Text style={statusTextStyle}>{t(status)}</Text>\n        </View>\n        {time && time !== defaultReceiptTimestampForUser ? (\n          receiptDatePattern ? (\n            receiptDatePattern(time)\n          ) : (\n            <CometChatDate\n              style={{ textStyle: mergedStyle.receiptItemStyle.subtitleStyle }}\n              pattern=\"dayDateTimeFormat\"\n              timeStamp={time*1000}\n            />\n          )\n        ) : (\n          <Text style={mergedStyle.receiptItemStyle.subtitleStyle}>\n            {defaultReceiptTimestampForUser}\n          </Text>\n        )}\n      </View>\n    );\n  };\n\n  /**\n   * Renders a list of receipts for a given recipient item.\n   *\n   * @param item - The receipt item data.\n   * @returns A JSX.Element containing the rendered receipts.\n   */\n  const receiptsList = (item: any) => {\n    if (message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.user) {\n      item.readAt = item.readAt || defaultReceiptTimestampForUser;\n      item.deliveredAt = item.deliveredAt || defaultReceiptTimestampForUser;\n    }\n    return (\n      <View\n        style={{\n          gap:\n            message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.user\n              ? theme.spacing.padding.p3\n              : 0,\n        }}\n      >\n        {item.readAt && receipt(\"READ\", MessageReceipt.READ, item.readAt)}\n        {item.deliveredAt && receipt(\"DELIVERED\", MessageReceipt.DELIVERED, item.deliveredAt)}\n      </View>\n    );\n  };\n\n  /**\n   * Renders each recipient as a list item.\n   *\n   * @param param0 - Object containing the current item and its index.\n   * @returns A JSX.Element representing a single recipient list item.\n   */\n  const renderReceipients = useCallback(\n    ({ item, index }: any) => {\n      const { sender } = item;\n      return (\n        <CometChatListItem\n          id={sender[\"uid\"]}\n          {...(message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.group\n            ? { avatarName: sender[\"name\"] }\n            : {})}\n          {...(message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.group\n            ? { avatarURL: sender[\"avatar\"] }\n            : {})}\n          SubtitleView={receiptsList(item)}\n          {...(message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.group\n            ? { title: sender[\"name\"] }\n            : {})}\n          containerStyle={mergedStyle.receiptItemStyle.containerStyle}\n          titleStyle={mergedStyle.receiptItemStyle.titleStyle}\n          avatarStyle={mergedStyle.receiptItemStyle.avatarStyle}\n        />\n      );\n    },\n    [receipts, mergedStyle]\n  );\n\n  /**\n   * Checks if the current message is for a group.\n   *\n   * @returns True if the receiver type is \"group\"; otherwise, false.\n   */\n  const isGroup = () => {\n    return message?.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.group;\n  };\n\n  /**\n   * Populates receipts for the current message.\n   * For group messages, it fetches receipts from the server.\n   * For user messages, it creates a single receipt object.\n   */\n  const populateReceipts = () => {\n    if (isGroup()) {\n      setListState(\"loading\");\n      CometChat.getMessageReceipts(message.getId())\n        .then((receiptsList) => {\n          setReceipts(receiptsList);\n          setListState(\"done\");\n        })\n        .catch((er) => {\n          onError && onError(er);\n          setListState(\"error\");\n        });\n    } else {\n      const userReceipt = {\n        sender: isGroup() ? message.getSender() : message.getReceiver(),\n        sentAt: message.getSentAt(),\n        readAt: message.getReadAt(),\n        deliveredAt: message.getDeliveredAt(),\n      };\n      setReceipts([userReceipt]);\n    }\n  };\n\n  /**\n   * Updates the message receipt when a new receipt is received.\n   *\n   * @param newReceipt - The new message receipt.\n   */\n  const updateMessageReceipt = useCallback(\n    (newReceipt: CometChat.MessageReceipt) => {\n      if (message.getId() === Number(newReceipt.getMessageId())) {\n        if (message.getReceiverType() === CometChatUiKitConstants.ReceiverTypeConstants.user) {\n          const udpatedReceipt = {\n            sender: message.getReceiver(),\n            sentAt: message.getSentAt(),\n            readAt: message.getReadAt() || newReceipt.getReadAt(),\n            deliveredAt: message.getDeliveredAt() || newReceipt.getDeliveredAt(),\n          };\n          setReceipts([udpatedReceipt]);\n          return;\n        }\n\n        const receiptIndex = receipts.findIndex((rec) => {\n          return (\n            (rec as CometChat.MessageReceipt).getSender().getUid() ===\n            newReceipt.getSender().getUid()\n          );\n        });\n\n        if (receiptIndex > -1) {\n          let oldReceipt = receipts[receiptIndex] as CometChat.MessageReceipt;\n          oldReceipt.setDeliveredAt(oldReceipt.getDeliveredAt() || newReceipt.getDeliveredAt());\n          oldReceipt.setReadAt(oldReceipt.getReadAt() || newReceipt.getReadAt());\n          let receiptsList = [...receipts];\n          receiptsList.splice(receiptIndex, 1, oldReceipt);\n          setReceipts(receiptsList);\n        } else {\n          setReceipts([newReceipt, ...receipts]);\n        }\n      }\n    },\n    [receipts, message]\n  );\n\n  // Add listener for message delivery/read events.\n  useEffect(() => {\n    CometChatUIEventHandler.addMessageListener(listenerId, {\n      onMessagesDelivered: (messageReceipt: CometChat.MessageReceipt) => {\n        updateMessageReceipt(messageReceipt);\n      },\n      onMessagesRead: (messageReceipt: CometChat.MessageReceipt) => {\n        updateMessageReceipt(messageReceipt);\n      },\n    });\n    return () => CometChatUIEventHandler.removeMessageListener(listenerId);\n  }, [message, receipts, updateMessageReceipt]);\n\n  // Populate receipts when the message changes.\n  useEffect(() => {\n    populateReceipts();\n  }, [message]);\n\n  /**\n   * Renders the loading state view.\n   *\n   * @returns A JSX.Element representing the loading view.\n   */\n  const LoadingView = () => {\n    if (LoadingStateView) return <LoadingStateView />;\n    return <Skeleton />;\n  };\n\n  /**\n   * Renders the error state view.\n   *\n   * @returns A JSX.Element representing the error view.\n   */\n  const ErrorView = () => {\n    if (ErrorStateView) return <ErrorStateView />;\n    return (\n      <ErrorEmptyView\n        subTitle={t(\"WRONG_TEXT\")}\n        tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n        containerStyle={mergedStyle.errorStateStyle?.containerStyle}\n        titleStyle={mergedStyle.errorStateStyle?.titleStyle}\n        subTitleStyle={mergedStyle.errorStateStyle?.subtitleStyle}\n      />\n    );\n  };\n\n  /**\n   * Renders the empty state view.\n   *\n   * @returns A JSX.Element representing the empty view.\n   */\n  const EmptyView = useCallback(() => {\n    if (EmptyStateView) return <EmptyStateView />;\n    return <></>;\n  }, [message, receipts]);\n\n  return (\n    <View style={mergedStyle.containerStyle}>\n      <View style={mergedStyle.titleContainerStyle}>\n        <View style={{ flex: 1, alignItems: \"center\" }}>\n          <Text style={mergedStyle.titleStyle}>{title}</Text>\n        </View>\n      </View>\n      <View style={mergedStyle.messageBubbleContainerStyle}>\n        <ScrollView>\n          <View pointerEvents='none'>\n            {BubbleView\n              ? BubbleView(message)\n              : MessageUtils.getMessageView({\n                  message,\n                  templates: template\n                    ? [template]\n                    : ChatConfigurator.dataSource.getAllMessageTemplates(theme),\n                  alignment: \"right\",\n                  theme: theme,\n                })}\n          </View>\n        </ScrollView>\n      </View>\n      {listState === \"loading\" && receipts.length === 0 ? (\n        <LoadingView />\n      ) : listState === \"error\" && receipts.length === 0 ? (\n        <ErrorView />\n      ) : receipts.length === 0 ? (\n        <EmptyView />\n      ) : (\n        <FlatList\n          style={{ flex: 1, marginBottom: 50 }}\n          data={receipts}\n          renderItem={renderReceipients}\n        />\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageInformation/Skeleton.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, G, LinearGradient, Path, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../theme\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonItemBottom = () => {\n  const theme = useTheme();\n  return (\n    <Svg\n      height={screenWidth / 5}\n      viewBox='0 0 360 72'\n      fill='none'\n      preserveAspectRatio='xMidYMid meet'\n    >\n      <G>\n        <Path d='M64 12H16V60H64V12Z' fill='url(#paint0_linear_4_115)' />\n      </G>\n      <Path d='M236 16.5H76V35.5H236V16.5Z' fill='url(#paint0_linear_4_115)' />\n      <Path d='M344 16.5H284V35.5H344V16.5Z' fill='url(#paint0_linear_4_115)' />\n      <Path d='M344 43.5H76V55.5H344V43.5Z' fill='url(#paint0_linear_4_115)' />\n      <Defs>\n        <LinearGradient\n          id='paint0_linear_4_115'\n          x1={16}\n          y1={36}\n          x2={64}\n          y2={36}\n          gradientUnits='userSpaceOnUse'\n        >\n          <Stop stopColor={theme.reactionListStyles.skeletonStyle.linearGradientColors[0]} />\n          <Stop\n            offset={1}\n            stopColor={theme.reactionListStyles.skeletonStyle.linearGradientColors[1]}\n          />\n        </LinearGradient>\n      </Defs>\n    </Svg>\n  );\n};\n\nconst SkeletonItemTop = () => {\n  const theme = useTheme();\n  return (\n    <Svg height={screenWidth / 5} viewBox='0 0 360 72' fill='none'>\n      <Path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M0 0H360V72H0V0ZM16 36C16 22.7452 26.7452 12 40 12C53.2548 12 64 22.7452 64 36C64 49.2548 53.2548 60 40 60C26.7452 60 16 49.2548 16 36ZM84 16.5C79.5817 16.5 76 20.0817 76 24.5V27.5C76 31.9183 79.5817 35.5 84 35.5H228C232.418 35.5 236 31.9183 236 27.5V24.5C236 20.0817 232.418 16.5 228 16.5H84ZM284 24.5C284 20.0817 287.582 16.5 292 16.5H336C340.418 16.5 344 20.0817 344 24.5V27.5C344 31.9183 340.418 35.5 336 35.5H292C287.582 35.5 284 31.9183 284 27.5V24.5ZM82 43.5C78.6863 43.5 76 46.1863 76 49.5C76 52.8137 78.6863 55.5 82 55.5H338C341.314 55.5 344 52.8137 344 49.5C344 46.1863 341.314 43.5 338 43.5H82Z'\n        fill={theme.color.background2}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const theme = useTheme();\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / theme.messageInformationStyles.skeletonStyle.speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n  }, [animatedValue]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth * 2, screenWidth],\n  });\n\n  return (\n    <ScrollView>\n      {new Array(20).fill(0).map((_, i) => {\n        return <SkeletonItemBottom key={i} />;\n      })}\n      <Animated.View\n        style={[\n          {\n            transform: [\n              { translateX: shimmerTranslateX },\n              { translateY: -20 },\n              { rotate: \"15deg\" },\n            ],\n          },\n          styles.animatedView,\n          {\n            backgroundColor: theme.messageInformationStyles.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.messageInformationStyles.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      ></Animated.View>\n      <Animated.View\n        style={[\n          {\n            transform: [\n              { translateX: Animated.add(shimmerTranslateX, screenWidth / 2) },\n              { translateY: -20 },\n              { rotate: \"15deg\" },\n            ],\n          },\n          styles.animatedView,\n          {\n            backgroundColor: theme.messageInformationStyles.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.messageInformationStyles.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      ></Animated.View>\n      <View\n        style={{\n          position: \"absolute\",\n          top: 0,\n          left: 0,\n          right: 0,\n          bottom: 0,\n        }}\n      >\n        {new Array(20).fill(0).map((_, i) => {\n          return <SkeletonItemTop key={i} />;\n        })}\n      </View>\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  animatedView: {\n    width: \"25%\",\n    top: 0,\n    bottom: 0,\n    position: \"absolute\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageInformation/index.ts",
    "content": "import {\n  CometChatMessageInformation,\n  CometChatMessageInformationInterface,\n} from \"./CometChatMessageInformation\";\n\nexport {\n  CometChatMessageInformation,\n};\n\nexport type {\n  CometChatMessageInformationInterface,  \n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageInformation/resources/index.ts",
    "content": "import LoadingIcon from \"./Spineer.png\";\n\nexport { LoadingIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageInformation/styles.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../theme/type\";\n\nexport const Style = StyleSheet.create({\n  container: {\n    // paddingStart: 8,\n    // paddingEnd: 8,\n  },\n  divider: {\n    height: 1,\n    marginVertical: 10,\n  },\n  viewContainer: {\n    // flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  imageStyle: {\n    height: 24,\n    width: 24,\n    alignSelf: \"center\",\n  }\n});\n\nexport const getMessageInormationListStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"messageInformationStyles\"] => {\n  return {\n    containerStyle: {\n       height: '100%',\n       width: '100%'\n    },\n    titleContainerStyle: {\n      flexDirection: \"row\",\n      height: 64,\n      justifyContent: 'center'\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      paddingHorizontal: spacing.padding.p4,\n      paddingVertical: spacing.padding.p2,\n      ...typography.heading2.bold,\n      alignSelf: 'flex-start'\n    },\n    messageBubbleContainerStyle: {\n      paddingHorizontal: spacing.padding.p4,\n      paddingVertical: spacing.padding.p5,\n      borderWidth: 1,\n      borderColor: color.borderLight,\n      backgroundColor: color.background2,\n      maxHeight: '40%',\n      width: '100%'\n      //marginHorizontal: -10\n    },\n    receiptItemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        gap: spacing.padding.p3,\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p4,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.caption1.regular,\n      },\n      avatarStyle: {\n        containerStyle: {\n          height: 40,\n          width: 40,\n        },\n        imageStyle: {\n          borderRadius: spacing.radius.max,\n        },\n        textStyle: {},\n      },\n      emojiStyle: {\n        height: 24,\n        width: 24,\n        ...typography.heading2.regular,\n      },\n    },\n    skeletonStyle: {\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 0.1,\n    },\n    errorStateStyle: {\n      containerStyle: {\n        marginTop: 0,\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n        marginBottom: 50\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    },\n  };\n};\n\nexport const getMessageInormationListStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"messageInformationStyles\"] => {\n  return deepMerge(getMessageInormationListStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 0.1,\n    },\n    errorStateStyle: {\n      containerStyle: {\n        marginTop: spacing.margin.m0,\n        height: \"95%\",\n        width: \"100%\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"-10%\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    }\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/CometChatMessageList.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport Clipboard from \"@react-native-clipboard/clipboard\";\nimport React, {\n  forwardRef,\n  JSX,\n  memo,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  ActivityIndicator,\n  Animated,\n  Dimensions,\n  FlatList,\n  Keyboard,\n  NativeModules,\n  Platform,\n  Pressable,\n  ScrollView,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n  StyleSheet,\n  StyleProp,\n} from \"react-native\";\nimport {\n  PanGestureHandler,\n  State,\n  PanGestureHandlerGestureEvent,\n  PanGestureHandlerStateChangeEvent,\n} from \"react-native-gesture-handler\";\nimport { CometChatMessageEvents } from \"../shared/events/CometChatMessageEvents\";\nimport { CometChatConversationEvents } from \"../shared/events/conversations\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatSoundManager,\n  CometChatTextFormatter,\n  CometChatUiKitConstants,\n  CometChatUrlsFormatter,\n  SuggestionItem,\n} from \"../shared\";\nimport {\n  MessageBubbleAlignmentType,\n  MessageListAlignmentType,\n} from \"../shared/base/Types\";\nimport {\n  CallTypeConstants,\n  CometChatCustomMessageTypes,\n  GroupsConstants,\n  MessageCategoryConstants,\n  MessageOptionConstants,\n  MessageReceipt,\n  MessageStatusConstants,\n  MessageTypeConstants,\n  ReceiverTypeConstants,\n  ViewAlignment,\n} from \"../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { MessageEvents } from \"../shared/events/messages\";\nimport { ChatConfigurator } from \"../shared/framework/ChatConfigurator\";\nimport { deepClone, deepMerge } from \"../shared/helper/helperFunctions\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { CometChatMessageTemplate } from \"../shared/modals/CometChatMessageTemplate\";\nimport { getUnixTimestamp, messageStatus } from \"../shared/utils/CometChatMessageHelper\";\nimport { CommonUtils } from \"../shared/utils/CommonUtils\";\nimport { CometChatNewMessageIndicator, CometChatReceipt, NewMessageIndicatorStyle } from \"../shared/views\";\nimport { CometChatAvatar } from \"../shared/views/CometChatAvatar\";\nimport { CometChatBadge } from \"../shared/views/CometChatBadge\";\nimport { CometChatDate } from \"../shared/views/CometChatDate\";\nimport { CometChatMessageBubble } from \"../shared/views/CometChatMessageBubble\";\nimport { getModerationStatus, ModerationBottomView, MimeErrorBottomView } from \"../shared/utils/MessageUtils\";\nimport { stripMarkdown } from \"../shared/utils/MarkdownUtils\";\nimport { CometChatReactions } from \"../shared/views/CometChatReactions\";\nimport { useTheme, useThemeInternal } from \"../theme\";\nimport { MessageSkeleton } from \"./Skeleton\";\nimport { ErrorEmptyView } from \"../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { BubbleStyles, CometChatTheme } from \"../theme/type\";\nimport { ExtensionConstants } from \"../extensions\";\n//@ts-ignore\nimport { getExtensionData } from \"../shared/helper/functions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport {\n  handleWebsocketMessage,\n  setAIAssistantTools,\n  setStreamSpeed,\n  setQueueCompletionCallback,\n  onConnected as streamOnConnected,\n  onDisconnected as streamOnDisconnected,\n  checkAndTriggerQueueCompletion,\n  storeAIAssistantMessage,\n} from \"../shared/services/stream-message.service\";\nimport { CometChatAIAssistantTools } from \"../shared/modals/CometChatAIAssistantTools\";\nimport { StreamMessage } from \"../shared/modals\";\nimport { internalMessageDataSource } from \"../shared/framework/MessageDataSource\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\nimport { MessageModals } from \"./components/MessageModals\";\nimport { ReactionModals } from \"./components/ReactionModals\";\nimport { MessageOptionsSheet } from \"./components/MessageOptionsSheet\";\nimport { MessageListItem } from \"./components/MessageListItem\";\n\nlet _defaultRequestBuilder: CometChat.MessagesRequestBuilder;\nconst SEPARATOR_HEIGHT = 40;\nconst AVERAGE_ITEM_LENGTH = 120;\nconst SMALL_LIST_THRESHOLD = 30;\n/**\n * Helper function to safely get message ID\n * Prevents \"undefined is not a function\" errors in release builds\n */\nconst safeGetId = (item: any): string | number | undefined => {\n  return typeof item?.getId === 'function' ? item.getId() : item?.id;\n};\n\n/**\n * Helper function to safely get message MUID\n * Prevents \"undefined is not a function\" errors in release builds\n */\nconst safeGetMuid = (item: any): string | undefined => {\n  return typeof item?.getMuid === 'function' ? item.getMuid() : item?.muid;\n};\n\n\n\n/**\n * Fetches a conversation if it exists, returning null for new users\n * who haven't exchanged any messages yet (ERR_CONVERSATION_NOT_ACCESSIBLE).\n */\nasync function getConversationIfExists(\n  convId: string,\n  conversationType: string\n): Promise<CometChat.Conversation | null> {\n  try {\n    return await CometChat.getConversation(convId, conversationType);\n  } catch (e: any) {\n    if (e?.code === \"ERR_CONVERSATION_NOT_ACCESSIBLE\") {\n      return null;\n    }\n    throw e;\n  }\n}\n\n/**\n * Batch state updates using requestAnimationFrame\n * It delays the callback until the next animation frame, \n * so all state updates inside the callback happen together, \n * just before the UI paints.\n */\n\nfunction batchStateUpdates(callback: () => void): void {\n  requestAnimationFrame(() => {\n    callback();\n  });\n}\n\n\n/**\n * Uses Inverted Flat List\n * Array needs to be reversed meaning the latest message will be at array[0]\n * New message will be appended to the beginning of the array\n * ScrollToBottom -> scrollToOffset({offset: 0}) (top of the list is visual bottom, remember that latest message is at array[0])\n * layoutHeight is the viewport or visible portion of the list\n * Intially when the list is loaded, offset 0 is the bottom most item of the visible area\n */\n\n/**\n * Properties for rendering the CometChat message list component.\n */\nexport interface CometChatMessageListProps {\n  /**\n   * ID of the parent message when rendering threaded messages.\n   */\n  parentMessageId?: string;\n  /**\n   * The user object associated with the message list.\n   */\n  user?: CometChat.User;\n  /**\n   * The group object associated with the message list.\n   */\n  group?: CometChat.Group;\n  /**\n   * A component to display when there are no messages.\n   */\n  EmptyView?: () => JSX.Element;\n  /**\n   * A component to display when an error occurs.\n   */\n  ErrorView?: () => JSX.Element;\n  /**\n   * Flag to hide the error view.\n   */\n  hideError?: boolean;\n  /**\n   * A component to display while messages are loading.\n   */\n  LoadingView?: () => JSX.Element;\n  /**\n   * Flag to hide read receipts.\n   */\n  receiptsVisibility?: boolean;\n  /**\n   * Flag to disable sound for incoming messages.\n   */\n  disableSoundForMessages?: boolean;\n  /**\n   * Custom sound URL for messages.\n   */\n  customSoundForMessages?: string;\n  /**\n   * Hides moderation status UI (bottom view) and suppresses moderation toasts.\n   */\n  hideModerationStatus?: boolean;\n  /**\n   * Alignment type for the message list.\n   */\n  alignment?: MessageListAlignmentType;\n  /**\n   * Flag to show or hide the user's avatar.\n   */\n  avatarVisibility?: boolean;\n  /**\n   * Function that returns a custom string representation for a message's date.\n   *\n   * @param message - The base message object.\n   * @returns A string representing the custom date.\n   */\n  datePattern?: (message: CometChat.BaseMessage) => string;\n  /**\n   * Function that returns a custom date separator string based on a timestamp.\n   *\n   * @param date - The timestamp (in milliseconds).\n   * @returns A string representing the date separator.\n   */\n  dateSeparatorPattern?: (date: number) => string;\n  /**\n   * An array of message templates for rendering custom message types.\n   */\n  templates?: Array<CometChatMessageTemplate>;\n  /**\n   * An array of message templates for rendering custom message types.\n   */\n  addTemplates?: Array<CometChatMessageTemplate>;\n  /**\n   * Builder for constructing a request to fetch messages.\n   */\n  messageRequestBuilder?: CometChat.MessagesRequestBuilder;\n  /**\n   * If true, the message list will scroll to the bottom when new messages arrive.\n   */\n  scrollToBottomOnNewMessages?: boolean;\n  /**\n   * If true, the \"Mark as Unread\" option will be shown in the message options.\n   * @defaultValue false\n   */\n  showMarkAsUnreadOption?: boolean;\n  /**\n   * If true, the message list will start from the first unread message.\n   * @defaultValue false\n   */\n  startFromUnreadMessages?: boolean;\n  /**\n   * Custom component to render the \"New Messages\" indicator.\n   */\n  NewMessageIndicatorView?: React.ComponentType<any>;\n  /**\n   * Custom styles for the \"New Messages\" indicator.\n   */\n  newMessageIndicatorStyle?: NewMessageIndicatorStyle;\n  /**\n   * Text to display in the \"New Messages\" indicator.\n   */\n  newMessageIndicatorText?: string;\n  /**\n   * Callback invoked when the thread replies view is pressed.\n   *\n   * @param messageObject - The message object triggering the event.\n   * @param messageBubbleView - A function that returns the message bubble view component.\n   */\n  onThreadRepliesPress?: (\n    messageObject: CometChat.BaseMessage,\n    messageBubbleView: () => JSX.Element | null\n  ) => void;\n  /**\n   * Custom header view component.\n   *\n   * @param props - The props object containing user, group, and identifiers.\n   * @returns A JSX element representing the header.\n   */\n  HeaderView?: ({\n    user,\n    group,\n    id,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    id?: { uid?: string; guid?: string; parentMessageId?: string };\n  }) => JSX.Element;\n  /**\n   * Custom footer view component.\n   *\n   * @param props - The props object containing user, group, and identifiers.\n   * @returns A JSX element representing the footer.\n   */\n  FooterView?: ({\n    user,\n    group,\n    id,\n  }: {\n    user?: CometChat.User;\n    group?: CometChat.Group;\n    id?: { uid?: string; guid?: string; parentMessageId?: string };\n  }) => JSX.Element;\n  /**\n   * Callback to handle errors.\n   *\n   * @param e - The error object from CometChat.\n   */\n  onError?: (e: CometChat.CometChatException) => void;\n  /**\n   * Callback invoked on back navigation.\n   */\n  onBack?: () => void;\n  /**\n   * Collection of text formatter classes to apply custom formatting.\n   *\n   * @type {Array<CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter>}\n   */\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n  /**\n   * Callback invoked when a reaction is pressed.\n   *\n   * @param reaction - The reaction count object.\n   * @param messageObject - The message object.\n   */\n  onReactionPress?: (\n    reaction: CometChat.ReactionCount,\n    messageObject: CometChat.BaseMessage\n  ) => void;\n  /**\n   * Callback invoked when a reaction is long pressed.\n   *\n   * @param reaction - The reaction count object.\n   * @param messageObject - The message object.\n   */\n  onReactionLongPress?: (\n    reaction: CometChat.ReactionCount,\n    messageObject: CometChat.BaseMessage\n  ) => void;\n  /**\n   * Callback invoked when an item in the reaction list is pressed.\n   *\n   * @param messageReaction - The message reaction object.\n   * @param messageObject - The message object.\n   */\n  onReactionListItemPress?: (\n    messageReaction: CometChat.Reaction,\n    messageObject: CometChat.BaseMessage\n  ) => void;\n  /**\n   * Custom styles for the message list component.\n   */\n  style?: DeepPartial<CometChatTheme[\"messageListStyles\"]>;\n  /**\n   * Builder for constructing a request to fetch reactions.\n   */\n  reactionsRequestBuilder?: CometChat.ReactionsRequestBuilder;\n  /**\n   * Callback invoked when the add reaction button is pressed.\n   */\n  onAddReactionPress?: () => void;\n  /**\n   * List of quick reactions.\n   *\n   * @type {[string, string?, string?, string?, string?]}\n   */\n  quickReactionList?: [string, string?, string?, string?, string?];\n  /**\n   * Callback to handle errors.\n   *\n   * @param messageList - Array of CometChat.BaseMessage\n   */\n  onLoad?: (messageList: CometChat.BaseMessage[]) => void;\n  /**\n   * Callback to handle errors.\n   *\n   * @param messageList - Array of CometChat.BaseMessage\n   */\n  onEmpty?: () => void;\n  /**\n   * Flag to hide the reply option\n   */\n  hideReplyOption?: boolean;\n  /**\n   * Flag to hide the reply in thread option\n   */\n  hideReplyInThreadOption?: boolean;\n  /**\n   * Flag to hide the share message option\n   */\n  hideShareMessageOption?: boolean;\n  /**\n   * Flag to hide the edit message option\n   */\n  hideEditMessageOption?: boolean;\n  /**\n   * Flag to hide the translate message option\n   */\n  hideTranslateMessageOption?: boolean;\n  /**\n   * Flag to hide the delete message option\n   */\n  hideDeleteMessageOption?: boolean;\n  /**\n   * Flag to hide the react to message option\n   */\n  hideReactionOption?: boolean;\n  /**\n   * Flag to hide the message privately option\n   */\n  hideMessagePrivatelyOption?: boolean;\n  /**\n   * Flag to hide the copy message option\n   */\n  hideCopyMessageOption?: boolean;\n  /**\n   * Flag to hide the message info option\n   */\n  hideMessageInfoOption?: boolean;\n  /**\n   * Flag to hide the \"Flag Message\" option from the actions menu\n   */\n  hideFlagMessageOption?: boolean;\n  /**\n   * Flag to hide the remark text area in the flag dialog\n   */\n  hideFlagRemarkField?: boolean;\n  /**\n   * Flag to hide group action messages\n   */\n  hideGroupActionMessages?: boolean;\n  /**\n   * Flag to hide the timestamp on message bubbles\n   */\n  hideTimestamp?: boolean;\n  /**\n   * Array of suggested messages for AI agent empty view (only applies to @agentic users)\n   */\n  suggestedMessages?: Array<string>;\n  /**\n   * Flag to hide suggested messages in AI agent empty view (only applies to @agentic users)\n   */\n  hideSuggestedMessages?: boolean;\n  /**\n   * Custom AI agent greeting view (only applies to @agentic users)\n   */\n  emptyChatGreetingView?: React.ReactNode;\n  /**\n   * Custom AI agent intro message view (only applies to @agentic users)\n   */\n  emptyChatIntroMessageView?: React.ReactNode;\n  /**\n   * Custom AI agent image/avatar view (only applies to @agentic users)\n   */\n  emptyChatImageView?: React.ReactNode;\n  /**\n   * Callback when suggested message is clicked (only applies to @agentic users)\n   */\n  onSuggestedMessageClick?: (suggestion: string) => void;\n  /**\n   * Custom AI assistant tools with action functions (only applies to @agentic users)\n   */\n  aiAssistantTools?: CometChatAIAssistantTools;\n  /**\n   * Controls the speed of AI message streaming (only applies to @agentic users)\n   */\n  streamingSpeed?: number;\n  /**\n   * Message ID to navigate to and highlight in the message list.\n   * When provided, the message list will fetch messages around this ID and scroll to it.\n   */\n  goToMessageId?: string;\n  /**\n   * Search keyword to highlight in message text.\n   * When provided, matching text in messages will be highlighted.\n   */\n  searchKeyword?: string;\n  /**\n   * @internal\n   * Flag to indicate if the user navigated from search results.\n   * When true, this modifies the message list behavior for better search navigation UX:\n   * - The target message from search is highlighted with a temporary animation\n   * - The \"New Messages\" indicator is suppressed to avoid visual clutter\n   * - When startFromUnreadMessages is enabled, prevents automatic marking of messages as read\n   *   to preserve unread status for user reference\n   * This ensures users can easily find their searched message without UI distractions\n   * and maintain their unread message state for later review.\n   */\n  navigatedFromSearch?: boolean;\n}\n\n/**\n * Interface defining the actions for managing a CometChat message list.\n */\nexport interface CometChatMessageListActionsInterface {\n  /**\n   * Adds a new message to the message list.\n   *\n   * @param messageObject - The message object to be added.\n   */\n  addMessage: (messageObject: object) => void;\n\n  /**\n   * Updates an existing message in the message list.\n   *\n   * @param messageObject - The base message object containing updated data.\n   * @param withMuid - A flag indicating whether to update the message with a MUID.\n   */\n  updateMessage: (messageObject: CometChat.BaseMessage, withMuid: boolean) => void;\n\n  /**\n   * Removes a message from the message list.\n   *\n   * @param messageObject - The base message object to be removed.\n   */\n  removeMessage: (messageObject: CometChat.BaseMessage) => void;\n\n  /**\n   * Permanently deletes a message from the message list.\n   *\n   * @param messageObject - The base message object to be deleted.\n   */\n  deleteMessage: (messageObject: CometChat.BaseMessage) => void;\n\n  /**\n   * Scrolls the message list to the bottom.\n   */\n  scrollToBottom: () => void;\n\n  /**\n   * Creates an action message.\n   *\n   * @remarks\n   * TODO: Clarify the purpose and the appropriate usage of this method.\n   */\n  createActionMessage: () => void;\n\n  /**\n   * Updates the receipt information of a message.\n   *\n   * @param message - The base message object whose receipt is to be updated.\n   */\n  updateMessageReceipt: (message: CometChat.BaseMessage) => void;\n}\n\nexport const CometChatMessageList = memo(\n  forwardRef<CometChatMessageListActionsInterface, CometChatMessageListProps>(\n    (props: CometChatMessageListProps, ref) => {\n      const {\n        parentMessageId,\n        user,\n        group,\n        EmptyView,\n        ErrorView,\n        hideError,\n        LoadingView,\n        receiptsVisibility = true,\n        disableSoundForMessages,\n        customSoundForMessages,\n        hideModerationStatus = false,\n        alignment = \"standard\",\n        avatarVisibility = true,\n        datePattern,\n        dateSeparatorPattern,\n        templates = [],\n        messageRequestBuilder,\n        scrollToBottomOnNewMessages = false,\n        onThreadRepliesPress,\n        HeaderView,\n        FooterView,\n        onError,\n        onBack,\n        reactionsRequestBuilder,\n        textFormatters,\n        onReactionPress: onReactionPressFromProp,\n        onReactionLongPress: onReactionLongPressFromProp,\n        onReactionListItemPress: onReactionListItemPressProp,\n        style,\n        onAddReactionPress,\n        quickReactionList,\n        addTemplates = [],\n        onLoad,\n        onEmpty,\n        hideReplyOption: propHideReplyOption = false,\n        hideReplyInThreadOption: propHideReplyInThreadOption = false,\n        hideShareMessageOption: propHideShareMessageOption = false,\n        hideEditMessageOption: propHideEditMessageOption = false,\n        hideTranslateMessageOption: propHideTranslateMessageOption = false,\n        hideDeleteMessageOption: propHideDeleteMessageOption = false,\n        hideReactionOption: propHideReactionOption = false,\n        hideMessagePrivatelyOption: propHideMessagePrivatelyOption = false,\n        hideCopyMessageOption: propHideCopyMessageOption = false,\n        hideMessageInfoOption: propHideMessageInfoOption = false,\n        hideGroupActionMessages: propHideGroupActionMessages = false,\n        hideTimestamp = false,\n        suggestedMessages = [],\n        hideSuggestedMessages = false,\n        emptyChatGreetingView,\n        emptyChatIntroMessageView,\n        emptyChatImageView,\n        onSuggestedMessageClick,\n        aiAssistantTools,\n        streamingSpeed = 30,\n        goToMessageId,\n        searchKeyword,\n        navigatedFromSearch = false,\n        hideFlagRemarkField,\n        hideFlagMessageOption: propHideFlagMessageOption = false,\n        showMarkAsUnreadOption = false,//change it to false default is false\n        startFromUnreadMessages = false,//change it to false default is false\n        NewMessageIndicatorView,\n        newMessageIndicatorStyle,\n        newMessageIndicatorText,\n      } = props;\n\n      // Helper to check if user is agentic - memoized as boolean for performance\n      const isAgenticUser = useMemo(() => {\n        return user?.getRole?.() === \"@agentic\";\n      }, [user]);\n\n      // hiding props for @agentic users\n      const hideReplyOption = isAgenticUser ? true : propHideReplyOption;\n      const hideReplyInThreadOption = isAgenticUser ? true : propHideReplyInThreadOption;\n      const hideShareMessageOption = isAgenticUser ? true : propHideShareMessageOption;\n      const hideEditMessageOption = isAgenticUser ? true : propHideEditMessageOption;\n      const hideTranslateMessageOption = isAgenticUser ? true : propHideTranslateMessageOption;\n      const hideDeleteMessageOption = isAgenticUser ? true : propHideDeleteMessageOption;\n      const hideReactionOption = isAgenticUser ? true : propHideReactionOption;\n      const hideMessagePrivatelyOption = isAgenticUser ? true : propHideMessagePrivatelyOption;\n      const hideCopyMessageOption = isAgenticUser ? true : propHideCopyMessageOption;\n      const hideMessageInfoOption = isAgenticUser ? true : (propHideMessageInfoOption || !receiptsVisibility);\n      const hideGroupActionMessages = isAgenticUser ? true : propHideGroupActionMessages;\n      const hideFlagMessageOption = isAgenticUser ? true : propHideFlagMessageOption;\n      const hideMarkAsUnreadOption = (isAgenticUser || !!parentMessageId) ? true : !showMarkAsUnreadOption;//hide for threads and agentic users\n\n      const callListenerId = \"call_\" + new Date().getTime();\n      const effectiveHideModeration = hideModerationStatus;\n      const groupListenerId = \"group_\" + new Date().getTime();\n      const uiEventListener = \"uiEvent_\" + new Date().getTime();\n      const callEventListener = \"callEvent_\" + new Date().getTime();\n      const uiEventListenerShow = \"uiEvent_show_\" + new Date().getTime();\n      const uiEventListenerHide = \"uiEvent_hide_\" + new Date().getTime();\n      const connectionListenerId = \"connectionListener_\" + new Date().getTime();\n      const messageEventListener = \"messageEvent_\" + new Date().getTime();\n      const groupEventListener = \"groupEvent_\" + new Date().getTime();\n      const streamListenerId = \"agent_\" + new Date().getTime();\n      const deleteItem = useRef<CometChat.BaseMessage>(undefined);\n      const shouldSuppressHighlightRef = useRef(false);\n\n      useLayoutEffect(() => {\n        // For agent chats (non-thread), don't set up message request builder\n        if (isAgenticUser && !parentMessageId) {\n          msgRequestBuilder.current = null as any;\n          return;\n        }\n\n        if (user) {\n          _defaultRequestBuilder = new CometChat.MessagesRequestBuilder()\n            .setLimit(30)\n            .setTags([])\n            .setUID(user.getUid());\n        } else if (group) {\n          _defaultRequestBuilder = new CometChat.MessagesRequestBuilder()\n            .setLimit(30)\n            .setTags([])\n            .setGUID(group.getGuid());\n        }\n\n        _defaultRequestBuilder.setTypes(ChatConfigurator.dataSource.getAllMessageTypes());\n        _defaultRequestBuilder.setCategories(ChatConfigurator.dataSource.getAllMessageCategories());\n\n        //updating users request builder\n        let _updatedCustomRequestBuilder = _defaultRequestBuilder;\n        if (messageRequestBuilder) {\n          _updatedCustomRequestBuilder = messageRequestBuilder;\n          if (user)\n            _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setUID(user.getUid());\n          if (group)\n            _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setGUID(group.getGuid());\n        } else {\n          _updatedCustomRequestBuilder.hideReplies(true);\n          if (user)\n            _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setUID(user.getUid());\n          if (group)\n            _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setGUID(group.getGuid());\n          if (parentMessageId) {\n            _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setParentMessageId(\n              parseInt(parentMessageId)\n            );\n            if (isAgenticUser) {\n              _updatedCustomRequestBuilder.hideReplies(false);\n              _updatedCustomRequestBuilder.withParent(true);\n            }\n          }\n          let types: any[] = [],\n            categories: any[] = [];\n          if (templates.length) {\n            types = templates.map((template) => template.type);\n            categories = templates.map((template) => template.category);\n          } else {\n            types = ChatConfigurator.dataSource.getAllMessageTypes();\n            categories = ChatConfigurator.dataSource.getAllMessageCategories();\n            if (addTemplates.length) {\n              types.push(...addTemplates.map((template) => template.type));\n              categories.push(...addTemplates.map((template) => template.category));\n            }\n          }\n\n          if (hideGroupActionMessages) {\n            types = types.filter((type) => type !== MessageTypeConstants.groupMember);\n          }\n          _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setTypes(types);\n          _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setCategories(categories);\n        }\n\n        msgRequestBuilder.current = _updatedCustomRequestBuilder;\n      }, [hideGroupActionMessages, isAgenticUser, parentMessageId]);\n\n      const theme = useTheme();\n      const themeInternal = useThemeInternal();\n      const themeMode = themeInternal.mode; // 'light' or 'dark' - used for FlatList extraData\n      const { t } = useCometChatTranslation();\n\n      const mergedTheme: CometChatTheme = useMemo(() => {\n        const themeClone = deepClone(theme);\n        themeClone.messageListStyles = deepMerge(theme.messageListStyles, style ?? {});\n        return themeClone;\n      }, [theme, style]);\n\n      const overridenBubbleStyles = useMemo(() => {\n        const styleCache = new Map();\n        const outgoingBubbleStyles = mergedTheme.messageListStyles.outgoingMessageBubbleStyles;\n        const incomingBubbleStyles = mergedTheme.messageListStyles.incomingMessageBubbleStyles;\n\n        styleCache.set(MessageTypeConstants.text, {\n          incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.textBubbleStyles ?? {}),\n          outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.textBubbleStyles ?? {}),\n        });\n\n        styleCache.set(MessageTypeConstants.image, {\n          incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.imageBubbleStyles ?? {}),\n          outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.imageBubbleStyles ?? {}),\n        });\n\n        const fileIncoming = deepMerge(incomingBubbleStyles, incomingBubbleStyles.fileBubbleStyles ?? {});\n        if (fileIncoming.containerStyle) fileIncoming.containerStyle.height = undefined;\n        const fileOutgoing = deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.fileBubbleStyles ?? {});\n        if (fileOutgoing.containerStyle) fileOutgoing.containerStyle.height = undefined;\n\n        styleCache.set(MessageTypeConstants.file, {\n          incoming: fileIncoming,\n          outgoing: fileOutgoing,\n        });\n\n        const audioIncoming = deepMerge(incomingBubbleStyles, incomingBubbleStyles.audioBubbleStyles ?? {});\n        if (audioIncoming.containerStyle) audioIncoming.containerStyle.height = undefined;\n        const audioOutgoing = deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.audioBubbleStyles ?? {});\n        if (audioOutgoing.containerStyle) audioOutgoing.containerStyle.height = undefined;\n\n        styleCache.set(MessageTypeConstants.audio, {\n          incoming: audioIncoming,\n          outgoing: audioOutgoing,\n        });\n\n        styleCache.set(MessageTypeConstants.messageDeleted, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            mergedTheme.deletedBubbleStyles ?? {},\n            incomingBubbleStyles.deletedBubbleStyles ?? {}\n          ),\n          outgoing: deepMerge(\n            outgoingBubbleStyles,\n            mergedTheme.deletedBubbleStyles ?? {},\n            outgoingBubbleStyles.deletedBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(MessageTypeConstants.sticker, {\n          incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.stickerBubbleStyles ?? {}),\n          outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.stickerBubbleStyles ?? {}),\n        });\n\n        styleCache.set(MessageTypeConstants.document, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.collaborativeBubbleStyles ?? {}\n          ),\n          outgoing: deepMerge(\n            outgoingBubbleStyles,\n            outgoingBubbleStyles.collaborativeBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(CometChatCustomMessageTypes.meeting, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.meetCallBubbleStyles ?? {}\n          ),\n          outgoing: deepMerge(\n            outgoingBubbleStyles,\n            outgoingBubbleStyles.meetCallBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(MessageTypeConstants.whiteboard, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.collaborativeBubbleStyles ?? {}\n          ),\n          outgoing: deepMerge(\n            outgoingBubbleStyles,\n            outgoingBubbleStyles.collaborativeBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(MessageTypeConstants.video, {\n          incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.videoBubbleStyles ?? {}),\n          outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.videoBubbleStyles ?? {}),\n        });\n\n        styleCache.set(MessageTypeConstants.poll, {\n          incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.pollBubbleStyles ?? {}),\n          outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.pollBubbleStyles ?? {}),\n        });\n\n        styleCache.set(GroupsConstants.ACTION_TYPE_GROUPMEMBER, {\n          incoming: mergedTheme.messageListStyles.groupActionBubbleStyles,\n          outgoing: mergedTheme.messageListStyles.groupActionBubbleStyles,\n        });\n\n        styleCache.set(ExtensionConstants.linkPreview, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.linkPreviewBubbleStyles ?? {}\n          ),\n          outgoing: deepMerge(\n            outgoingBubbleStyles,\n            outgoingBubbleStyles.linkPreviewBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(MessageTypeConstants.assistant, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.assistantBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(CometChatUiKitConstants.streamMessageTypes.run_started, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.assistantBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(CometChatUiKitConstants.streamMessageTypes.run_finished, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.assistantBubbleStyles ?? {}\n          ),\n        });\n\n        styleCache.set(CometChatUiKitConstants.streamMessageTypes.text_message_content, {\n          incoming: deepMerge(\n            incomingBubbleStyles,\n            incomingBubbleStyles.assistantBubbleStyles ?? {}\n          ),\n        });\n\n        return styleCache;\n      }, [mergedTheme]);\n\n      // refs\n      const currentScrollPosition = useRef({\n        y: null,\n        scrollViewHeight: 0,\n        layoutHeight: 0,\n        contentHeight: 0,\n      });\n      const messagesLength = useRef(0); // Tracks total message count to detect additions/updates\n      const prevMessagesLength = useRef(0); // Tracks previous message count to handle scroll position adjustments when loading history\n      const messageListRef = useRef<FlatList | null>(null); // Reference to the FlatList for programmatic scrolling\n      const loggedInUser = useRef<CometChat.User | null | any>(null); // Stores current user to check sender identity (me vs others)\n      const messageRequest = useRef<CometChat.MessagesRequest | null>(null); // Manages pagination state for fetching messages\n      const messagesContentListRef = useRef<any[]>([]); // \"Main Stage\": Source of Truth for visible messages. Kept in sync with state to handle real-time updates without closure staleness.\n      const temporaryMessageListRef = useRef<any[]>([]); // \"Waiting Room\": Buffer for new messages arriving while scrolled up. Prevents UI jumps/insertions while reading history.\n\n      const msgRequestBuilder = useRef<CometChat.MessagesRequestBuilder>(undefined);\n      const lastMessageDate = useRef(new Date().getTime());\n\n      // states\n      const [messagesList, setMessagesList] = useState<any[]>([]);\n      const [listState, setListState] = useState(\n        isAgenticUser && !parentMessageId ? \"loaded\" : \"loading\"\n      );\n      const [loadingMessages, setLoadingMessages] = useState(false);\n      /** this is required to prevent duplicate api calls. Cannot use state for this since this is being used in scrollHandler  **/\n      const loadingMessagesRef = useRef(false);\n      const loadingPrevMessagesRef = useRef(false);\n      // State for showing loading when navigating to far messages\n      const [navigationLoading, setNavigationLoading] = useState(false);\n      // State for showing loading when scrolling to searched messages\n      const [scrollLoading, setScrollLoading] = useState(false);\n      // State for showing bottom loading indicator when fetching next messages\n      const [bottomLoading, setBottomLoading] = useState(false);\n      const [unreadCount, setUnreadCount] = useState<number>(0);\n      const [showMessageOptions, setShowMessageOptions] = useState<any[]>([]);\n      const [ExtensionsComponent, setExtensionsComponent] = useState<JSX.Element | null>(null);\n      const [CustomListHeader, setCustomListHeader] = useState<any>(null);\n      // State to track new message indicator visibility\n      const [newMessageIndicatorId, setNewMessageIndicatorId] = useState<string | null>(null);\n      // State to track if user has manually marked messages as unread\n      const [hasManuallyMarkedUnread, setHasManuallyMarkedUnread] = useState<boolean>(false);\n      const [messageInfo, setMessageInfo] = useState(false);\n      const [showReportDialog, setShowReportDialog] = useState(false);\n      const [ongoingCallView, setOngoingCallView] = useState(null);\n      const [selectedMessage, setSelectedMessage] = useState<any>(null);\n      const [showEmojiKeyboard, setShowEmojiKeyboard] = useState(false);\n      const [showReactionList, setShowReactionList] = useState(false);\n      const [selectedEmoji, setSelectedEmoji] = useState<string | undefined>(undefined);\n      const [hideScrollToBottomButton, setHideScrollToBottomButton] = useState<boolean>(true);\n      const [showDeleteModal, setShowDeleteModal] = useState(false);\n\n      const infoObject = useRef<CometChat.BaseMessage | null>(undefined);\n      const [highlightedMessageId, setHighlightedMessageId] = useState<string | null>(null);\n      const [scrollTargetId, setScrollTargetId] = useState<string | null>(null);\n      const highlightAnimatedValue = useRef(new Animated.Value(0)).current;\n      const reportedMessageRef = useRef<CometChat.BaseMessage | null>(null);\n      const pendingReportRef = useRef<boolean>(false);\n      const bottomSheetRef = useRef<any>(null);\n      const conversationId = useRef<string | null>(null);\n      const streamSubscriptionRef = useRef<any>(null);\n      const lastID = useRef(0);\n      const reachedFirstMessage = useRef<boolean>(false);\n      const agenticParentMessageIdRef = useRef<string | undefined>(undefined);\n      const [hasTargetMessageId, setHasTargetMessageId] = useState(false);\n      const fetchingAroundIdRef = useRef<string | null>(null); // Track which message ID we're currently fetching\n      const [itemPositions, setItemPositions] = useState<Map<string, { y: number; height: number }>>(new Map());\n      const markUnreadMessageAsRead = (message?: CometChat.BaseMessage) => {\n        const messageToMark = message || messagesContentListRef.current[0];\n        if (\n          messageToMark &&\n          messageToMark.getSender?.()?.getUid() !== loggedInUser.current?.getUid()\n        ) {\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, {\n            message: messageToMark,\n          });\n          CometChat.markAsRead(messageToMark).catch((error: any) => {\n            console.log(\"Error marking unread message as read\", error);\n            onError && onError(error);\n          });\n          setUnreadCount(0);\n        }\n      };\n      const prevMsgListLengthRef = useRef<number>(0);\n      const aMessageWasSentByMeRef = useRef<boolean>(false);\n      const latestMessageRef = useRef<any | null>(null);\n\n\n\n      useEffect(() => {\n        if (messagesList.length === prevMsgListLengthRef.current + 1 && aMessageWasSentByMeRef.current) {\n          aMessageWasSentByMeRef.current = false;\n          setTimeout(() => {\n            messageListRef.current?.scrollToOffset({ offset: 0, animated: true });\n          }, 100);\n        }\n        prevMsgListLengthRef.current = messagesList.length;\n      }, [messagesList]);\n\n\n      const newMsgIndicatorPressed = useCallback(async () => {\n        if (hasTargetMessageId) {\n          try {\n            messageRequest.current = msgRequestBuilder.current?.build() || null;\n            messagesContentListRef.current = [];\n            setMessagesList([]);\n            setListState(\"loading\");\n            reachedFirstMessage.current = false;\n            msgRequestBuilder.current?.setMessageId(0);\n            messageRequest.current = msgRequestBuilder.current?.build() || null;\n            await getPreviousMessages();\n            setTimeout(() => {\n              scrollToBottom();\n              setHideScrollToBottomButton(true);\n            }, 100);\n          } catch (error) {\n            setListState(\"\");\n          }\n        } else {\n          // Merge temporary messages when unread count button is pressed\n          messagesContentListRef.current = [\n            ...temporaryMessageListRef.current,\n            ...messagesContentListRef.current,\n          ];\n          temporaryMessageListRef.current = [];\n\n          // Update latestMessageRef after merge\n          if (messagesContentListRef.current.length > 0) {\n            latestMessageRef.current = messagesContentListRef.current[0];\n          }\n\n          onLoad && onLoad([...messagesContentListRef.current].reverse());\n          setMessagesList([...messagesContentListRef.current]);\n\n          setTimeout(() => {\n            scrollToBottom();\n            setHideScrollToBottomButton(true);\n            markUnreadMessageAsRead();\n          }, 100);\n        }\n      }, [onLoad, hasTargetMessageId]);\n\n      const getPreviousMessages = async () => {\n        if (fetchingAroundIdRef.current) return;\n\n        if (loadingPrevMessagesRef.current) {\n          return;\n        }\n        loadingPrevMessagesRef.current = true;\n        // For agent chats (non-thread), don't fetch previous messages\n        if (isAgenticUser && !parentMessageId) {\n          return;\n        }\n\n        // Prevent concurrent API calls\n        if (loadingMessagesRef.current) {\n          return;\n        }\n\n        if (reachedFirstMessage.current) {\n          return;\n        }\n        if (messagesList.length == 0) setListState(\"loading\");\n        else {\n          setLoadingMessages(true);\n          loadingMessagesRef.current = true;\n        }\n        // TODO: this condition is applied because somewhere from whiteboard extention group scope is set to undefined.\n        if (group != undefined && group.getGuid() == undefined) {\n          let fetchedGroup: any = await CometChat.getGroup(group.getGuid()).catch((e: any) => {\n            console.log(\"Error: fetching group\", e);\n            onError && onError(e);\n          });\n          group.setScope(fetchedGroup[\"scope\"]);\n        }\n        messageRequest.current\n          ?.fetchPrevious()\n          .then((msgs: any[]) => {\n            if (messageRequest.current!.getLimit() > msgs.length) {\n              reachedFirstMessage.current = true;\n            }\n            let previousMessagesFetched = [...msgs].reverse(); // Reverse for UI use\n\n            if (messagesList.length === 0 && msgs?.length > 0) {\n              CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccActiveChatChanged, {\n                message: previousMessagesFetched[0],\n                user: user,\n                group: group,\n                theme: mergedTheme,\n                parentMessageId: parentMessageId,\n              });\n              if (conversationId.current == null)\n                conversationId.current = previousMessagesFetched[0].getConversationId();\n            } else if (messagesList.length === 0 && !props?.parentMessageId) {\n              CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccActiveChatChanged, {\n                message: previousMessagesFetched[0],\n                user: user,\n                group: group,\n                theme: mergedTheme,\n                parentMessageId: parentMessageId,\n              });\n            }\n\n            // When startFromUnreadMessages is enabled, the chat starts from the first unread message\n            // and shows a \"New Messages\" indicator above the oldest unread message.\n            // This allows users to quickly see where unread messages begin.\n            if (startFromUnreadMessages && previousMessagesFetched.length > 0) {\n              const lastMessage = previousMessagesFetched[0];//latest message\n              if (!goToMessageId && messagesList.length === 0) {\n                // If we are not navigating to a specific message, we want to show the new message indicator\n                // above the oldest unread message (which is the last one in the fetched list since it's reversed).\n                const oldestMessage = previousMessagesFetched[previousMessagesFetched.length];\n\n                if (oldestMessage) {\n                  const msgId = safeGetId(oldestMessage);\n                  if (msgId !== undefined) {\n                    setNewMessageIndicatorId(String(msgId));\n                  }\n                }\n              }\n\n              // Only mark conversation as read on initial load.\n              // If we do this on every fetch (pagination), it would reset the unread count\n              // even if the user manually marked a message as unread.\n              // The !navigatedFromSearch condition ensures we don't mark as read when navigating from search results,\n              // as the user might want to keep messages unread for reference.\n              if (messagesList.length === 0 && !navigatedFromSearch) {\n                //marking most recent unread message as read\n                if (user) {\n                  CometChat.markConversationAsRead(user.getUid(), CometChat.RECEIVER_TYPE.USER).catch((e) => {\n                    console.log(\"Error marking user conversation as read\", e);\n                  });\n                } else if (group) {\n                  CometChat.markConversationAsRead(group.getGuid(), CometChat.RECEIVER_TYPE.GROUP).catch((e) => {\n                    console.log(\"Error marking group conversation as read\", e);\n                  });\n                }\n                setUnreadCount(0);\n                //emitting event to update read receipts in other components\n                CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, {\n                  message: lastMessage,\n                });\n              }\n\n            } else {\n              for (let index = 0; index < previousMessagesFetched.length; index++) {\n                 const message=previousMessagesFetched[index];\n                  CometChat.markAsRead(message).catch((error: any) => {\n                    console.log(\"Error marking message as read\", error);\n                  })  \n                if (\n                  message &&\n                  !message.hasOwnProperty(\"readAt\") &&\n                  loggedInUser.current?.getUid() != message?.getSender()?.getUid()\n                ) {\n                  CometChat.markAsRead(message);\n                  if (index == 0)\n                    CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, {\n                      message,\n                    });\n                } else break;\n              }\n            }\n            previousMessagesFetched = previousMessagesFetched.map(\n              (item: CometChat.BaseMessage, index: any) => {\n                if (item.getCategory() === MessageCategoryConstants.interactive) {\n                  return item;\n                } else {\n                  return item;\n                }\n              }\n            );\n            if (previousMessagesFetched.length > 0) {\n              messagesContentListRef.current = [\n                ...messagesContentListRef.current,\n                ...previousMessagesFetched,\n              ];\n              setMessagesList(messagesContentListRef.current);\n            }\n            if (messagesContentListRef.current.length == 0) {\n              onEmpty && onEmpty();\n            } else {\n              onLoad && onLoad([...messagesContentListRef.current].reverse());\n              setLoadingMessages(false);\n              loadingMessagesRef.current = false;\n            }\n            setListState(\"\");\n          })\n          .catch((e: any) => {\n            if (messagesContentListRef.current.length == 0) setListState(\"error\");\n            if (e?.code === \"REQUEST_IN_PROGRESS\") return;\n            else {\n              setLoadingMessages(false);\n              loadingMessagesRef.current = false;\n            }\n            onError && onError(e);\n          }).finally(() => {\n            loadingPrevMessagesRef.current = false;\n          });\n      };\n\n      const highlightMessage = (messageId: string) => {\n        setHighlightedMessageId(messageId);\n        highlightAnimatedValue.setValue(0);\n        Animated.sequence([\n          Animated.timing(highlightAnimatedValue, {\n            toValue: 1,\n            duration: 300,\n            useNativeDriver: false,\n          }),\n          Animated.delay(2000), // Hold highlighted state for 2 seconds\n          Animated.timing(highlightAnimatedValue, {\n            toValue: 0,\n            duration: 500,\n            useNativeDriver: false,\n          }),\n        ]).start(() => {\n          setHighlightedMessageId(null);\n        });\n      };\n\n      const getMessagesAroundId = async (messageId: string) => {\n        if (fetchingAroundIdRef.current === messageId) {\n          return;\n        }\n\n        if (isAgenticUser && !parentMessageId) {\n          return;\n        }\n\n        fetchingAroundIdRef.current = messageId; // Mark this message as being fetched\n\n        // Check if message exists in current list\n        const messageExists = messagesContentListRef.current.find(msg =>\n          String(msg.getId()) === String(messageId)\n        );\n\n        if (messageExists) {\n          setHasTargetMessageId(true);\n        } else {\n          // Message is far, show navigation loading\n          setNavigationLoading(true);\n          setHasTargetMessageId(true);\n        }\n\n        try {\n          const targetMessageId = Number(messageId);\n          msgRequestBuilder.current?.setMessageId(targetMessageId);\n          messageRequest.current = msgRequestBuilder.current?.build() || null;\n\n          // Get the target message first to ensure it exists\n          const targetMessage = await CometChat.getMessageDetails(targetMessageId);\n          if (!targetMessage) {\n            setListState(\"\");\n            return;\n          }\n\n          // Create request builder for next messages (messages after the target)  \n          let nextMessagesRequest = new CometChat.MessagesRequestBuilder()\n            .setLimit(15)\n            .setMessageId(targetMessageId);\n\n          if (user) {\n            nextMessagesRequest = nextMessagesRequest.setUID(user.getUid());\n          }\n\n          if (group) {\n            nextMessagesRequest = nextMessagesRequest.setGUID(group.getGuid());\n          }\n\n          // Main chat: Server excludes thread replies\n          if (!parentMessageId) {\n            nextMessagesRequest = nextMessagesRequest.hideReplies(true);\n          }\n          // Thread: Server includes only relevant messages\n          else {\n            nextMessagesRequest = nextMessagesRequest\n              .setParentMessageId(Number(parentMessageId))\n              .hideReplies(false);\n            if (isAgenticUser) {\n              nextMessagesRequest = nextMessagesRequest.withParent(true);\n            }\n          }\n\n          // Fetch messages before and after the target message\n          const nextMessages = await nextMessagesRequest.build().fetchNext();\n\n          // Combine messages: previous (reversed) + target + next\n          const allMessages = [\n            // ...previousMessages,\n            targetMessage,\n            ...nextMessages\n          ].reverse();\n\n          // Remove duplicates and sort by timestamp (newest at top for FlatList)\n          const uniqueMessages = allMessages\n          // .filter((message, index, array) => \n          //   array.findIndex(m => m.getId() === message.getId()) === index\n          // )\n          // .sort((a, b) => b.getSentAt() - a.getSentAt());\n\n          if (uniqueMessages.length > 0) {\n            // Check if this is the initial load using ref to ensure fresh state\n            const isInitialLoad = messagesContentListRef.current.length === 0;\n            messagesContentListRef.current = uniqueMessages;\n            setMessagesList(uniqueMessages);\n            onLoad && onLoad([...uniqueMessages].reverse());\n\n            //This check is used to determine if this is the Initial Load of the chat screen.\n            // Check if this is the initial load (messagesList is empty).\n            // We check messagesList.length === 0 because setMessagesList above is async,\n            // so messagesList still reflects the state before this update.\n            // We only want to mark as read on the first load, not during subsequent updates.\n            // The !navigatedFromSearch condition ensures we don't mark as read when navigating from search results,\n            // as the user might want to keep messages unread for reference.\n            if (startFromUnreadMessages && isInitialLoad && !navigatedFromSearch && !goToMessageId) {\n              shouldSuppressHighlightRef.current = true;\n              //marking most recent unread message as read\n              if (user) {\n                CometChat.markConversationAsRead(user.getUid(), CometChat.RECEIVER_TYPE.USER).catch((e) => {\n                  console.log(\"Error marking user conversation as read\", e);\n                });\n              } else if (group) {\n                CometChat.markConversationAsRead(group.getGuid(), CometChat.RECEIVER_TYPE.GROUP).catch((e) => {\n                  console.log(\"Error marking group conversation as read\", e);\n                });\n              }\n              setUnreadCount(0);\n              // If we have next messages (unread messages), set the indicator on the first one.\n              // Otherwise, fallback to the target message (last read message).\n              if (nextMessages.length > 0) {\n                setNewMessageIndicatorId(String(nextMessages[0].getId()));\n              } else {\n                setNewMessageIndicatorId(messageId);\n              }\n              //emitting event to update read receipts in other components\n              CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, {\n                message: uniqueMessages[0],\n              });\n            } else if (navigatedFromSearch) {\n              // If we navigated from search, we want to show the \"New Messages\" indicator\n              // but NOT mark the conversation as read immediately.\n              // We fetch the first unread message to place the indicator correctly.\n              const fetchFirstUnread = async () => {\n                try {\n                  const convId = user?.getUid() ?? group?.getGuid();\n                  const conversationType = user\n                    ? CometChat.RECEIVER_TYPE.USER\n                    : CometChat.RECEIVER_TYPE.GROUP;\n\n                  if (!convId) return;\n\n                  const conversation = await getConversationIfExists(convId, conversationType);\n                  if (!conversation) return;\n                  const unreadCount = conversation.getUnreadMessageCount();\n                  setUnreadCount(unreadCount);\n\n                  if (unreadCount > 0) {\n                    const lastReadMsgId = conversation.getLastReadMessageId();\n\n                    if (lastReadMsgId) {\n                      let request = new CometChat.MessagesRequestBuilder()\n                        .setLimit(1)\n                        .setMessageId(Number(lastReadMsgId));\n\n                      if (user) request = request.setUID(user.getUid());\n                      if (group) request = request.setGUID(group.getGuid());\n\n                      if (!parentMessageId) {\n                        request = request.hideReplies(true);\n                      } else {\n                        request = request.setParentMessageId(Number(parentMessageId)).hideReplies(false);\n                        if (isAgenticUser) {\n                          request = request.withParent(true);\n                        }\n                      }\n\n                      const unreadMessages = await request.build().fetchNext();\n                      if (unreadMessages.length > 0) {\n                        setNewMessageIndicatorId(String(unreadMessages[0].getId()));\n                      }\n                    }\n                  }\n                } catch (e) {\n                  console.log(\"Error fetching first unread message\", e);\n                }\n              };\n              fetchFirstUnread();\n            }\n\n            // Reset layout state and set up for scrolling to target message\n            // console.log(`Setting scroll target to message: ${messageId}`);\n            setScrollTargetId(messageId);\n          }\n\n          setListState(\"\");\n        } catch (error) {\n          console.log(\"Error fetching messages around ID:\", error);\n          setListState(\"error\");\n          setNavigationLoading(false);\n          onError && onError(error as CometChat.CometChatException);\n        } finally {\n          // setNavigationLoading(false);\n          fetchingAroundIdRef.current = null;\n        }\n      };\n\n      // Scrolls to a specific message by ID, centering it in the view\n      // Uses position data from itemPositions map for precise scrolling\n      const scrollToMessage = (messageId: string, retryCount: number = 0) => {\n        if (messageListRef.current && messagesContentListRef.current.length > 0) {\n          // Show loading indicator during scroll operation\n          setNavigationLoading(true);\n\n          const targetIndex = messagesContentListRef.current.findIndex(\n            (msg) => String(msg.getId()) === messageId\n          );\n\n          if (targetIndex !== -1) {\n            // Get the actual position of the target message\n            const targetMessage = messagesContentListRef.current[targetIndex];\n            const position = itemPositions.get(String(targetMessage.getId()));\n\n            if (position) {\n              // Calculate centered offset for the message\n              // For inverted list, we need to position it from the top of the visible area\n              const screenHeight = currentScrollPosition.current.scrollViewHeight || Dimensions.get('window').height;\n\n              // Center the message: place it at middle of the screen\n              // In inverted list: offset = message.y - (screenHeight/2 - messageHeight/2)\n              const centeredOffset = Math.max(0, position.y - (screenHeight / 2) + (position.height / 2));\n\n              // console.log(`📍 Scrolling to position-based offset ${centeredOffset.toFixed(1)}px for message ${messageId}`);\n              messageListRef.current?.scrollToOffset({\n                offset: centeredOffset,\n                animated: true,\n              });\n\n              // Highlight after scroll animation starts\n              setTimeout(() => {\n                // console.log(`Highlighting message ${messageId} after position-based scroll`);\n                if (!shouldSuppressHighlightRef.current) {\n                  highlightMessage(messageId);\n                }\n                shouldSuppressHighlightRef.current = false;\n                setNavigationLoading(false); // Clear loading when scroll completes\n              }, 150);\n            } else {\n              // Position not measured yet - retry with faster progressive delays (up to 5 times)\n              if (retryCount < 5) {\n                // Faster progressive delays: 100ms, 200ms, 300ms, 400ms, 500ms\n                const delay = (retryCount + 1) * 100;\n                // console.log(`Retrying scroll for message ${messageId} (attempt ${retryCount + 1}/5) after ${delay}ms - waiting for layout`);\n                setTimeout(() => {\n                  scrollToMessage(messageId, retryCount + 1);\n                }, delay);\n              } else {\n                // Final fallback: use scrollToIndex with onScrollToIndexFailed handling for dynamic heights\n                try {\n                  messageListRef.current?.scrollToIndex({\n                    index: targetIndex,\n                    animated: true,\n                    viewPosition: 0.5, // For inverted FlatList\n                  });\n                  // onScrollToIndexFailed will handle retry logic if this fails\n                  setTimeout(() => {\n                    if (!shouldSuppressHighlightRef.current) {\n                      highlightMessage(messageId);\n                    }\n                    shouldSuppressHighlightRef.current = false;\n                    setNavigationLoading(false); // Clear loading when scroll completes\n                  }, 250);\n                } catch (scrollError) {\n                  console.error('scrollToIndex failed immediately:', scrollError);\n                  if (!shouldSuppressHighlightRef.current) {\n                    highlightMessage(messageId);\n                  }\n                  shouldSuppressHighlightRef.current = false;\n                  setNavigationLoading(false); // Clear loading on final failure\n                }\n              }\n            }\n          } else {\n            setNavigationLoading(false); // Clear loading since we're fetching\n            getMessagesAroundId(messageId);\n          }\n        }\n      };\n\n      const getUpdatedPreviousMessages = () => {\n        // For agent chats (non-thread), don't fetch updated previous messages\n        if (isAgenticUser && !parentMessageId) {\n          return;\n        }\n\n        let messagesRequest = new CometChat.MessagesRequestBuilder().setLimit(50);\n        if (user) messagesRequest = messagesRequest.setUID(user.getUid());\n        if (group) messagesRequest = messagesRequest.setGUID(group.getGuid());\n\n        messagesRequest.setTypes([MessageCategoryConstants.message]);\n        messagesRequest.setCategories([MessageCategoryConstants.action]);\n        messagesRequest.setMessageId(lastID.current);\n\n        messagesRequest\n          .build()\n          .fetchNext()\n          .then((updatedMessages: string | any[]) => {\n            let tmpList = [...messagesContentListRef.current];\n            for (let i = 0; i < updatedMessages.length; i++) {\n              let condition = (msg: any) => msg.getId() == updatedMessages[i]?.actionOn.getId();\n              let msgIndex = messagesContentListRef.current.findIndex(condition);\n              if (msgIndex > -1) {\n                tmpList[msgIndex] = updatedMessages[i]?.actionOn;\n              }\n            }\n            // console.log(\"UPDATES LIST LENGTH\", tmpList.length)\n            // setMessagesList(tmpList);\n            getNewMessages(tmpList);\n          })\n          .catch((e: any) => console.log(\"error while fetching updated msgs\", e));\n      };\n\n      const getNewMessages = (updatedList: any[]) => {\n        let newRequestBuilder = msgRequestBuilder.current;\n        newRequestBuilder?.setMessageId(lastID.current);\n\n        newRequestBuilder\n          ?.build()\n          .fetchNext()\n          .then((newMessages: any[]) => {\n            let cleanUpdatedList = [...updatedList];\n            let finalOutput = CommonUtils.mergeArrays(newMessages.reverse(), cleanUpdatedList, [\n              \"id\",\n              \"muid\",\n            ]);\n            let tmpList = [...finalOutput];\n            tmpList = tmpList.map((item: CometChat.BaseMessage, index) => {\n              if (item.getCategory() === MessageCategoryConstants.interactive) {\n                return item;\n              } else {\n                return item;\n              }\n            });\n            if (isAtBottom() || isNearBottom()) {\n              messagesContentListRef.current = tmpList;\n              setMessagesList(tmpList);\n              onLoad && onLoad([...messagesContentListRef.current].reverse());\n              for (let i = 0; i < newMessages.length; i++) {\n                if (newMessages[i].getSender().getUid() !== loggedInUser.current.getUid()) {\n                  bottomHandler(newMessages[i], true, true);\n                  break;\n                }\n              }\n            } else {\n              /*****\n               * If scroll is not at bottom or near bottom then add the messages to temporary list\n               * lastID in onDisconnected will always be messagesList[0].getId() (state)\n               * Example:\n               * - unreadCount is already 2 (meaning not near bottom or at bottom)\n               * - App is now in the background\n               * - Someone sends 2 messages to the user\n               * - User now puts the app in foreground and onConnected handler fetches the messages from lastID onwards\n               * - unreadCount will now be 4\n               * ****/\n              const currentUnreadCount = tmpList.length - messagesContentListRef.current.length;\n              temporaryMessageListRef.current = [];\n              const currentUnreadMessages = tmpList.slice(0, currentUnreadCount);\n              temporaryMessageListRef.current = [...currentUnreadMessages];\n              setUnreadCount(temporaryMessageListRef.current.length);\n            }\n            if (newMessages.length === 30) {\n              getNewMessages(tmpList);\n            }\n            newRequestBuilder?.setMessageId(undefined);\n          })\n          .catch((e: any) => console.log(\"error while fetching updated msgs\", e));\n      };\n\n      const templatesMap = useMemo(() => {\n        const isAgenticUserCheck = isAgenticUser;\n        const options = {\n          textFormatters: textFormatters || [],\n          hideReplyOption: isAgenticUserCheck || hideReplyOption,\n          hideReplyInThreadOption: isAgenticUserCheck || hideReplyInThreadOption,\n          hideShareMessageOption: isAgenticUserCheck || hideShareMessageOption,\n          hideEditMessageOption: isAgenticUserCheck || hideEditMessageOption,\n          hideTranslateMessageOption: isAgenticUserCheck || hideTranslateMessageOption,\n          hideDeleteMessageOption: isAgenticUserCheck || hideDeleteMessageOption,\n          hideReactionOption: isAgenticUserCheck || hideReactionOption,\n          hideMessagePrivatelyOption: isAgenticUserCheck || hideMessagePrivatelyOption,\n          hideCopyMessageOption: isAgenticUserCheck || hideCopyMessageOption,\n          hideMessageInfoOption: isAgenticUserCheck || hideMessageInfoOption || !receiptsVisibility,\n          hideFlagMessageOption: isAgenticUserCheck || hideFlagMessageOption,\n          hideMarkAsUnreadOption: isAgenticUserCheck || hideMarkAsUnreadOption,\n          hideGroupActionMessages,\n          onReplyClick: (messageId: string) => {\n            scrollToMessage(messageId);\n          },\n        };\n        let _formatters = textFormatters || [];\n        let tempTemplates: CometChatMessageTemplate[] =\n          templates && templates.length\n            ? templates\n            : [\n              ...ChatConfigurator.dataSource.getAllMessageTemplates(mergedTheme, options),\n              ...addTemplates,\n            ];\n        //for internal use only\n        const streamTemplate = internalMessageDataSource.getStreamMessageTemplate(\n          mergedTheme,\n          options\n        );\n\n        if (isAgenticUserCheck) {\n          tempTemplates.push(streamTemplate);\n        }\n\n        let templatesMapTemp = new Map<string, CometChatMessageTemplate>();\n\n        tempTemplates.forEach((template) => {\n          if (hideGroupActionMessages && template.type === MessageTypeConstants.groupMember) return;\n          if (templatesMapTemp.get(`${template.category}_${template.type}`)) return;\n          templatesMapTemp.set(`${template.category}_${template.type}`, template);\n        });\n\n        return templatesMapTemp;\n      }, [\n        mergedTheme,\n        templates,\n        addTemplates,\n        isAgenticUser,\n        hideReplyInThreadOption,\n        hideShareMessageOption,\n        hideEditMessageOption,\n        hideTranslateMessageOption,\n        hideDeleteMessageOption,\n        hideReactionOption,\n        hideMessagePrivatelyOption,\n        hideCopyMessageOption,\n        hideMessageInfoOption,\n        hideGroupActionMessages,\n        receiptsVisibility,\n      ]);\n\n      const getPlainString = (underlyingText: string, messageObject: CometChat.BaseMessage) => {\n        let _plainString = underlyingText;\n\n        let regexes: Array<RegExp> = [];\n        let users: any = {};\n\n        let edits: Array<{\n          endIndex: number;\n          replacement: string;\n          startIndex: number;\n          user: SuggestionItem;\n        }> = [];\n\n        let allFormatters = [...(textFormatters || [])];\n        let mentionsTextFormatter = ChatConfigurator.getDataSource().getMentionsFormatter(\n          loggedInUser.current\n        );\n        allFormatters.push(mentionsTextFormatter);\n\n        allFormatters.forEach((formatter, key) => {\n          regexes.push(formatter.getRegexPattern());\n          let suggestionUsers = formatter.getSuggestionItems();\n          if (formatter instanceof CometChatMentionsFormatter) {\n            let mentionUsers =\n              (messageObject?.getMentionedUsers && messageObject?.getMentionedUsers()).map(\n                (item: { getUid: () => any; getName: () => string }) =>\n                  new SuggestionItem({\n                    id: item.getUid(),\n                    name: item.getName(),\n                    promptText: \"@\" + item.getName(),\n                    trackingCharacter: \"@\",\n                    underlyingText: `<@uid:${item.getUid()}>`,\n                    hideLeadingIcon: false,\n                  })\n              ) || [];\n            suggestionUsers = [...suggestionUsers, ...mentionUsers];\n          }\n          suggestionUsers?.length > 0 &&\n            suggestionUsers.forEach((item: any) => (users[item.underlyingText] = item));\n        });\n\n        regexes.forEach((regex) => {\n          let match;\n          while ((match = regex.exec(_plainString)) !== null) {\n            const user = users[match[0]];\n            if (user) {\n              edits.push({\n                startIndex: match.index,\n                endIndex: regex.lastIndex,\n                replacement: user.promptText,\n                user,\n              });\n            }\n          }\n        });\n\n        // Sort edits by startIndex to apply them in order\n        edits.sort((a, b) => a.startIndex - b.startIndex);\n\n        // Get an array of the entries in the map using the spread operator\n        const entries = [...edits].reverse();\n\n        // Iterate over the array in reverse order\n        entries.forEach(({ endIndex, replacement, startIndex, user }) => {\n          let pre = _plainString.substring(0, startIndex);\n          let post = _plainString.substring(endIndex);\n\n          _plainString = pre + replacement + post;\n        });\n\n        return _plainString;\n      };\n\n      const playNotificationSound = useCallback((message: any) => {\n        if (disableSoundForMessages) return;\n\n        if (message?.category === MessageCategoryConstants.message) {\n          if (customSoundForMessages) {\n            CometChatSoundManager.play(\n              loggedInUser.current?.getUid() == message[\"sender\"][\"uid\"]\n                ? \"outgoingMessage\"\n                : \"incomingMessage\",\n              customSoundForMessages\n            );\n          } else {\n            CometChatSoundManager.play(\n              // \"incomingMessage\"\n              loggedInUser.current?.getUid() == message[\"sender\"][\"uid\"]\n                ? \"outgoingMessage\"\n                : \"incomingMessage\"\n            );\n          }\n        }\n      }, []);\n\n      const markMessageAsRead = (message: any) => {\n        if (\n          !message?.readAt &&\n          message?.getSender?.()?.getUid() !== loggedInUser.current?.getUid()\n        ) {\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, { message });\n          CometChat.markAsRead(message).catch((error: any) => {\n            console.log(\"Error\", error);\n            onError && onError(error);\n            // errorHandler(error);\n          });\n        }\n      };\n      /**\n       * Marks a specific message as unread in the conversation.\n       * This function calls the CometChat SDK to mark the message as unread,\n       * updates the conversation's unread count, and emits events to notify\n       * other components of the conversation update. It also sets the new\n       * message indicator to appear above this message.\n       * @param message - The message to mark as unread\n       */\n      const markMessageAsUnread = (message: CometChat.BaseMessage) => {\n        CometChat.markMessageAsUnread(message).then(\n          (response: any) => {\n            setShowMessageOptions([]);\n            setHasManuallyMarkedUnread(true);\n            let convId: string = \"\";\n            let conversationType: string = \"\";\n            if (user) {\n              convId = user?.getUid();\n              conversationType = CometChat.RECEIVER_TYPE.USER;\n            } else if (group) {\n              convId = group?.getGuid();\n              conversationType = CometChat.RECEIVER_TYPE.GROUP;\n            }\n            //getting convesation using conversationId=Uid and conversationType\n            getConversationIfExists(convId, conversationType).then((conversation) => {\n              if (!conversation) return;\n              // Emitting conversation update event\n              CometChatUIEventHandler.emitConversationEvent(\n                CometChatConversationEvents.ccUpdateConversation,\n                { conversation }\n              );\n              setUnreadCount(conversation.getUnreadMessageCount());\n              setNewMessageIndicatorId(message.getId().toString());\n            }).catch((error) => {\n              console.log(\"Error fetching conversation after marking as unread\", error);\n            });\n          },\n          (error: any) => {\n            console.log(\"Error marking as unread\", error);\n          }\n        );\n      };\n\n      function checkMessageInSameConversation(message: CometChat.BaseMessage | any): boolean {\n        return (\n          (message?.getReceiverType() == ReceiverTypeConstants.user &&\n            user &&\n            user?.getUid() == message.getReceiver()?.[\"uid\"]) ||\n          (message?.getReceiverType() == ReceiverTypeConstants.group &&\n            message.getReceiverId() &&\n            group &&\n            group?.getGuid() == message.getReceiverId()) ||\n          false\n        );\n      }\n\n      function messageToSameConversation(message: CometChat.BaseMessage): boolean {\n        return (\n          (message?.getReceiverType() == ReceiverTypeConstants.user &&\n            user &&\n            user?.getUid() == message.getReceiverId()) ||\n          (message?.getReceiverType() == ReceiverTypeConstants.group &&\n            message.getReceiverId() &&\n            group &&\n            group?.getGuid() == message.getReceiverId()) ||\n          false\n        );\n      }\n      useEffect(() => {\n        if (isAgenticUser && parentMessageId && !conversationId.current) {\n          conversationId.current = parentMessageId;\n        }\n      }, [parentMessageId, isAgenticUser]);\n\n      // Handle scrolling to highlighted message when FlatList layout is complete\n      useEffect(() => {\n        let rafId: number | undefined;\n\n        if (scrollTargetId && messagesList.length > 0) {\n          rafId = requestAnimationFrame(() => {\n            scrollToMessage(scrollTargetId);\n            setScrollTargetId(null);\n          });\n        }\n\n        return () => {\n          if (rafId) cancelAnimationFrame(rafId);\n        };\n      }, [scrollTargetId, messagesList.length]);\n\n      function checkSameConversation(message: CometChat.BaseMessage): boolean {\n        if ((message as any).isStreamMessage) {\n          if (isAgenticUser) {\n            return (message as any).targetMessageId === agenticParentMessageIdRef.current;\n          }\n          return true;\n        }\n\n        if (isAgenticUser) {\n          if (parentMessageId && agenticParentMessageIdRef.current) {\n            return (\n              message.getParentMessageId &&\n              String(message.getParentMessageId()) === String(agenticParentMessageIdRef.current)\n            );\n          }\n          return false;\n        }\n\n        return (\n          message.getConversationId() == conversationId.current ||\n          (message.getSender()?.getUid() === user?.getUid() &&\n            message.getReceiverType() == CometChatUiKitConstants.ReceiverTypeConstants.user) ||\n          checkMessageInSameConversation(message) ||\n          messageToSameConversation(message)\n        );\n      }\n\n      function isNearBottom() {\n        const { layoutHeight, scrollViewHeight, y }: any = currentScrollPosition.current;\n\n        // In an inverted list, scrolling up moves the user towards the top of the scrollable area,\n        // which is actually the \"bottom\" of the visible chat (since it's inverted).\n        let scrollPos = y; // 'y' is how far the user has scrolled from the top of the full content.\n\n        // Calculate how far the user is from the top of the scrollable content (which is visually\n        // the \"bottom\" in an inverted list) as a percentage of the total height.\n        if (!layoutHeight) {\n          return true;\n        }\n\n        // If we have navigated from search and are viewing old messages, we are NOT near bottom\n        if (navigatedFromSearch && hasTargetMessageId) {\n          return false;\n        }\n\n        let scrollPosFromTopInPercentage = (scrollPos / layoutHeight) * 100;\n\n        // If the user has scrolled to within 30% from the top (which is the \"bottom\" in this case),\n        // we consider them to be near the bottom of the list (since it's inverted).\n        if (scrollPosFromTopInPercentage <= 30) {\n          return true;\n        }\n        return false;\n      }\n\n      const newMessage = (newMessage: any, isReceived = true) => {\n        let baseMessage = newMessage as CometChat.BaseMessage;\n        if (baseMessage.getCategory() === MessageCategoryConstants.interactive) {\n          //todo show unsupported bubble\n        }\n\n        if (isAgenticUser) {\n          if (\n            String(baseMessage.getParentMessageId()) === String(parentMessageId) ||\n            String(baseMessage.getId()) === String(parentMessageId)\n          ) {\n            CometChat.markAsDelivered(newMessage);\n            bottomHandler(newMessage, isReceived);\n          } else {\n            return;\n          }\n        } else {\n          if (\n            checkSameConversation(baseMessage) ||\n            checkMessageInSameConversation(baseMessage) ||\n            messageToSameConversation(baseMessage)\n          ) {\n            if (user) {\n              CometChat.markConversationAsDelivered(user.getUid(), CometChat.RECEIVER_TYPE.USER);\n            } else if (group) {\n              CometChat.markConversationAsDelivered(group.getGuid(), CometChat.RECEIVER_TYPE.GROUP);\n            }\n            if (!parentMessageId && newMessage.getParentMessageId()) {\n              const parentId = String(\n                newMessage.getParentMessageId\n                  ? newMessage.getParentMessageId()\n                  : newMessage.parentMessageId\n              );\n              let index = messagesContentListRef.current.findIndex(\n                (msg) => String(msg.getId ? msg.getId() : msg.id) === parentId\n              );\n              if (index !== -1 && messagesContentListRef.current[index]) {\n                let oldMsg: CometChat.BaseMessage = CommonUtils.clone(\n                  messagesContentListRef.current[index]\n                );\n                oldMsg.setReplyCount(oldMsg.getReplyCount() ? oldMsg.getReplyCount() + 1 : 1);\n                let tmpList = [...messagesContentListRef.current];\n                tmpList[index] = oldMsg;\n                messagesContentListRef.current = tmpList;\n                onLoad && onLoad([...messagesContentListRef.current].reverse());\n                setMessagesList(tmpList);\n              }\n              return;\n            }\n\n            bottomHandler(newMessage, isReceived);\n          }\n        }\n\n        playNotificationSound(newMessage);\n      };\n\n      const isAtBottom = () => {\n        //0 or null\n        const atBottom = !currentScrollPosition.current.y || currentScrollPosition.current.y === 0;\n        return atBottom;\n      };\n\n      const bottomHandler = (\n        newMessage: CometChat.BaseMessage | any,\n        isReceived = false,\n        skipAddToList = false\n      ) => {\n        if (newMessage?.isStreamMessage) {\n          if (!skipAddToList) {\n            addToMessageList(newMessage);\n          }\n          scrollToBottom();\n          return;\n        }\n\n        if (\n          (newMessage.getSender()?.getUid() || newMessage?.[\"sender\"]?.[\"uid\"]) ==\n          loggedInUser.current?.[\"uid\"]\n        ) {\n          if (!skipAddToList) {\n            addToMessageList(newMessage);\n          }\n          scrollToBottom();\n          return;\n        }\n        if (!isReceived) {\n          return;\n        }\n        if (\n          (!parentMessageId && newMessage.getParentMessageId()) ||\n          (parentMessageId && !newMessage.getParentMessageId()) ||\n          (parentMessageId &&\n            newMessage.getParentMessageId() &&\n            parentMessageId != newMessage.getParentMessageId())\n        ) {\n          return;\n        }\n        if (isAtBottom() || isNearBottom() || scrollToBottomOnNewMessages) {\n          if (!skipAddToList) {\n            addToMessageList(newMessage);\n          }\n          scrollToBottom();\n          \n          markMessageAsRead(newMessage);\n          latestMessageRef.current = newMessage;\n        } else {\n          const lastMsg = messagesContentListRef.current[0];\n\n          // Helper to process the message after checking conversation state\n          const processMessage = (isSequenceCorrect: boolean) => {\n            if (isSequenceCorrect && temporaryMessageListRef.current.length === 0) {\n              if (!skipAddToList) addToMessageList(newMessage);\n              latestMessageRef.current = newMessage;\n            } else {\n              temporaryMessageListRef.current = [newMessage, ...temporaryMessageListRef.current];\n            }\n            setUnreadCount((prev) => prev + 1);\n          };\n\n          let conversationWith = \"\";\n          const conversationType = newMessage.getReceiverType();\n\n          if (conversationType === CometChat.RECEIVER_TYPE.USER) {\n            const senderUid = newMessage.getSender()?.getUid();\n            conversationWith =\n              senderUid === loggedInUser.current?.getUid()\n                ? newMessage.getReceiverId()\n                : senderUid;\n          } else {\n            conversationWith = newMessage.getReceiverId();\n          }\n\n          /**\n           * Sequence Integrity Check for New Message Handling\n           *\n           * Purpose:\n           * Ensures message sequence integrity when appending new messages to the chat list while the user is scrolled up.\n           * Prevents silent gaps in the conversation history due to network latency or missed real-time events.\n           *\n           * Implementation:\n           * Fetches the latest conversation state from the server to compare the local last message ID with the server's last message ID.\n           * If they match, the sequence is intact and the message can be safely appended to the visible list.\n           * If they differ, a gap exists, and the message is buffered in the temporary list to prompt the user to load missing messages.\n           *\n           * Performance Considerations:\n           * This asynchronous operation is conditionally executed only when the user is not at the bottom of the chat,\n           * minimizing unnecessary network calls during active scrolling or bottom-positioned interactions.\n           * A fallback mechanism reverts to local state comparison if the network request fails, ensuring robustness.\n           */\n          getConversationIfExists(conversationWith, conversationType)\n            .then((conversation) => {\n              if (!conversation) {\n                // No conversation yet (fresh user) — treat as sequence correct\n                processMessage(true);\n                return;\n              }\n              const convLastMsg = conversation?.getLastMessage();\n              // Check if the last message in our list matches the conversation's last message\n              // This ensures we don't have a gap\n              // THE CORE LOGIC:\n              // Does the ID of the last message in our list (lastMsg)\n              // MATCH the ID of the last message the server knows about (convLastMsg)?\n              const isSequenceCorrect =\n                lastMsg && convLastMsg && lastMsg.getId() === convLastMsg.getId();\n              processMessage(isSequenceCorrect);\n            })\n            .catch((e) => {\n              console.log(\"Error fetching conversation in bottomHandler\", e);\n              // Fall back to checking our local variable latestMessageRef.\n              const isLatest =\n                lastMsg &&\n                latestMessageRef.current &&\n                lastMsg.getId() === latestMessageRef.current.getId();\n              processMessage(isLatest);\n            });\n        }\n      };\n\n      const addToMessageList = async (newMessage: CometChat.BaseMessage) => {\n        // Handle incoming messages and prevent duplicate key errors\n        if (isAgenticUser && !conversationId.current) {\n          if (newMessage.getConversationId()) {\n            conversationId.current = newMessage.getConversationId();\n          } else if (!parentMessageId) {\n            const msgId = newMessage.getId();\n            if (msgId) {\n              conversationId.current = `${loggedInUser.current?.getUid()}_${user?.getUid()}_${msgId}`;\n            }\n          }\n        } else if (!conversationId.current && newMessage.getConversationId()) {\n          conversationId.current = newMessage.getConversationId();\n        }\n\n        // Prevent duplicate messages by checking existing messages\n        if (!isAgenticUser) {\n          try {\n            const incomingId = typeof (newMessage as any)?.getId === 'function' ? (newMessage as any).getId() : (newMessage as any)?.id;\n            const incomingMuid = (newMessage as any)?.muid || (typeof (newMessage as any)?.getMuid === 'function' ? (newMessage as any).getMuid() : undefined);\n            const existingIndex = messagesContentListRef.current.findIndex((m: any) => {\n              const mid = typeof m?.getId === 'function' ? m.getId() : m?.id;\n              const mmuid = m?.muid || (typeof m?.getMuid === 'function' ? m.getMuid() : undefined);\n              return (\n                (incomingId && mid && String(mid) === String(incomingId)) ||\n                (incomingMuid && mmuid && String(mmuid) === String(incomingMuid))\n              );\n            });\n            if (existingIndex !== -1) {\n              // Update existing message instead of duplicating\n              const updatedList = [...messagesContentListRef.current];\n              updatedList[existingIndex] = CommonUtils.clone(newMessage);\n              messagesContentListRef.current = updatedList;\n              onLoad && onLoad([...messagesContentListRef.current].reverse());\n              setMessagesList(updatedList);\n              return;\n            }\n          } catch (e) {\n            console.log(\"Error in message de-duplication:\", e);\n          }\n        }\n\n        const isStreamMessage = (newMessage as any).isStreamMessage;\n        const targetMessageId = (newMessage as any).targetMessageId;\n        if (isStreamMessage && targetMessageId) {\n          const targetId = String(targetMessageId);\n          let userMsgIdx = messagesContentListRef.current.findIndex(\n            (msg) => String(msg.getId()) === targetId\n          );\n          let updatedList;\n          if (userMsgIdx !== -1) {\n            updatedList = [...messagesContentListRef.current].filter(\n              (msg, idx) =>\n                !(\n                  idx === userMsgIdx - 1 &&\n                  (msg as any).isStreamMessage &&\n                  (msg as any).targetMessageId === targetId\n                )\n            );\n            updatedList.splice(userMsgIdx, 0, newMessage);\n            messagesContentListRef.current = updatedList;\n            onLoad && onLoad([...messagesContentListRef.current].reverse());\n            setMessagesList(updatedList);\n            return;\n          }\n        }\n        // Check for message sequence gaps and skip for own messages\n        const isOwnMessage = (newMessage.getSender()?.getUid() || (newMessage as any)?.[\"sender\"]?.[\"uid\"]) === loggedInUser.current?.[\"uid\"];\n\n        if (!isOwnMessage &&\n          latestMessageRef.current !== null &&\n          messagesContentListRef.current.length > 0\n        ) {\n          const firstMsg = messagesContentListRef.current[0];\n          const firstMsgId = firstMsg?.getId?.();\n          const firstMsgMuid = firstMsg?.getMuid?.() || (firstMsg as any)?.muid;\n          const latestMsgId = latestMessageRef.current?.getId?.();\n          const latestMsgMuid = latestMessageRef.current?.getMuid?.() || (latestMessageRef.current as any)?.muid;\n\n          const hasGap = (firstMsgId && latestMsgId && String(firstMsgId) !== String(latestMsgId)) &&\n            (firstMsgMuid && latestMsgMuid && String(firstMsgMuid) !== String(latestMsgMuid));\n\n          if (hasGap) {\n            reachedFirstMessage.current = false;\n            msgRequestBuilder.current?.setMessageId(0);\n            messageRequest.current = msgRequestBuilder.current?.build() || null;\n            messagesContentListRef.current = [];\n            await getPreviousMessages();\n          }\n        }\n        messagesContentListRef.current = [newMessage, ...messagesContentListRef.current];\n        onLoad && onLoad([...messagesContentListRef.current].reverse());\n        // Update latestMessageRef for gap detection\n        latestMessageRef.current = newMessage;\n\n        // Batch state updates for better performance\n        batchStateUpdates(() => {\n          setMessagesList([...messagesContentListRef.current]);\n          if (hideScrollToBottomButton === false && unreadCount < 1 && !hasTargetMessageId) {\n            setHideScrollToBottomButton(true);\n          }\n        });\n        aMessageWasSentByMeRef.current = true;\n      };\n\n      const markParentMessageAsRead = (message: CometChat.BaseMessage) => {\n        let condition: (value: any, index: number, obj: any[]) => unknown;\n        condition = (msg) => msg.getId() == message?.[\"parentMessageId\"];\n        let msgIndex = messagesList.findIndex(condition);\n        if (msgIndex > -1) {\n          let tmpList = [...messagesList];\n          if (message.getCategory() === MessageCategoryConstants.interactive) {\n            //todo show unsupported bubble\n          }\n          tmpList[msgIndex]?.setUnreadReplyCount(0);\n          messagesContentListRef.current = tmpList;\n          onLoad && onLoad([...messagesContentListRef.current].reverse());\n          setMessagesList(tmpList);\n        }\n      };\n\n      const messageEdited = (editedMessage: CometChat.BaseMessage, withMuid: boolean = false) => {\n        let condition: (value: any, index: number, obj: any[]) => unknown;\n        if (withMuid) {\n          condition = (msg) => msg[\"muid\"] == editedMessage[\"muid\"];\n        } else condition = (msg) => msg.getId() == editedMessage.getId();\n        let msgIndex = messagesContentListRef.current.findIndex(condition);\n        if (msgIndex > -1) {\n\n          let tmpList = [...messagesContentListRef.current];\n          if (editedMessage.getCategory() === MessageCategoryConstants.interactive) {\n            //todo show unsupported bubble\n          }\n          tmpList[msgIndex] = CommonUtils.clone(editedMessage);\n          messagesContentListRef.current = tmpList;\n          onLoad && onLoad([...messagesContentListRef.current].reverse());\n          setMessagesList(tmpList);\n        }\n      };\n\n      const removeMessage = (message: CometChat.BaseMessage) => {\n        let msgIndex = messagesList.findIndex((msg) => msg.getId() == message.getId());\n        if (msgIndex == -1) return;\n\n        let tmpList = [...messagesList];\n        tmpList.splice(msgIndex, 1);\n        messagesContentListRef.current = tmpList;\n        onLoad && onLoad([...messagesContentListRef.current].reverse());\n        setMessagesList(tmpList);\n      };\n\n      const deleteMessage = (message: CometChat.BaseMessage) => {\n        CometChat.deleteMessage(message.getId().toString())\n          .then((res: any) => {\n            CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageDeleted, {\n              message: res,\n            });\n            setShowMessageOptions([]);\n            setShowDeleteModal(false);\n            deleteItem.current = undefined;\n          })\n          .catch((rej: any) => {\n            deleteItem.current = undefined;\n            setShowDeleteModal(false);\n            onError && onError(rej);\n          });\n      };\n\n      const createActionMessage = () => { };\n\n      const updateMessageReceipt = (receipt: any) => {\n        if (\n          receipt?.getReceiverType() === ReceiverTypeConstants.group &&\n          ![\n            receipt.RECEIPT_TYPE.DELIVERED_TO_ALL_RECEIPT,\n            receipt.RECEIPT_TYPE.READ_BY_ALL_RECEIPT,\n          ].includes(receipt?.getReceiptType())\n        ) {\n          return;\n        }\n        // Use getter method for messageId (SDK receipt objects expose it via getMessageId())\n        let receiptMessageId = typeof receipt.getMessageId === 'function' ? receipt.getMessageId() : receipt[\"messageId\"];\n        let index = messagesContentListRef.current.findIndex(\n          (msg) => {\n            const msgId = typeof msg.getId === 'function' ? msg.getId() : msg[\"id\"];\n            return String(msgId) === String(receiptMessageId);\n          }\n        );\n\n        if (index == -1) return;\n\n        let tmpList: Array<CometChat.BaseMessage> = [...messagesContentListRef.current];\n\n        for (let i = index; i < messagesContentListRef.current.length; i++) {\n          if (tmpList[i]?.getReadAt && tmpList[i]?.getReadAt()) break;\n\n          let tmpMsg = tmpList[i];\n          if (!Number.isNaN(Number(tmpMsg.getId()))) {\n            if (tmpMsg.getCategory() === MessageCategoryConstants.interactive) {\n              //todo show unsupported bubble\n            }\n            if (receipt.getDeliveredAt()) {\n              tmpMsg.setDeliveredAt(receipt.getDeliveredAt());\n            }\n            if (receipt.getReadAt()) {\n              tmpMsg.setReadAt(receipt.getReadAt());\n            }\n          }\n          tmpList[i] = CommonUtils.clone(tmpMsg);\n        }\n\n        messagesContentListRef.current = tmpList;\n        onLoad && onLoad([...messagesContentListRef.current].reverse());\n        setMessagesList(tmpList);\n      };\n\n      const handlePannel = (item: any) => {\n        if (\n          item.alignment === ViewAlignment.messageListBottom &&\n          user &&\n          group &&\n          CommonUtils.checkIdBelongsToThisComponent(item.id, user, group, parentMessageId || \"\")\n        ) {\n          if (item.child) setCustomListHeader(() => item.child);\n          else setCustomListHeader(null);\n        }\n      };\n\n      useEffect(() => {\n        CometChatUIEventHandler.addUIListener(uiEventListenerShow, {\n          showPanel: (item) => handlePannel(item),\n          // ccMentionClick: (item) => {\n          //     // console.log(\"item\", item)\n          // }\n        });\n        CometChatUIEventHandler.addUIListener(uiEventListenerHide, {\n          hidePanel: (item) => handlePannel(item),\n        });\n        CometChatUIEventHandler.addUIListener(uiEventListener, {\n          ccToggleBottomSheet: (item) => {\n            bottomSheetRef.current?.togglePanel();\n          },\n        });\n        CometChat.getLoggedinUser()\n          .then(async (u: any) => {\n            loggedInUser.current = u;\n            if (isAgenticUser && !parentMessageId) {\n              messageRequest.current = null;\n            } else {\n              messageRequest.current = msgRequestBuilder.current?.build() || null;\n            }\n            if (!isAgenticUser || (isAgenticUser && parentMessageId)) {\n              if (goToMessageId) {\n                getMessagesAroundId(goToMessageId);\n              } else if (startFromUnreadMessages) {\n\n\n                try {\n                  const convId = user?.getUid() ?? group?.getGuid();\n                  const conversationType = user\n                    ? CometChat.RECEIVER_TYPE.USER\n                    : CometChat.RECEIVER_TYPE.GROUP;\n\n\n                  if (convId && conversationType) {\n\n                    const conversation = await getConversationIfExists(\n                      convId,\n                      conversationType\n                    );\n                    if (conversation && conversation.getUnreadMessageCount() > 0) {\n                      setUnreadCount(conversation.getUnreadMessageCount());\n                      const lastReadMessageId = conversation.getLastReadMessageId();\n                      //if lastReadMessageId is 0, it means no messages have been read yet\n                      if (lastReadMessageId && Number(lastReadMessageId) !== 0) {\n                        getMessagesAroundId(String(lastReadMessageId));\n                      } else {\n                        getPreviousMessages();\n                      }\n                    } else {\n                      getPreviousMessages();\n                    }\n                  } else {\n                    getPreviousMessages();\n                  }\n                } catch (e) {\n                  console.log(\"Error while getting conversation\", e);\n\n                  getPreviousMessages();\n                }\n              } else {\n                getPreviousMessages();\n              }\n              fetchLatestMessage();\n            }\n          })\n          .catch((e: any) => {\n            console.log(\"Error while getting loggedInUser\");\n            onError && onError(e);\n            loggedInUser.current = null;\n          });\n\n        return () => {\n          CometChatUIEventHandler.removeUIListener(uiEventListenerShow);\n          CometChatUIEventHandler.removeUIListener(uiEventListenerHide);\n          CometChatUIEventHandler.removeUIListener(uiEventListener);\n          onBack && onBack();\n        };\n      }, []);\n\n\n      const fetchLatestMessage = useCallback(() => {\n        const messageRequestBuilder = new CometChat.MessagesRequestBuilder()\n          .setLimit(1)\n          .setMessageId(0);\n\n        if (user) {\n          messageRequestBuilder.setUID(user.getUid());\n        }\n        if (group) {\n          messageRequestBuilder.setGUID(group.getGuid());\n        }\n        messageRequestBuilder\n          .build()\n          .fetchPrevious()\n          .then((messages) => {\n            if (messages[0]) {\n              latestMessageRef.current = messages[0];\n            }\n          });\n      }, []);\n\n      // Callback to add stream message and register queue completion callback\n      const createStreamMessage = useCallback(\n        (userMessage: CometChat.BaseMessage) => {\n          const runId = String(userMessage.getId());\n          const streamMessage = new StreamMessage(\n            user?.getUid() || group?.getGuid() || \"\",\n            user ? CometChat.RECEIVER_TYPE.USER : CometChat.RECEIVER_TYPE.GROUP,\n            CometChatUiKitConstants.streamMessageTypes.run_started,\n            CometChatUiKitConstants.MessageCategoryConstants.stream\n          );\n          streamMessage.setId(Number(userMessage.getId()));\n          if (user) {\n            streamMessage.setSender(user);\n          }\n          streamMessage.setReceiver(loggedInUser.current);\n          streamMessage.setReceiverType(CometChat.RECEIVER_TYPE.USER);\n          streamMessage.setSentAt(Math.floor(Date.now() / 1000));\n          (streamMessage as any).targetMessageId = runId;\n          (streamMessage as any).isStreamMessage = true;\n          return streamMessage;\n        },\n        [user, group, addToMessageList, setMessagesList, onLoad]\n      );\n\n      const updateStreamMessageWithActualId = useCallback(\n        (actualMessageId: string, runId: string) => {\n          const updatedList = messagesContentListRef.current.map((msg) => {\n            if ((msg as any).isStreamMessage === true && (msg as any).targetMessageId === runId) {\n              const updatedStreamMessage = { ...msg };\n              msg.setId(actualMessageId);\n              msg.targetMessageId = String(actualMessageId);\n              return msg;\n            }\n            return msg;\n          });\n\n          messagesContentListRef.current = updatedList;\n          onLoad && onLoad([...messagesContentListRef.current].reverse());\n          setMessagesList(updatedList);\n        },\n        [onLoad]\n      );\n\n      useEffect(() => {\n        if (isAgenticUser) {\n          agenticParentMessageIdRef.current = undefined;\n        }\n      }, [user?.getUid(), group?.getGuid(), isAgenticUser]);\n\n      useEffect(() => {\n        //add listeners\n\n        // AI Assistant listener for agent chats\n        if (isAgenticUser && user && (!parentMessageId || (parentMessageId && isAgenticUser))) {\n          CometChat.addAIAssistantListener(streamListenerId, {\n            onAIAssistantEventReceived: (message: CometChat.AIAssistantBaseEvent) => {\n              if (\n                message.getConversationId &&\n                (!message.getConversationId()?.includes(user.getUid()) ||\n                  !message.getConversationId()?.includes(loggedInUser.current?.getUid()))\n              ) {\n                return;\n              }\n\n              if (message.getType() === CometChatUiKitConstants.streamMessageTypes.run_started) {\n                const lastMessage = messagesContentListRef.current[0];\n                updateStreamMessageWithActualId(message.getMessageId(), message.getRunId());\n              }\n\n              if (message.getType() === CometChatUiKitConstants.streamMessageTypes.run_finished) {\n                const runId = String(message.getRunId?.() || message.getMessageId?.());\n\n                setQueueCompletionCallback(\n                  runId,\n                  (aiAssistantMessage, aiToolResultMessage, aiToolArgumentMessage) => {\n                    const replacement = aiAssistantMessage || aiToolResultMessage || aiToolArgumentMessage;\n                    if (!replacement) return;\n\n                    const replacementId = typeof (replacement as any)?.getId === 'function' ? (replacement as any).getId() : (replacement as any)?.id;\n                    const replacementRunId = (replacement as any)?.data?.runId;\n\n                    if (messagesContentListRef.current?.[0]?.isStreamMessage && messagesContentListRef.current?.[0]?.targetMessageId === runId) {\n                      latestMessageRef.current = replacement;\n                    }\n\n                    const list = messagesContentListRef.current;\n                    let updatedList: any[];\n\n                    // Find indices in one pass\n                    const existingIndex = list.findIndex((msg) => {\n                      const msgId = typeof (msg as any)?.getId === 'function' ? (msg as any).getId() : (msg as any)?.id;\n                      return msgId && String(msgId) === String(replacementId);\n                    });\n\n                    const streamIndex = existingIndex === -1 ? list.findIndex((msg) =>\n                      (msg as any).isStreamMessage === true && (msg as any).targetMessageId === runId\n                    ) : -1;\n\n                    const matchingIndex = (existingIndex === -1 && streamIndex === -1) ? list.findIndex((msg) => {\n                      const msgRunId = (msg as any)?.data?.runId;\n                      return msgRunId && String(msgRunId) === String(replacementRunId);\n                    }) : -1;\n\n                    // Update list based on what we found\n                    if (existingIndex !== -1) {\n                      // Update existing message\n                      updatedList = list.map((msg, idx) => idx === existingIndex ? replacement : msg);\n                    } else if (streamIndex !== -1) {\n                      // Replace stream message\n                      updatedList = list.map((msg, idx) => idx === streamIndex ? replacement : msg);\n                    } else if (matchingIndex !== -1) {\n                      // Insert before matching message\n                      updatedList = [...list];\n                      updatedList.splice(matchingIndex, 0, replacement);\n                    } else {\n                      // Add at beginning\n                      updatedList = [replacement, ...list];\n                    }\n\n                    messagesContentListRef.current = updatedList;\n                    setMessagesList(updatedList);\n                    onLoad && onLoad([...updatedList].reverse());\n                  }\n                );\n                checkAndTriggerQueueCompletion(runId);\n              }\n\n              handleWebsocketMessage(message);\n            },\n          });\n        }\n\n        let reactionListeners = {\n          onMessageReactionAdded: (reaction: CometChat.ReactionEvent) => {\n            updateMessageReaction(reaction, true);\n          },\n          onMessageReactionRemoved: (reaction: CometChat.ReactionEvent) => {\n            updateMessageReaction(reaction, false);\n          },\n        };\n\n        CometChat.addGroupListener(\n          groupListenerId,\n          new CometChat.GroupListener({\n            onGroupMemberScopeChanged: (message: any) => {\n              newMessage(message);\n            },\n            onGroupMemberLeft: (message: any) => {\n              newMessage(message);\n            },\n            onGroupMemberKicked: (message: any) => {\n              newMessage(message);\n            },\n            onGroupMemberBanned: (message: any) => {\n              newMessage(message);\n            },\n            onGroupMemberUnbanned: (message: any) => {\n              newMessage(message);\n            },\n            onMemberAddedToGroup: (message: any) => {\n              newMessage(message);\n            },\n            onGroupMemberJoined: (message: any) => {\n              newMessage(message);\n            },\n          })\n        );\n\n        CometChatUIEventHandler.addMessageListener(messageEventListener, {\n          ccMessageSent: ({ message, status }: any) => {\n            if (status == MessageStatusConstants.inprogress) {\n              // Remove incomplete stream messages before adding new user message (for agentic users)\n              if (isAgenticUser) {\n                const filteredList = messagesContentListRef.current.filter(\n                  (msg) => !(msg as any).isStreamMessage\n                );\n                if (filteredList.length !== messagesContentListRef.current.length) {\n                  messagesContentListRef.current = filteredList;\n                  setMessagesList(filteredList);\n                }\n              }\n              newMessage(message, false);\n            }\n\n            if (status == MessageStatusConstants.success) {\n              messageEdited(message, true);\n              if (\n                isAgenticUser &&\n                agenticParentMessageIdRef.current === undefined &&\n                messagesList.length !== 0 &&\n                message.getType() === MessageTypeConstants.text &&\n                message.getCategory() === MessageCategoryConstants.message\n              ) {\n                agenticParentMessageIdRef.current = String(message.getId());\n              }\n              if (isAgenticUser && agenticParentMessageIdRef.current !== undefined) {\n                const streamMessage = createStreamMessage(message);\n                addToMessageList(streamMessage);\n              }\n            }\n            if (status == MessageStatusConstants.error) {\n              messageEdited(message, true);\n            }\n          },\n          ccMessageEdited: ({ message, status }: any) => {\n            if (status == messageStatus.success) {\n              messageEdited(message, false);\n            }\n          },\n          ccMessageDeleted: ({ message }: any) => {\n            messageEdited(message, false);\n          },\n          ccMessageRead: ({ message }: any) => {\n            if (!parentMessageId && message.parentMessageId) {\n              // markParentMessageAsRead(message); //NOTE: uncomment this when want unread count in thread view\n            }\n          },\n          onTextMessageReceived: (textMessage: any) => {\n            if (isAgenticUser) {\n              if (\n                textMessage.getSender?.()?.getRole?.() === \"@agentic\" ||\n                textMessage.getCategory?.() === MessageCategoryConstants.agentic ||\n                textMessage.getCategory?.() ===\n                CometChatUiKitConstants.MessageCategoryConstants.stream\n              ) {\n                newMessage(textMessage);\n              }\n            } else {\n              newMessage(textMessage);\n            }\n          },\n          onMediaMessageReceived: (mediaMessage: any) => {\n            if (isAgenticUser) {\n              if (\n                mediaMessage.getSender?.()?.getRole?.() === \"@agentic\" ||\n                mediaMessage.getCategory?.() === MessageCategoryConstants.agentic ||\n                mediaMessage.getCategory?.() ===\n                CometChatUiKitConstants.MessageCategoryConstants.stream\n              ) {\n                newMessage(mediaMessage);\n              }\n            } else {\n              newMessage(mediaMessage);\n            }\n          },\n          onCustomMessageReceived: (customMessage: any) => {\n            if (isAgenticUser) {\n              if (\n                customMessage.getSender?.()?.getRole?.() === \"@agentic\" ||\n                customMessage.getCategory?.() === MessageCategoryConstants.agentic ||\n                customMessage.getCategory?.() ===\n                CometChatUiKitConstants.MessageCategoryConstants.stream\n              ) {\n                newMessage(customMessage);\n              }\n            } else {\n              newMessage(customMessage);\n            }\n          },\n          onMessagesDelivered: (messageReceipt: any) => {\n            updateMessageReceipt(messageReceipt);\n          },\n          onMessagesRead: (messageReceipt: any) => {\n            updateMessageReceipt(messageReceipt);\n          },\n          //suraj\n          onMessageDeleted: (deletedMessage: any) => {\n            // If the deleted message was unread, decrement the unread count\n            if (!deletedMessage.getReadAt?.() && deletedMessage.getSender()?.getUid() !== loggedInUser.current?.getUid()) {\n              setUnreadCount(prev => Math.max(0, prev - 1));\n            }\n            messageEdited(deletedMessage);\n          },\n          onMessageEdited: (editedMessage: any) => {\n            messageEdited(editedMessage);\n          },\n          onMessageModerated: (moderatedMessage: any) => {\n            !isAgenticUser && messageEdited(moderatedMessage);\n          },\n          onFormMessageReceived: (formMessage: any) => {\n            newMessage(formMessage);\n          },\n          onCardMessageReceived: (cardMessage: any) => {\n            newMessage(cardMessage);\n          },\n          onSchedulerMessageReceived: (schedulerMessage: any) => {\n            newMessage(schedulerMessage);\n          },\n          onCustomInteractiveMessageReceived: (customInteractiveMessage: any) => {\n            newMessage(customInteractiveMessage);\n          },\n          onInteractionGoalCompleted: (interactionReceipt: CometChat.InteractionReceipt) => {\n            //todo show unsupported bubble\n          },\n          onAIAssistantMessageReceived: (aiAssistantMessage: CometChat.AIAssistantMessage) => {\n            const assistantData = typeof aiAssistantMessage.getAssistantMessageData === 'function' ? aiAssistantMessage.getAssistantMessageData() : null;\n            const runIdFromData = assistantData && typeof assistantData.getRunId === 'function' ? assistantData.getRunId() : null;\n            const runIdFromId = typeof aiAssistantMessage.getId === 'function' ? aiAssistantMessage.getId() : (aiAssistantMessage as any)?.id;\n            const runId = runIdFromData || runIdFromId;\n            const parentMsgId = typeof aiAssistantMessage.getParentMessageId === 'function' ? aiAssistantMessage.getParentMessageId() : (aiAssistantMessage as any)?.parentMessageId;\n\n            if (\n              parentMessageId &&\n              String(parentMessageId) !== String(parentMsgId)\n            ) {\n              return;\n            }\n            if (runId) {\n              storeAIAssistantMessage(String(runId), aiAssistantMessage);\n            }\n          },\n          ...reactionListeners,\n          onMessagesDeliveredToAll: (messageReceipt: CometChat.MessageReceipt) => {\n            updateMessageReceipt(messageReceipt);\n          },\n          onMessagesReadByAll: (messageReceipt: CometChat.MessageReceipt) => {\n            updateMessageReceipt(messageReceipt);\n          },\n        });\n        CometChatUIEventHandler.addGroupListener(groupEventListener, {\n          ccGroupMemberUnBanned: ({ message }: any) => {\n            message[\"action\"] = \"unbanned\";\n            newMessage(message, false);\n          },\n          ccGroupMemberBanned: ({ message }: any) => {\n            message[\"action\"] = \"banned\";\n            newMessage(message, false);\n          },\n          ccGroupMemberAdded: ({ message, usersAdded, userAddedIn }: any) => {\n            usersAdded.forEach((user: any) => {\n              message[\"message\"] = `${loggedInUser.current?.getName()} added ${user.name}`;\n              message[\"muid\"] = String(getUnixTimestamp());\n              message[\"sentAt\"] = getUnixTimestamp();\n              message[\"actionOn\"] = user;\n              message[\"action\"] = \"added\";\n              newMessage(message, false);\n            });\n          },\n          ccGroupMemberKicked: ({ message }: any) => {\n            message[\"action\"] = \"kicked\";\n            newMessage(message, false);\n          },\n          ccGroupMemberScopeChanged: ({\n            action,\n            updatedUser,\n            scopeChangedTo,\n            scopeChangedFrom,\n            group,\n          }: any) => {\n            action[\"action\"] = \"scopeChanged\";\n            action[\"newScope\"] = scopeChangedTo;\n            action[\"oldScope\"] = scopeChangedFrom;\n            newMessage(action, false);\n          },\n          ccOwnershipChanged: ({ group, message }: any) => {\n            // newMessage(message, false); removed after discussion.\n          },\n        });\n\n        CometChat.addCallListener(\n          callListenerId,\n          new CometChat.CallListener({\n            onIncomingCallReceived: (call: any) => {\n              Platform.OS === \"ios\" && Keyboard.dismiss();\n              newMessage(call);\n            },\n            onOutgoingCallAccepted: (call: any) => {\n              newMessage(call);\n            },\n            onOutgoingCallRejected: (call: any) => {\n              newMessage(call);\n            },\n            onIncomingCallCancelled: (call: any) => {\n              newMessage(call);\n            },\n          })\n        );\n\n        CometChatUIEventHandler.addCallListener(callEventListener, {\n          ccCallInitiated: ({ call }: any) => {\n            if (\n              call[\"type\"] == CallTypeConstants.audio ||\n              call[\"type\"] == CallTypeConstants.video\n            ) {\n              newMessage(call);\n            }\n          },\n          ccOutgoingCall: ({ call }: any) => {\n            if (\n              call[\"type\"] == CallTypeConstants.audio ||\n              call[\"type\"] == CallTypeConstants.video\n            ) {\n              newMessage(call);\n            }\n          },\n          ccCallAccepted: ({ call }: any) => {\n            if (\n              call[\"type\"] == CallTypeConstants.audio ||\n              call[\"type\"] == CallTypeConstants.video\n            ) {\n              newMessage(call);\n            }\n          },\n          ccCallRejected: ({ call }: any) => {\n            if (\n              call[\"type\"] == CallTypeConstants.audio ||\n              call[\"type\"] == CallTypeConstants.video\n            ) {\n              newMessage(call);\n            }\n          },\n          ccCallEnded: ({ call }: any) => {\n            if (\n              call[\"type\"] == CallTypeConstants.audio ||\n              call[\"type\"] == CallTypeConstants.video\n            ) {\n              newMessage(call);\n            }\n          },\n          ccShowOngoingCall: (CometChatOngoingComponent) => {\n            //show ongoing call\n            setOngoingCallView(CometChatOngoingComponent?.child);\n          },\n        });\n        CometChat.addConnectionListener(\n          connectionListenerId,\n          new CometChat.ConnectionListener({\n            onConnected: () => {\n              streamOnConnected();\n              if (lastID.current) {\n                getUpdatedPreviousMessages();\n              }\n            },\n            inConnecting: () => { },\n            onDisconnected: () => {\n              streamOnDisconnected();\n              const currentMessages = messagesContentListRef.current;\n              if (currentMessages.length > 0 && !currentMessages[0].id) {\n                for (let i = 0; i < currentMessages.length; i++) {\n                  if (currentMessages[i].id) {\n                    lastID.current = currentMessages[i].id;\n                    break;\n                  }\n                }\n              } else if (currentMessages.length > 0) {\n                lastID.current = currentMessages[0].id;\n              }\n            },\n          })\n        );\n\n        return () => {\n          // clean up code like removing listeners\n          CometChatUIEventHandler.removeMessageListener(messageEventListener);\n          CometChatUIEventHandler.removeGroupListener(groupEventListener);\n          CometChatUIEventHandler.removeCallListener(callEventListener);\n\n          CometChat.removeGroupListener(groupListenerId);\n          CometChat.removeCallListener(callListenerId);\n          CometChat.removeConnectionListener(connectionListenerId);\n          if (isAgenticUser) {\n            CometChat.removeAIAssistantListener(streamListenerId);\n            if (streamSubscriptionRef.current) {\n              streamSubscriptionRef.current.unsubscribe();\n            }\n          }\n        };\n      }, [unreadCount, user, group, isAgenticUser]);\n\n      useEffect(() => {\n        if (aiAssistantTools) {\n          setAIAssistantTools(aiAssistantTools);\n        }\n      }, [aiAssistantTools]);\n\n      // Initialize streaming configuration and subscribe to streaming state\n      useEffect(() => {\n        setStreamSpeed(streamingSpeed);\n        return () => streamSubscriptionRef.current?.unsubscribe();\n      }, [streamingSpeed]);\n\n      useEffect(() => {\n        prevMessagesLength.current =\n          messagesLength.current || messagesContentListRef.current.length;\n        messagesLength.current = messagesContentListRef.current.length;\n      }, [messagesContentListRef.current]);\n\n      useEffect(() => {\n        if (selectedEmoji) {\n          Keyboard.dismiss();\n          setShowReactionList(true);\n        }\n      }, [selectedEmoji]);\n\n      useImperativeHandle(ref, () => {\n        return {\n          addMessage: newMessage,\n          updateMessage: messageEdited,\n          removeMessage,\n          deleteMessage,\n          scrollToBottom,\n          /// todo: not handeled yet\n          createActionMessage,\n          updateMessageReceipt,\n        };\n      });\n\n      const getMessageById = (messageId: string): CometChat.BaseMessage => {\n        const message = messagesList.find((message) => message.getId() === messageId);\n        return message;\n      };\n\n      function isReactionOfThisList(receipt: CometChat.ReactionEvent) {\n        const receiverId = receipt?.getReceiverId();\n        const receiverType = receipt?.getReceiverType();\n        const reactedById = receipt?.getReaction()?.getReactedBy()?.getUid();\n        const parentMessageId = receipt?.getParentMessageId();\n        const listParentMessageId = parentMessageId && String(parentMessageId);\n        if (listParentMessageId) {\n          if (parentMessageId === listParentMessageId) {\n            return true;\n          } else {\n            return false;\n          }\n        } else {\n          if (receipt.getParentMessageId()) {\n            return false;\n          }\n          if (user) {\n            if (\n              receiverType === ReceiverTypeConstants.user &&\n              (receiverId === user.getUid() || reactedById === user.getUid())\n            ) {\n              return true;\n            }\n          } else if (group) {\n            if (receiverType === ReceiverTypeConstants.group && receiverId === group.getGuid()) {\n              return true;\n            }\n          }\n        }\n        return false;\n      }\n\n      const updateMessageReaction = (message: CometChat.ReactionEvent, isAdded: boolean): void => {\n        let _isReactionOfThisList = isReactionOfThisList(message);\n        if (!_isReactionOfThisList) return;\n\n        const messageId = message?.getReaction()?.getMessageId();\n        const messageObject = getMessageById(messageId);\n        if (!messageObject) return;\n\n        let action: any;\n        if (isAdded) {\n          action = CometChat.REACTION_ACTION.REACTION_ADDED;\n        } else {\n          action = CometChat.REACTION_ACTION.REACTION_REMOVED;\n        }\n        const modifiedMessage = CometChat.CometChatHelper.updateMessageWithReactionInfo(\n          messageObject,\n          message.getReaction(),\n          action\n        );\n        if (modifiedMessage instanceof CometChat.CometChatException) {\n          onError && onError(modifiedMessage);\n          return;\n        }\n        messageEdited(modifiedMessage, false);\n      };\n\n      const getCurrentBubbleStyle = useCallback(\n        (item: CometChat.BaseMessage): BubbleStyles => {\n          const type = (() => {\n            if (item.getDeletedAt()) {\n              return MessageTypeConstants.messageDeleted;\n            }\n            if (item.getType() === MessageTypeConstants.text) {\n              let linkData = getExtensionData(item, ExtensionConstants.linkPreview);\n              if (linkData && linkData.links.length != 0) {\n                return ExtensionConstants.linkPreview;\n              }\n            }\n            return item.getType();\n          })();\n\n          if (item.getSender()?.getUid() != loggedInUser.current?.getUid()) {\n            return (\n              overridenBubbleStyles.get(type)?.incoming ??\n              mergedTheme.messageListStyles.incomingMessageBubbleStyles\n            );\n          }\n\n          return (\n            overridenBubbleStyles.get(type)?.outgoing ??\n            mergedTheme.messageListStyles.outgoingMessageBubbleStyles\n          );\n        },\n        [mergedTheme, overridenBubbleStyles]\n      );\n\n      // functions returning view\n      const getLeadingView = useCallback((item: CometChat.BaseMessage): JSX.Element | undefined => {\n        let _style = getCurrentBubbleStyle(item);\n\n        if (\n          [MessageCategoryConstants.action, MessageCategoryConstants.call].includes(\n            item.getCategory()\n          )\n        ) {\n          return undefined;\n        }\n        const isIncomingMessage = item.getSender()?.getUid() !== loggedInUser.current?.getUid();\n        const shouldShowAvatar =\n          avatarVisibility &&\n          (alignment === \"leftAligned\" ||\n            (isIncomingMessage && !user) ||\n            (isAgenticUser && isIncomingMessage));\n\n        if (shouldShowAvatar) {\n          return (\n            <CometChatAvatar\n              image={\n                item?.getSender()?.getAvatar && item?.getSender()?.getAvatar()\n                  ? { uri: item.getSender().getAvatar() }\n                  : undefined\n              }\n              name={\n                item?.getSender()?.getName && item?.getSender()?.getName()\n                  ? item?.getSender()?.getName()\n                  : \"\"\n              }\n              style={_style.avatarStyle}\n            />\n          );\n        }\n        return undefined;\n      }, []);\n\n      const getHeaderView = useCallback(\n        (item: CometChat.BaseMessage | any): JSX.Element | undefined => {\n          const _style = getCurrentBubbleStyle(item);\n          if (\n            (alignment === \"leftAligned\" ||\n              (item.getSender()?.getUid() != loggedInUser.current?.getUid() && !user)) &&\n            ![MessageCategoryConstants.action, MessageCategoryConstants.call].includes(\n              item.getCategory()\n            )\n          ) {\n            const senderName = (item.getSender()?.getName() || \"\").trim();\n            return (\n              <View style={{ flexDirection: \"row\" }}>\n                {Boolean(senderName) && (\n                  <Text\n                    style={_style.senderNameTextStyles}\n                    numberOfLines={1}\n                    ellipsizeMode={\"tail\"}\n                  >\n                    {senderName}\n                  </Text>\n                )}\n              </View>\n            );\n          }\n          return undefined;\n        },\n        []\n      );\n\n      const getStatusInfoView = useCallback(\n        (\n          item:\n            | CometChat.TextMessage\n            | CometChat.MediaMessage\n            | CometChat.CustomMessage\n            | CometChat.InteractiveMessage\n            | CometChat.BaseMessage\n            | any,\n          bubbleAlignment: MessageBubbleAlignmentType,\n          currentIndex?: number\n        ): JSX.Element | undefined => {\n          // Skip action category messages\n          if ((item as CometChat.BaseMessage).getCategory() === MessageCategoryConstants.action)\n            return undefined;\n\n          let isOutgoingMessage = item.getSender()?.getUid() === loggedInUser.current?.getUid();\n          let _style = getCurrentBubbleStyle(item);\n\n          let messageState;\n          const nextItemIsRead =\n            messagesContentListRef.current[currentIndex! - 1] &&\n            messagesContentListRef.current[currentIndex! - 1].getReadAt();\n          const nextItemIsDelivered =\n            messagesContentListRef.current[currentIndex! - 1] &&\n            messagesContentListRef.current[currentIndex! - 1].getDeliveredAt();\n          // Moderation status should override other receipt states for immediate error display\n          const moderationStatus = getModerationStatus(item);\n\n          // Check error metadata FIRST before sentAt/deliveredAt/readAt\n          const hasErrorMetadata = item?.getData?.()?.metaData?.error;\n          const hasErrorInMetadata = item?.getMetadata?.()?.error;\n          const hasErrorProp = (item as any)?.error;\n          if (hasErrorMetadata || hasErrorInMetadata || hasErrorProp) {\n            messageState = MessageReceipt.ERROR;\n          } else if (item.getReadAt() || nextItemIsRead) messageState = MessageReceipt.READ;\n          else if (item.getDeliveredAt() || nextItemIsDelivered)\n            messageState = MessageReceipt.DELIVERED;\n          else if (item.getSentAt()) messageState = MessageReceipt.SENT;\n          else if (isOutgoingMessage) messageState = MessageReceipt.WAIT;\n          else messageState = MessageReceipt.ERROR;\n\n          // Moderation overrides\n          if (moderationStatus === \"disapproved\") {\n            messageState = MessageReceipt.ERROR;\n          }\n          // Determine if message has been edited.\n          // This example assumes you have a method (or property) like getEditedAt() that returns a timestamp when edited.\n          const isEdited =\n            item.getEditedAt?.() &&\n            (item as CometChat.BaseMessage).getType() === MessageTypeConstants.text;\n\n          const shouldShowTimestamp = isAgenticUser ? isOutgoingMessage : !hideTimestamp;\n          return (\n            <View>\n              <View\n                style={[\n                  {\n                    flexDirection: \"row\",\n                    justifyContent: bubbleAlignment === \"right\" ? \"flex-end\" : \"flex-start\",\n                    alignSelf: \"flex-end\",\n                    alignItems: \"center\",\n                  },\n                  _style.dateReceiptContainerStyle,\n                ]}\n              >\n                {isEdited && (\n                  <Text\n                    style={[\n                      _style.dateStyles.textStyle,\n                      {\n                        textTransform: \"none\",\n                      },\n                    ]}\n                  >\n                    {t(\"EDITED\")}\n                  </Text>\n                )}\n                {shouldShowTimestamp && (\n                  <CometChatDate\n                    timeStamp={\n                      !hideTimestamp\n                        ? (item.getDeletedAt() || item.getSentAt()) * 1000 ||\n                        getSentAtTimestamp(item)\n                        : undefined\n                    }\n                    pattern={\"timeFormat\"}\n                    customDateString={datePattern && datePattern(item)}\n                    style={_style.dateStyles}\n                  />\n                )}\n                {receiptsVisibility &&\n                  alignment !== \"leftAligned\" &&\n                  isOutgoingMessage &&\n                  !item.getDeletedAt?.() ? (\n                  <View style={{ marginLeft: 2, alignItems: \"center\", justifyContent: \"center\" }}>\n                    <CometChatReceipt\n                      receipt={messageState}\n                      style={{\n                        deliveredIcon: _style.receiptStyles.deliveredIcon,\n                        readIcon: _style.receiptStyles.readIcon,\n                        sentIcon: _style.receiptStyles.sentIcon,\n                        waitIcon: _style.receiptStyles.waitIcon,\n                        errorIcon: _style.receiptStyles.errorIcon,\n                        sentIconStyle: _style.receiptStyles.sentIconStyle,\n                        readIconStyle: _style.receiptStyles.readIconStyle,\n                        waitIconStyle: _style.receiptStyles.waitIconStyle,\n                        errorIconStyle: _style.receiptStyles.errorIconStyle,\n                        deliveredIconStyle: _style.receiptStyles.deliveredIconStyle,\n                      }}\n                    />\n                  </View>\n                ) : null}\n              </View>\n            </View>\n          );\n        },\n        [mergedTheme, isAgenticUser, hideTimestamp]\n      );\n\n      const reactToMessage = (emoji: string, messageObj?: CometChat.BaseMessage) => {\n        const originalMessage = messageObj || selectedMessage;\n        const msgObj = CommonUtils.clone(originalMessage);\n\n        if (!msgObj.getId() && originalMessage?.getId()) {\n          msgObj.getId = () => originalMessage.getId();\n        }\n\n        const messageId = msgObj?.getId();\n        const reactions = msgObj?.getReactions() || [];\n        const emojiObject = reactions?.find((reaction: any) => {\n          return reaction?.reaction == emoji;\n        });\n\n        if (emojiObject && emojiObject?.getReactedByMe()) {\n          const updatedReactions: any[] = [];\n          reactions.forEach((reaction: any) => {\n            if (reaction?.getReaction() == emoji) {\n              if (reaction?.getCount() === 1) {\n                return;\n              } else {\n                reaction.setCount(reaction?.getCount() - 1);\n                reaction.setReactedByMe(false);\n                updatedReactions.push(reaction);\n              }\n            } else {\n              updatedReactions.push(reaction);\n            }\n          });\n\n          const newMessageObj = CommonUtils.clone(msgObj);\n          newMessageObj.setReactions(updatedReactions);\n\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n            message: newMessageObj,\n            status: messageStatus.success,\n          });\n\n          CometChat.removeReaction(messageId, emoji)\n            .then((message: any) => { })\n            .catch((error: any) => {\n              CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n                message: msgObj,\n                status: messageStatus.success,\n              });\n            });\n        } else {\n          const updatedReactions: any[] = [];\n          const reactionAvailable = reactions.find((reaction: any) => {\n            return reaction?.getReaction() == emoji;\n          });\n\n          reactions.forEach((reaction: any) => {\n            if (reaction?.getReaction() == emoji) {\n              reaction.setCount(reaction?.getCount() + 1);\n              reaction.setReactedByMe(true);\n              updatedReactions.push(reaction);\n            } else {\n              updatedReactions.push(reaction);\n            }\n          });\n          if (!reactionAvailable) {\n            const react: CometChat.ReactionCount = new CometChat.ReactionCount(emoji, 1, true);\n            updatedReactions.push(react);\n          }\n\n          const newMessageObj = CommonUtils.clone(msgObj);\n\n          newMessageObj.setReactions(updatedReactions);\n\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n            message: newMessageObj,\n            status: messageStatus.success,\n          });\n\n          CometChat.addReaction(messageId, emoji)\n            .then((response: any) => { })\n            .catch((error: any) => {\n              CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n                message: msgObj,\n                status: messageStatus.success,\n              });\n            });\n        }\n\n        setShowMessageOptions([]);\n      };\n\n      const onReactionPress = useCallback(\n        (reaction: CometChat.ReactionCount, messageObject: CometChat.BaseMessage) => {\n          if (reaction.getReaction() == \"All\") {\n            setSelectedMessage(messageObject);\n            setSelectedEmoji(reaction.getReaction());\n            return;\n          }\n          if (onReactionPressFromProp) {\n            onReactionPressFromProp(reaction, messageObject);\n            return;\n          }\n          reactToMessage(reaction.getReaction(), messageObject);\n        },\n        [onReactionPressFromProp]\n      );\n\n      const onReactionLongPress = useCallback(\n        (reaction: CometChat.ReactionCount, messageObject: CometChat.BaseMessage) => {\n          if (onReactionLongPressFromProp && reaction.getReaction() !== \"All\") {\n            onReactionLongPressFromProp(reaction, messageObject);\n            return;\n          }\n          setSelectedMessage(messageObject);\n          setSelectedEmoji(reaction.getReaction());\n        },\n        [onReactionLongPressFromProp]\n      );\n\n      const getFooterView = useCallback(\n        (messageObject: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          let hasReaction =\n            messageObject?.getReactions &&\n            messageObject?.getReactions() &&\n            messageObject?.getReactions().length > 0;\n          return hasReaction\n            ? (params: { maxContentWidth?: number }) => {\n              return (\n                <CometChatReactions\n                  messageObject={messageObject}\n                  onReactionPress={onReactionPress}\n                  onReactionLongPress={onReactionLongPress}\n                  alignment={alignment}\n                  style={getCurrentBubbleStyle(messageObject).reactionStyles}\n                  maxContentWidth={params?.maxContentWidth}\n                />\n              );\n            }\n            : null;\n        },\n        [mergedTheme]\n      );\n\n      const getAlignment = useCallback(\n        (item: CometChat.BaseMessage | any): MessageBubbleAlignmentType => {\n          if (item && item.getCategory() == MessageCategoryConstants.action) return \"center\";\n          if (\n            alignment == \"standard\" &&\n            (item.getSender()?.getUid() || item?.[\"sender\"]?.[\"uid\"]) ==\n            loggedInUser.current?.getUid()\n          )\n            return \"right\";\n          return \"left\";\n        },\n        []\n      );\n\n      const openMessageInfo = (message: any) => {\n        infoObject.current = message;\n        setMessageInfo(true);\n        setShowMessageOptions([]);\n      };\n\n      const openReportDialog = (message: any) => {\n        reportedMessageRef.current = message;\n        // On iOS wait for BottomSheet modal dismiss animation to avoid flashing\n        if (Platform.OS === \"ios\") {\n          pendingReportRef.current = true;\n        } else {\n          setShowReportDialog(true);\n        }\n        setShowMessageOptions([]);\n      };\n\n      const openThreadView = (...params: any[]) => {\n        if (onThreadRepliesPress) {\n          onThreadRepliesPress(params[0], () => (\n            <MessageView message={params[0]} isThreaded={true} showOptions={false} />\n          ));\n        }\n        setShowMessageOptions([]);\n        return onThreadRepliesPress;\n      };\n\n      const editMessage = (item: any) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n          message: item,\n          status: messageStatus.inprogress,\n        });\n        setShowMessageOptions([]);\n      };\n\n      const copyMessage = (item: any) => {\n        let copyMessage = getPlainString(item[\"text\"], item);\n        Clipboard.setString(copyMessage);\n        // Defer modal dismiss to next frame to avoid Fabric race condition\n        // where unmounting the Modal while Clipboard is still accessing views\n        // causes EXC_BAD_ACCESS (SIGSEGV) at null pointer in mount phase.\n        requestAnimationFrame(() => {\n          setShowMessageOptions([]);\n        });\n      };\n\n      const getThreadView = useCallback(\n        (item: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          let isThreaded = item.getReplyCount() > 0;\n\n          let _style = getCurrentBubbleStyle(item);\n          const moderationStatus = getModerationStatus(item);\n          const isOutgoing = item.getSender()?.getUid() === loggedInUser.current?.getUid();\n          if (isOutgoing && moderationStatus === \"disapproved\") {\n            return undefined; // hide thread view if disapproved\n          }\n          return !isThreaded ? undefined : (\n            <TouchableOpacity\n              onPress={() => openThreadView(item, null)}\n              style={_style.threadedMessageStyles?.containerStyle}\n            >\n              <Icon\n                icon={_style.threadedMessageStyles?.icon}\n                size={16}\n                name='subdirectory-arrow-right-fill'\n              />\n\n              <Text\n                style={_style.threadedMessageStyles?.indicatorTextStyle}\n              >{`${item.getReplyCount()} ${item.getReplyCount() > 1 ? t(\"REPLIES\") : t(\"REPLY\")\n                }`}</Text>\n              <CometChatBadge\n                style={{\n                  containerStyle: _style.threadedMessageStyles?.unreadCountStyle?.containerStyle,\n                  textStyle: _style.threadedMessageStyles?.unreadCountStyle?.textStyle,\n                }}\n                count={item.getUnreadRepliesCount()}\n              />\n            </TouchableOpacity>\n          );\n        },\n        [mergedTheme]\n      );\n\n      const privateMessage = (item: CometChat.BaseMessage) => {\n        setShowMessageOptions([]);\n        CometChat.getUser(item.getSender().getUid())\n          .then((user: any) => {\n            CometChatUIEventHandler.emitUIEvent(\"openChat\", { user });\n          })\n          .catch((e: any) => {\n            onError && onError(e);\n          });\n      };\n\n      const shareMedia = async (messageObject: CometChat.MediaMessage | any) => {\n        // Strip markdown/HTML from raw text BEFORE mention replacement,\n        // because getPlainString can break HTML tags (e.g., </u> becomes /u>)\n        let rawText = messageObject?.getData()[\"text\"] || \"\";\n        let strippedText = stripMarkdown(rawText);\n        let textMessage = getPlainString(strippedText, messageObject);\n        let fileUrl = messageObject.getData()[\"url\"];\n\n        const getFileName = () => {\n          if (!fileUrl) return \"\";\n          return fileUrl.substring(fileUrl.lastIndexOf(\"/\") + 1, fileUrl.length).replace(\" \", \"_\");\n        };\n\n        let shareObj = {\n          message: textMessage,\n          type: messageObject[\"type\"],\n          mediaName: getFileName(), // get File name\n          fileUrl: fileUrl || \"\", // get File url\n          mimeType:\n            messageObject[\"type\"] === \"text\"\n              ? \"\"\n              : (messageObject as CometChat.MediaMessage)?.getAttachment()?.getMimeType(), // get Mime Type\n        };\n\n        // Dismiss message options modal before presenting native share sheet\n        // to prevent screen freeze when share sheet is dismissed on iOS (Fabric).\n        // Use setTimeout with enough delay for the modal dismiss animation to complete.\n        setShowMessageOptions([]);\n        setTimeout(() => {\n          NativeModules.FileManager.shareMessage(shareObj, (callback: any) => {\n          });\n        }, 600);\n      };\n\n      const openOptionsForMessage = useCallback(\n        (item: CometChat.BaseMessage | any, template: CometChatMessageTemplate) => {\n          let options = template?.options\n            ? loggedInUser.current\n              ? template.options(loggedInUser.current, item, mergedTheme, group)\n              : []\n            : [];\n          if (hideFlagMessageOption) {\n            options = options.filter((opt: any) => opt.id !== MessageOptionConstants.reportMessage);\n          }\n          if (hideMarkAsUnreadOption) {\n            options = options.filter((opt: any) => opt.id !== MessageOptionConstants.markAsUnread);\n          }\n          let optionsWithPressHandling = options.map((option) => {\n            if (!option.onPress)\n              switch (option.id) {\n                case MessageOptionConstants.messageInformation:\n                  option.onPress = openMessageInfo.bind(this, item);\n                  break;\n                case MessageOptionConstants.replyMessage:\n                  option.onPress = () => {\n                    // Trigger swipe-to-reply functionality\n                    CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n                      message: item,\n                      status: messageStatus.inprogress,\n                    });\n                    setShowMessageOptions([]);\n                  };\n                  break;\n                case MessageOptionConstants.reportMessage:\n                  option.onPress = openReportDialog.bind(this, item);\n                  break;\n                case MessageOptionConstants.replyInThread:\n                  option.onPress = openThreadView.bind(this, item);\n                  break;\n                case MessageOptionConstants.deleteMessage:\n                  option.onPress = () => {\n                    deleteItem.current = item;\n                    bottomSheetRef.current?.togglePanel();\n                    Platform.OS === \"android\" && setShowDeleteModal(true);\n                  };\n\n                  break;\n                case MessageOptionConstants.editMessage:\n                  option.onPress = editMessage.bind(this, item);\n                  break;\n                case MessageOptionConstants.copyMessage:\n                  option.onPress = copyMessage.bind(this, item);\n                  break;\n                case MessageOptionConstants.sendMessagePrivately:\n                  option.onPress = privateMessage.bind(this, item);\n                  break;\n                // case MessageOptionConstants.forwardMessage:\n                //     option.onPress = showForwardMessage.bind(this, item);\n                //     break\n                case MessageOptionConstants.shareMessage:\n                  option.onPress = shareMedia.bind(this, item);\n                  break;\n                case MessageOptionConstants.markAsUnread:\n                  option.onPress = markMessageAsUnread.bind(this, item);\n                  break;\n              }\n            else {\n              // If overriding `onPress`, make sure to pass `item` explicitly\n              const customOnPress = option.onPress;\n              option.onPress = () => {\n                customOnPress(item);\n                setShowMessageOptions([]);\n              };\n            }\n            if (option.id === MessageOptionConstants.reactToMessage) {\n              option.onPress = () => {\n                if (option.CustomView) {\n                  let view = option.CustomView(item);\n                  setExtensionsComponent(() => view);\n                }\n              };\n            }\n            return option;\n          });\n          // Restrict options for disapproved outgoing messages (only delete + copy for text)\n          const moderationStatus = getModerationStatus(item);\n          const isOutgoing = item.getSender?.()?.getUid?.() === loggedInUser.current?.getUid?.();\n          if (moderationStatus === \"disapproved\" && isOutgoing) {\n            optionsWithPressHandling = optionsWithPressHandling.filter((opt) => {\n              if (opt.id === MessageOptionConstants.deleteMessage) return true;\n              if (\n                opt.id === MessageOptionConstants.copyMessage &&\n                (item.getType?.() === MessageTypeConstants.text || item?.getType === MessageTypeConstants.text)\n              )\n                return true;\n              return false;\n            });\n          }\n          setShowMessageOptions(optionsWithPressHandling);\n        },\n        [mergedTheme]\n      );\n\n      const MessageView = useCallback(\n        (params: {\n          message: CometChat.BaseMessage;\n          showOptions?: boolean;\n          isThreaded?: boolean;\n          currentIndex?: number;\n        }) => {\n          const { message, showOptions = true, isThreaded = false, currentIndex } = params\n\n          const hasTemplate = useMemo(() => {\n            // Detect InteractiveMessage instances where Object.assign\n            // (from mergeObjects during history fetch dedup) overwrote `category` and `type`\n            let lookupCategory: string = message.getCategory();\n            let lookupType: string = message.getType();\n\n            const hasInteractiveData =\n              typeof (message as any).getInteractiveData === 'function' &&\n              (message as any).getInteractiveData() != null;\n\n            if (hasInteractiveData && lookupCategory !== MessageCategoryConstants.interactive) {\n              lookupCategory = MessageCategoryConstants.interactive;\n              const interactiveData = (message as any).getInteractiveData();\n              if (interactiveData?.formFields) {\n                lookupType = MessageTypeConstants.form;\n              } else if (interactiveData?.scheduleElement || interactiveData?.timezoneCode) {\n                lookupType = MessageTypeConstants.scheduler;\n              } else if (interactiveData?.cardActions) {\n                lookupType = MessageTypeConstants.card;\n              }\n              (message as any).category = lookupCategory;\n              (message as any).type = lookupType;\n            }\n\n            const defaultTemplate = templatesMap.get(\n              `${lookupCategory}_${lookupType}`\n            );\n\n            if (templates?.length > 0) {\n              const customTemplate = templates.find(\n                (template) =>\n                  template.type === lookupType && template.category === lookupCategory\n              );\n              return customTemplate ?? defaultTemplate;\n            }\n\n            return defaultTemplate;\n          }, [message, templatesMap, templates]);\n\n          let bubbleAlignment: MessageBubbleAlignmentType = useMemo(() => {\n            return getAlignment(message);\n          }, [getAlignment]);\n\n          const ContentView = useMemo(() => {\n            return hasTemplate?.ContentView?.(message, bubbleAlignment);\n          }, [hasTemplate, message, bubbleAlignment]);\n\n          const HeaderView = useMemo(() => {\n            return hasTemplate?.HeaderView\n              ? hasTemplate?.HeaderView(message, bubbleAlignment)\n              : !isThreaded\n                ? getHeaderView(message)\n                : undefined;\n          }, [message, bubbleAlignment, hasTemplate, getHeaderView]);\n\n          const FooterView = useMemo(() => {\n            return hasTemplate?.FooterView\n              ? hasTemplate?.FooterView(message, bubbleAlignment)\n              : isThreaded\n                ? undefined\n                : getFooterView(message, bubbleAlignment);\n          }, [hasTemplate, isThreaded, getFooterView, message, bubbleAlignment]);\n\n          const ThreadedView = useMemo(() => {\n            if (isAgenticUser) return undefined;\n            return !isThreaded\n              ? !message.getDeletedBy()\n                ? getThreadView(message, bubbleAlignment)\n                : undefined\n              : undefined;\n          }, [isThreaded, message, getThreadView, bubbleAlignment]);\n\n          const LeadingView = useMemo(() => {\n            if (hasTemplate?.LeadingView) {\n              return hasTemplate.LeadingView(message, bubbleAlignment);\n            }\n            return !isThreaded ? getLeadingView(message) : undefined;\n          }, [isThreaded, message, getLeadingView, hasTemplate, bubbleAlignment]);\n\n          const BottomView = useMemo(() => {\n            const moderationStatus = getModerationStatus(message);\n            const isOutgoing = message?.getSender?.()?.getUid() === loggedInUser.current?.getUid();\n            \n            // Check for MIME type error (ERR_PERMISSION_DENIED)\n            const hasErrorMetadata = message?.getData?.()?.metaData?.error;\n            const hasErrorInMetadata = (message as any)?.getMetadata?.()?.error;\n            if (isOutgoing && (hasErrorMetadata || hasErrorInMetadata) && !isAgenticUser) {\n              return (\n                <MimeErrorBottomView\n                  moderationStyle={\n                    mergedTheme.messageListStyles.outgoingMessageBubbleStyles.moderationStyle\n                  }\n                />\n              );\n            }\n\n            if (effectiveHideModeration) {\n              return hasTemplate?.BottomView && hasTemplate?.BottomView(message, bubbleAlignment);\n            } else {\n              if (isOutgoing && moderationStatus === \"disapproved\" && !isAgenticUser) {\n                return (\n                  <ModerationBottomView\n                    status={moderationStatus}\n                    moderationStyle={\n                      mergedTheme.messageListStyles.outgoingMessageBubbleStyles.moderationStyle\n                    }\n                  />\n                );\n              }\n              return hasTemplate?.BottomView && hasTemplate?.BottomView(message, bubbleAlignment);\n            }\n          }, [hasTemplate, message, bubbleAlignment, effectiveHideModeration]);\n\n          const StatusInfoView = useMemo(() => {\n            return hasTemplate?.StatusInfoView &&\n              hasTemplate?.StatusInfoView(message, bubbleAlignment)\n              ? hasTemplate?.StatusInfoView(message, bubbleAlignment)\n              : getStatusInfoView(message, bubbleAlignment, currentIndex);\n          }, [hasTemplate, message, bubbleAlignment, currentIndex, getStatusInfoView]);\n\n         \n          const ReplyView = useMemo(() => {\n            if (templates && templates.length > 0) {\n              return ChatConfigurator.dataSource.getReplyView?.(message, mergedTheme, {\n                onReplyClick: (messageId: string) => {\n                  scrollToMessage(messageId);\n                },\n              }) || null;\n            }\n            return hasTemplate?.ReplyView?.(message, bubbleAlignment) || null;\n          }, [hasTemplate, message, bubbleAlignment, mergedTheme, templates]);\n\n          if (hasTemplate) {\n            if (hasTemplate?.BubbleView) return hasTemplate?.BubbleView(message);\n\n            const onLongPress = useCallback(() => {\n              // Ensure the keyboard is dismissed before showing options so the action sheet / menu isn't obscured\n              Keyboard.dismiss();\n\n              if (message.getDeletedBy() != null) return;\n\n              if (!message.getId()) {\n                return;\n              }\n              // console.log({hasTemplate});\n\n              setSelectedMessage(message);\n              hasTemplate && openOptionsForMessage(message, hasTemplate);\n            }, [hasTemplate, message, openOptionsForMessage]);\n\n            const onPress = useCallback(() => {\n              Keyboard.dismiss();\n            }, []);\n\n            // Swipe-to-reply functionality\n            const onReply = useCallback(() => {\n              if (message.getDeletedBy() != null) return;\n              if (!message.getId()) return;\n\n              CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n                message: message,\n                status: messageStatus.inprogress,\n              });\n            }, [message]);\n\n            const swipeAnimatedValue = useRef(new Animated.Value(0)).current;\n            const replyIconOpacity = useRef(new Animated.Value(0)).current;\n\n            const handlePanGesture = useCallback((event: PanGestureHandlerGestureEvent) => {\n              const { translationX, velocityX } = event.nativeEvent;\n\n              // Only allow swiping from left to right for replies\n              if (translationX <= 0) return;\n\n              const swipeThreshold = 80; // Threshold to trigger reply\n              const maxSwipeDistance = 120; // Maximum swipe distance\n\n              // Calculate progress (0 to 1)\n              const progress = Math.min(translationX / maxSwipeDistance, 1);\n\n              // Update animated values\n              swipeAnimatedValue.setValue(Math.min(translationX, maxSwipeDistance));\n              replyIconOpacity.setValue(progress);\n            }, []);\n\n            const handlePanStateChange = useCallback((event: PanGestureHandlerStateChangeEvent) => {\n              const { state, translationX, velocityX } = event.nativeEvent;\n\n              if (state === State.END) {\n                const swipeThreshold = 80;\n\n                // Reset animations\n                Animated.parallel([\n                  Animated.spring(swipeAnimatedValue, {\n                    toValue: 0,\n                    useNativeDriver: true,\n                  }),\n                  Animated.spring(replyIconOpacity, {\n                    toValue: 0,\n                    useNativeDriver: true,\n                  }),\n                ]).start();\n\n                const moderationStatus = getModerationStatus(message);\n                const isOutgoing = message.getSender()?.getUid() === loggedInUser.current?.getUid();\n                if (isOutgoing && moderationStatus && moderationStatus !== \"approved\" && moderationStatus !== \"unmoderated\") return;\n\n                // Trigger reply if threshold is met\n                if (translationX > swipeThreshold || velocityX > 500) {\n                  onReply();\n                }\n              }\n            }, [onReply, message]);\n\n            return (\n              message.getCategory() === MessageCategoryConstants.action || isAgenticUser ? (\n                <View style={{ position: 'relative' }}>\n                  <TouchableOpacity\n                    activeOpacity={1}\n                    onPress={Platform.OS === \"ios\" ? onPress : undefined}\n                    onLongPress={() => (showOptions ? onLongPress() : undefined)}\n                  >\n                    <CometChatMessageBubble\n                      id={`${message.getId()}`}\n                      LeadingView={LeadingView}\n                      HeaderView={HeaderView}\n                      FooterView={FooterView}\n                      ReplyView={ReplyView}\n                      alignment={isThreaded ? \"left\" : bubbleAlignment}\n                      ContentView={ContentView}\n                      ThreadView={ThreadedView}\n                      BottomView={BottomView}\n                      StatusInfoView={StatusInfoView}\n                      style={getCurrentBubbleStyle(message)}\n                    />\n                  </TouchableOpacity>\n                </View>\n              ) : (\n                <PanGestureHandler\n                  onGestureEvent={handlePanGesture}\n                  onHandlerStateChange={handlePanStateChange}\n                  activeOffsetX={[-10, 10]}\n                  failOffsetY={[-50, 50]}\n                >\n                  <Animated.View style={{ transform: [{ translateX: swipeAnimatedValue }] }}>\n                    <View style={{ position: 'relative' }}>\n                      <TouchableOpacity\n                        activeOpacity={1}\n                        onPress={Platform.OS === \"ios\" ? onPress : undefined}\n                        onLongPress={() => (showOptions ? onLongPress() : undefined)}\n                      >\n                        <CometChatMessageBubble\n                          id={`${message.getId()}`}\n                          LeadingView={LeadingView}\n                          HeaderView={HeaderView}\n                          FooterView={FooterView}\n                          ReplyView={ReplyView}\n                          alignment={isThreaded ? \"left\" : bubbleAlignment}\n                          ContentView={ContentView}\n                          ThreadView={ThreadedView}\n                          BottomView={BottomView}\n                          StatusInfoView={StatusInfoView}\n                          style={getCurrentBubbleStyle(message)}\n                        />\n                      </TouchableOpacity>\n                    </View>\n                  </Animated.View>\n                </PanGestureHandler>\n              )\n            );\n          } else {\n            return null;\n          }\n        },\n        [\n          mergedTheme,\n          templates,\n          templatesMap,\n          getCurrentBubbleStyle,\n          getThreadView,\n          getFooterView,\n          getLeadingView,\n          getAlignment,\n          highlightedMessageId,\n        ]\n      );\n\n      const getSentAtTimestamp = useCallback((item: any) => {\n        return item.getSentAt() ? item.getSentAt() * 1000 : Date.now();\n      }, []);\n\n\n      //extract once per item\n      const sentAtToMs = useCallback((msg?: any): number | undefined => {\n        if (!msg) return undefined;\n        const raw = typeof msg.getSentAt === \"function\"\n          ? msg.getSentAt()\n          : (msg?.sentAt ?? msg?.sent_at ?? msg?.sentOn ?? msg?.senton);\n        if (raw === undefined || raw === null) return undefined;\n        return raw < 1e12 ? raw * 1000 : raw;\n      }, []);\n\n      const makeDayKey = useCallback((ms?: number | undefined) => {\n        if (!ms) return null;\n        const d = new Date(ms);\n        // use local date parts so \"Today\" follows user's timezone\n        return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;\n      }, []);\n\n      const messageDayKeys = useMemo(() => {\n        return messagesList.map((m) => {\n          const ms = sentAtToMs(m);\n          return makeDayKey(ms); // null if no timestamp\n        });\n      }, [messagesList]);\n\n      //extract once per item\n      const isSameLocalDay = useCallback((ms?: number) => {\n        if (!ms) return false;\n        const now = new Date();\n        const date = new Date(ms);\n        return (\n          date.getFullYear() === now.getFullYear() &&\n          date.getMonth() === now.getMonth() &&\n          date.getDate() === now.getDate()\n        );\n      }, []);\n\n      // Memoize separator calculation with cache\n      const separatorCache = useRef(new Map<string, boolean>());\n      const shouldShowSeparator = useCallback((index: number, currentMs?: number, nextMs?: number) => {\n        const cacheKey = `${index}-${currentMs}-${nextMs}`;\n\n        if (separatorCache.current.has(cacheKey)) {\n          return separatorCache.current.get(cacheKey)!;\n        }\n\n        const listLength = messagesContentListRef.current.length;\n\n        // Last item in small list\n        if (index === listLength - 1 && listLength < SMALL_LIST_THRESHOLD) {\n          separatorCache.current.set(cacheKey, true);\n          return true;\n        }\n\n        // Different days\n        if (currentMs && nextMs) {\n          const currentDate = new Date(currentMs).getDate();\n          const nextDate = new Date(nextMs).getDate();\n          const result = currentDate !== nextDate;\n          separatorCache.current.set(cacheKey, result);\n          return result;\n        }\n\n        separatorCache.current.set(cacheKey, false);\n        return false;\n      }, []);\n\n      // Clear cache when messages change\n      useEffect(() => {\n        separatorCache.current.clear();\n      }, [messagesList.length]);\n\n      const getDayHeaderString = useCallback((ms?: number | undefined) => {\n        if (!ms) return undefined;\n        if (isSameLocalDay(ms)) return t(\"TODAY\") || \"Today\";\n        return dateSeparatorPattern ? dateSeparatorPattern(ms) : undefined;\n      }, [isSameLocalDay, t, dateSeparatorPattern]);\n\n      const RenderMessageItem = useCallback(\n        ({\n          item,\n          theme,\n          idx,\n        }: {\n          item: CometChat.BaseMessage;\n          theme: CometChatTheme;\n          idx: number;\n        }) => {\n          const index = idx;\n          lastMessageDate.current = getSentAtTimestamp(item);\n          const id = typeof item?.getId === 'function' ? item.getId() : (item as any)?.id;\n          const muid = typeof item?.getMuid === 'function' ? item.getMuid() : (item as any)?.muid;\n          return (\n            <React.Fragment key={`${id}_${muid}`}>\n              <MessageView message={item} currentIndex={index} />\n            </React.Fragment>\n          );\n        },\n        [themeMode] // Only recreate when theme mode changes - MessageView accessed via closure\n      );\n\n      const keyExtractor = useCallback((item: any, index: number) => {\n        // Safely check if methods exist before calling them\n        const id = typeof item?.getId === 'function' ? item.getId() : item?.id;\n        const muid = typeof item?.getMuid === 'function' ? item.getMuid() : item?.muid;\n\n\n        // For stream messages in agentic mode, use a unique combination\n        // to prevent duplicate keys when navigating between chat and history\n        if (item?.isStreamMessage) {\n          const targetId = item?.targetMessageId || '';\n          return `stream_${id}_${targetId}_${index}`;\n        }\n\n        // Use id + muid if both exist\n        if (id && muid) {\n          return `${id}_${muid}`;\n        }\n\n\n        // Use muid if id doesn't exist (pending message)\n        if (muid) {\n          return `muid_${muid}`;\n        }\n\n\n        // Use id if muid doesn't exist\n        if (id) {\n          return `id_${id}`;\n        }\n\n\n        // Fallback to index if neither exist (should not happen, but prevents duplicates)\n        if (__DEV__) {\n          console.warn('[MessageList] Message without id or muid at index:', index, item);\n        }\n        return `index_${index}`;\n      }, []);\n\n      const itemSeparator = useCallback(() => <View style={staticStyles.itemSeparator} />, [staticStyles]);\n\n      const getAgentEmptyView = useCallback(() => {\n        if (!user) return <></>;\n\n        const userMetadata = user.getMetadata() as any;\n        const displaySuggestions =\n          suggestedMessages.length > 0\n            ? suggestedMessages\n            : (userMetadata?.suggestedMessages as string[]) || [];\n\n        const isDark = mergedTheme.mode === \"dark\";\n        const { background2, textPrimary, textSecondary, borderLight, primary, textTertiary } =\n          mergedTheme.color;\n\n        const avatarURL = user.getAvatar();\n        const isSVG = avatarURL && (avatarURL.includes(\".svg\") || avatarURL.includes(\"svg\"));\n        const imageSource = !isSVG && avatarURL ? { uri: avatarURL } : undefined;\n\n        return (\n          <ScrollView\n            contentContainerStyle={staticStyles.emptyScrollView}\n            showsVerticalScrollIndicator={false}\n          >\n            <View style={staticStyles.emptyContainer}>\n              {emptyChatImageView || (\n                <CometChatAvatar\n                  image={imageSource}\n                  name={user.getName()}\n                  style={{\n                    containerStyle: {\n                      ...staticStyles.avatarContainer,\n                      backgroundColor: mergedTheme.avatarStyle.containerStyle?.backgroundColor,\n                    },\n                    imageStyle: staticStyles.avatarImage,\n                    textStyle: {\n                      ...staticStyles.avatarText,\n                      color: mergedTheme.avatarStyle.textStyle?.color,\n                    },\n                  }}\n                />\n              )}\n\n              {emptyChatGreetingView || (\n                <Text\n                  style={{\n                    fontSize: theme.typography.heading4.medium.fontSize,\n                    fontWeight: theme.typography.heading4.medium.fontWeight,\n                    textAlign: \"center\",\n                    color: textPrimary,\n                  }}\n                >\n                  {userMetadata?.greetingMessage ?? `Hi, I'm ${user.getName()}`}\n                </Text>\n              )}\n\n              {emptyChatIntroMessageView || (\n                <Text\n                  style={{\n                    fontSize: theme.typography.body.regular.fontSize,\n                    fontWeight: theme.typography.body.regular.fontWeight,\n                    marginBottom: theme.spacing.spacing.s5,\n                    textAlign: \"center\",\n                    lineHeight: 22,\n                    paddingHorizontal: 20,\n                    color: textTertiary,\n                  }}\n                >\n                  {userMetadata?.introductoryMessage ??\n                    \"I'm here to help! Ask me anything or choose from the suggestions below.\"}\n                </Text>\n              )}\n\n              {!hideSuggestedMessages && displaySuggestions?.length > 0 && (\n                <View\n                  style={{\n                    flexDirection: \"row\",\n                    flexWrap: \"wrap\",\n                    justifyContent: \"center\",\n                    paddingHorizontal: 0,\n                    maxWidth: \"100%\",\n                  }}\n                >\n                  {displaySuggestions.map((suggestion, idx) => (\n                    <TouchableOpacity\n                      key={idx}\n                      style={{\n                        borderRadius: 20,\n                        paddingHorizontal: theme.spacing.spacing.s4,\n                        paddingVertical: theme.spacing.spacing.s2,\n                        margin: theme.spacing.margin.m1,\n                        borderWidth: 1,\n                        backgroundColor: isDark ? theme.color.background1 : theme.color.background1,\n                        borderColor: isDark\n                          ? mergedTheme.color.borderDefault\n                          : mergedTheme.color.borderDefault,\n                        flexDirection: \"row\",\n                        alignItems: \"center\",\n                        maxWidth: \"100%\",\n                      }}\n                      onPress={() => {\n                        if (onSuggestedMessageClick) {\n                          onSuggestedMessageClick(suggestion);\n                        } else {\n                          CometChatUIEventHandler.emitUIEvent(\"ccComposeMessage\", {\n                            text: suggestion,\n                          });\n                        }\n                      }}\n                    >\n                      <Text\n                        style={{\n                          fontWeight: theme.typography.body.regular.fontWeight,\n                          fontSize: theme.typography.body.regular.fontSize,\n                          color: textSecondary,\n                          paddingRight: mergedTheme.spacing.padding.p2,\n                          flexShrink: 1,\n                        }}\n                      >\n                        {suggestion}\n                      </Text>\n                      <Icon size={14} name='right-arrow' color={theme.color.iconSecondary} />\n                    </TouchableOpacity>\n                  ))}\n                </View>\n              )}\n            </View>\n          </ScrollView>\n        );\n      }, [\n        user,\n        suggestedMessages,\n        hideSuggestedMessages,\n        emptyChatGreetingView,\n        emptyChatIntroMessageView,\n        emptyChatImageView,\n        onSuggestedMessageClick,\n        mergedTheme,\n      ]);\n\n      const getEmptyStateView = useCallback(() => {\n        const isAgenticUserCheck = user?.getRole?.() === \"@agentic\";\n\n        if (isAgenticUserCheck) {\n          return getAgentEmptyView();\n        }\n        if (EmptyView)\n          return (\n            <>\n              <ScrollView contentContainerStyle={{}}>{EmptyView()}</ScrollView>\n            </>\n          );\n        return (\n          <ErrorEmptyView\n            containerStyle={\n              (mergedTheme.messageListStyles.emptyStateStyle?.containerStyle ??\n                mergedTheme?.messageListStyles.containerStyle) as ViewStyle\n            }\n            titleStyle={mergedTheme.messageListStyles.emptyStateStyle?.textStyle as TextStyle}\n          />\n        );\n      }, [mergedTheme, EmptyView, getAgentEmptyView, user]);\n\n      const getErrorStateView = useCallback(() => {\n        if (hideError) return null;\n        if (ErrorView) return ErrorView();\n        return (\n          <ErrorEmptyView\n            title={t(\"OOPS\")}\n            subTitle={t(\"SOMETHING_WENT_WRONG\")}\n            tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n            Icon={\n              <Icon\n                name='error-state'\n                size={mergedTheme.spacing.spacing.s15 * 2}\n                icon={mergedTheme.messageListStyles.errorStateStyle?.icon}\n                height={mergedTheme.messageListStyles.errorStateStyle?.iconStyle?.height}\n                width={mergedTheme.messageListStyles.errorStateStyle?.iconStyle?.width}\n                imageStyle={mergedTheme.messageListStyles.errorStateStyle?.iconStyle}\n                containerStyle={mergedTheme.messageListStyles.errorStateStyle?.iconContainerStyle}\n              />\n            }\n            containerStyle={mergedTheme.messageListStyles.errorStateStyle?.containerStyle}\n            titleStyle={mergedTheme.messageListStyles.errorStateStyle?.titleStyle}\n            subTitleStyle={mergedTheme.messageListStyles.errorStateStyle?.subtitleStyle}\n          />\n        );\n      }, [mergedTheme]);\n\n      const getLoadingStateView = useCallback(() => {\n        if (LoadingView) return LoadingView();\n\n        return (\n          <View style={{ padding: 16 }}>\n            <MessageSkeleton />\n          </View>\n        );\n      }, []);\n\n      const fetchNextMessages = async () => {\n        if (loadingMessagesRef.current || !hasTargetMessageId) return;\n\n        if (messagesContentListRef.current.length === 0) return;\n\n        const newestMessage = messagesContentListRef.current[0];\n        const newestMessageId = newestMessage.getId();\n        setBottomLoading(true);\n        loadingMessagesRef.current = true;\n\n        try {\n          let nextMessagesRequest = new CometChat.MessagesRequestBuilder()\n            .setLimit(30)\n            .setMessageId(newestMessageId);\n\n          if (user) {\n            nextMessagesRequest = nextMessagesRequest.setUID(user.getUid());\n          }\n          if (group) {\n            nextMessagesRequest = nextMessagesRequest.setGUID(group.getGuid());\n          }\n          if (!parentMessageId) {\n            nextMessagesRequest = nextMessagesRequest.hideReplies(true);\n          }\n          // Thread: Server includes only relevant messages\n          else {\n            nextMessagesRequest = nextMessagesRequest\n              .setParentMessageId(Number(parentMessageId))\n              .hideReplies(false);\n            if (isAgenticUser) {\n              nextMessagesRequest = nextMessagesRequest.withParent(true);\n            }\n          }\n\n          const nextMessages = await nextMessagesRequest.build().fetchNext();\n          if (nextMessages.length > 0) {\n\n            const existingIds = new Set(messagesContentListRef.current.map(m => m.getId()));\n            const uniqueNewMessages = nextMessages.filter(m => !existingIds.has(m.getId()));\n\n            if (uniqueNewMessages.length > 0) {\n              const reversedNewMessages = uniqueNewMessages.reverse();\n              messagesContentListRef.current = [\n                ...reversedNewMessages,\n                ...messagesContentListRef.current\n              ];\n\n              setMessagesList(messagesContentListRef.current);\n              onLoad && onLoad([...messagesContentListRef.current].reverse());\n            }\n          } else {\n            setHasTargetMessageId(false);\n          }\n        } catch (error) {\n          console.error('[fetchNextMessages] Error:', error);\n        } finally {\n          setBottomLoading(false);\n          loadingMessagesRef.current = false;\n        }\n      };\n\n      const renderFooter = () => {\n        if (!bottomLoading) return null;\n        return (\n          <View style={{ padding: 10, alignItems: \"center\" }}>\n            <ActivityIndicator size='small' color={mergedTheme.color.primary} />\n          </View>\n        );\n      };\n\n      const scrollHandler = (event: any) => {\n        /********************************************************************************\n             * layoutMeasurement.height: The height of the visible area within the ScrollView.\n             * contentOffset.y: The current vertical scroll position (distance from the top of the content).\n                                The y value in contentOffset indicates how far the top edge of the visible area is from the top of the scrollable content.\n                                For example:\n                                   If contentOffset.y is 0, the top of the visible area is aligned with the top of the content.\n                                   If contentOffset.y is 50, the top of the visible area is 50 units (pixels) below the top of the content.\n             * contentSize.height: The total height of the scrollable content.\n            *********************************************************************************/\n        const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;\n        currentScrollPosition.current.y = contentOffset.y;\n        currentScrollPosition.current.contentHeight = contentSize.height;\n\n        if (currentScrollPosition.current.layoutHeight != layoutMeasurement.height) {\n          currentScrollPosition.current.layoutHeight = layoutMeasurement.height;\n        }\n        if (currentScrollPosition.current.scrollViewHeight !== contentSize.height) {\n          currentScrollPosition.current.scrollViewHeight = contentSize.height;\n        }\n\n        const nearBottom = isNearBottom();\n        setHideScrollToBottomButton(nearBottom);\n\n        if (nearBottom && temporaryMessageListRef.current.length > 0) {\n          // Merge temporary messages when scrolling near bottom\n          messagesContentListRef.current = [\n            ...temporaryMessageListRef.current,\n            ...messagesContentListRef.current,\n          ];\n          temporaryMessageListRef.current = [];\n\n          // Update latestMessageRef after merge\n          if (messagesContentListRef.current.length > 0) {\n            latestMessageRef.current = messagesContentListRef.current[0];\n          }\n\n          setMessagesList([...messagesContentListRef.current]);\n          onLoad && onLoad([...messagesContentListRef.current].reverse());\n\n          setUnreadCount(0);\n\n          if (hasManuallyMarkedUnread) {\n            setHasManuallyMarkedUnread(false);\n          }\n\n          if (messagesContentListRef.current.length > 0) {\n            const latestMessage = messagesContentListRef.current[0];\n            markMessageAsRead(latestMessage);\n          }\n        }\n      };\n\n      const scrollToBottom = useCallback((scrollToFirstUnread = false) => {\n        // Merge temporary messages when scrolling to bottom\n        if (temporaryMessageListRef.current.length > 0) {\n          messagesContentListRef.current = [\n            ...temporaryMessageListRef.current,\n            ...messagesContentListRef.current,\n          ];\n          temporaryMessageListRef.current = [];\n\n          // Update latestMessageRef after merge\n          if (messagesContentListRef.current.length > 0) {\n            latestMessageRef.current = messagesContentListRef.current[0];\n          }\n\n          setMessagesList([...messagesContentListRef.current]);\n          onLoad && onLoad([...messagesContentListRef.current].reverse());\n\n          if (hasManuallyMarkedUnread) {\n            setHasManuallyMarkedUnread(false);\n          }\n        }\n\n        setUnreadCount(0);\n        setHideScrollToBottomButton(true);\n        messageListRef.current?.scrollToOffset({ offset: 0 });\n        // Clear navigation flags\n        setHasTargetMessageId(false);\n        setHighlightedMessageId(null);\n        setHasManuallyMarkedUnread(false);\n\n        // Mark latest message as read if available\n        if (messagesContentListRef.current.length > 0) {\n          const latestMessage = messagesContentListRef.current[0];\n          markMessageAsRead(latestMessage);\n        }\n        // setNavigatedFromSearch(false); // navigatedFromSearch is a prop, cannot set it\n      }, [hasTargetMessageId, messagesList.length]);\n\n      const handleScrollInternal = useCallback((event: any) => {\n        scrollHandler(event);\n      }, [scrollHandler]);\n\n\n      const handleScrollToIndexFailed = useCallback((error: any) => {\n\n        // Calculate dynamic average height from measured items\n        const measuredItems = Array.from(itemPositions.values());\n        let averageHeight = AVERAGE_ITEM_LENGTH;\n\n        if (measuredItems.length > 0) {\n          const totalHeight = measuredItems.reduce((sum, item) => sum + item.height, 0);\n          averageHeight = totalHeight / measuredItems.length;\n        }\n\n        // Scroll to estimated position to load more items\n        const targetOffset = error.index * averageHeight;\n\n        messageListRef.current?.scrollToOffset({\n          offset: targetOffset,\n          animated: true,\n        });\n\n        // After scrolling to load more items, retry scrollToIndex\n        setTimeout(() => {\n          if (messageListRef.current) {\n            try {\n              messageListRef.current.scrollToIndex({\n                index: error.index,\n                animated: true,\n                viewPosition: 0.5, // For inverted FlatList\n              });\n            } catch (retryError) {\n              console.warn('Retry scrollToIndex also failed:', retryError);\n            }\n          }\n        }, 300); // Wait for items to load\n      }, [itemPositions]);\n\n\n\n      // Clear item positions when messages list changes significantly\n      // Don't clear on minor changes (like adding one message) or when navigating\n      useEffect(() => {\n        const lengthDiff = Math.abs(messagesList.length - prevMessagesLength.current);\n        const isSignificantChange = lengthDiff > 5; // Only clear if 5+ messages added/removed\n\n        if (isSignificantChange) {\n          setItemPositions(new Map());\n        }\n\n        prevMessagesLength.current = messagesList.length;\n      }, [messagesList.length]);\n\n      // Handle layout measurement for accurate scrolling\n      const handleItemLayout = useCallback((event: any, messageId: string) => {\n        const { y, height } = event.nativeEvent.layout;\n        setItemPositions(prev => {\n          const newMap = new Map(prev);\n          newMap.set(messageId, { y, height });\n          return newMap;\n        });\n      }, []);\n      //\n      // Configuration for determining when list items are considered \"viewable\" (visible on screen)\n      // itemVisiblePercentThreshold: 50 means an item is viewable when at least 50% of it is visible\n      // This is used by onViewableItemsChanged to detect when messages come into view for auto-read functionality\n      const viewabilityConfig = useRef({\n        itemVisiblePercentThreshold: 50,\n      }).current;\n\n      // This function handles marking the conversation as read ONLY when the user scrolls\n      // to the \"New Message\" indicator (the first unread message).\n      // This preserves the unread count while the user is viewing searched messages.\n      const handleViewableItemsChanged = useCallback(({ viewableItems }: any) => {\n        if (navigatedFromSearch && newMessageIndicatorId && unreadCount > 0 && !hasManuallyMarkedUnread) {\n          const indicatorItem = viewableItems.find(\n            (item: any) => String(item.item.getId()) === newMessageIndicatorId\n          );\n\n          if (indicatorItem) {\n            if (user) {\n              CometChat.markConversationAsRead(user.getUid(), CometChat.RECEIVER_TYPE.USER).catch((e) => {\n                console.log(\"Error marking user conversation as read\", e);\n              });\n            } else if (group) {\n              CometChat.markConversationAsRead(group.getGuid(), CometChat.RECEIVER_TYPE.GROUP).catch((e) => {\n                console.log(\"Error marking group conversation as read\", e);\n              });\n            }\n            setUnreadCount(0);\n          }\n        }\n      }, [navigatedFromSearch, newMessageIndicatorId, unreadCount, user, group, hasManuallyMarkedUnread]);\n\n\n\n      // Optimized FlatList renderItem using memoized component\n      const memoizedRenderItem = useCallback(\n        ({ item, index }: any) => {\n          const nextItem = messagesContentListRef.current[index + 1];\n          const currentItemMs = sentAtToMs(item);\n          const nextItemMs = sentAtToMs(nextItem);\n          const showSeparator = shouldShowSeparator(index, currentItemMs, nextItemMs);\n          const ms = currentItemMs ?? Date.now();\n          const itemId = typeof item?.getId === 'function' ? item.getId() : (item as any)?.id;\n          const isHighlighted = highlightedMessageId === String(itemId);\n          const dayHeaderString = getDayHeaderString(ms);\n          // Show the \"New Messages\" indicator above a message if:\n          // - startFromUnreadMessages is enabled (to indicate unread messages start here), OR\n          // - showMarkAsUnreadOption is enabled (to show where a message was marked as unread)\n          // - OR navigatedFromSearch is true (when navigating from search to unread messages)\n          // - and this message matches the newMessageIndicatorId\n          // - and NOT in agentic mode (agentic chats don't support unread indicators)\n          const showNewMessageIndicator = !isAgenticUser && (startFromUnreadMessages || showMarkAsUnreadOption || navigatedFromSearch) && newMessageIndicatorId === String(item.getId());\n\n          return (\n            <View>\n              <MessageListItem\n                item={item}\n                index={index}\n                showSeparator={showSeparator}\n                isHighlighted={isHighlighted}\n                highlightAnimatedValue={highlightAnimatedValue}\n                theme={mergedTheme}\n                themeMode={themeMode}\n                timestamp={ms}\n                dayHeaderString={dayHeaderString}\n                RenderMessageItem={RenderMessageItem}\n                itemSeparator={itemSeparator}\n                staticStyles={staticStyles}\n                onLayout={handleItemLayout}\n                showNewMessageIndicator={showNewMessageIndicator}\n                NewMessageIndicatorView={NewMessageIndicatorView}\n                newMessageIndicatorStyle={newMessageIndicatorStyle}\n                newMessageIndicatorText={newMessageIndicatorText}\n              />\n            </View>\n          );\n        },\n        [\n          mergedTheme,\n          themeMode,\n          highlightedMessageId,\n          highlightAnimatedValue,\n          sentAtToMs,\n          shouldShowSeparator,\n          getDayHeaderString,\n          itemSeparator,\n          RenderMessageItem,\n          handleItemLayout,\n          newMessageIndicatorId,\n          NewMessageIndicatorView,\n          newMessageIndicatorStyle,\n          newMessageIndicatorText,\n          startFromUnreadMessages,\n          showMarkAsUnreadOption,\n          navigatedFromSearch,\n          isAgenticUser\n        ]\n      );\n\n\n      return (\n        <View style={mergedTheme.messageListStyles.containerStyle}>\n          {listState == \"loading\" && messagesList.length == 0 ? (\n            getLoadingStateView()\n          ) : listState == \"error\" ? (\n            getErrorStateView()\n          ) : (\n            messagesList.length == 0 ? (\n              getEmptyStateView()\n            ) : (\n              <View style={{ height: \"100%\", width: \"100%\" }}>\n                {HeaderView && (\n                  <View style={[{ top: 0 }]}>\n                    <HeaderView\n                      group={group}\n                      user={user}\n                      id={{\n                        guid: group && (group as any)[\"guid\"],\n                        uid: user && (user as any)[\"uid\"],\n                        parentMessageId: parentMessageId,\n                      }}\n                    />\n                  </View>\n                )}\n                {loadingMessages && (\n                  <View style={{ position: \"absolute\", alignSelf: \"center\" }}>\n                    <ActivityIndicator size='small' color={mergedTheme.color.primary} />\n                  </View>\n                )}\n\n                {/* Navigation loading for far message fetching and scroll operations */}\n                {navigationLoading && (getLoadingStateView())}\n                <FlatList\n                  showsVerticalScrollIndicator={false}\n                  ref={messageListRef}\n                  onMomentumScrollEnd={handleScrollInternal}\n                  onScroll={handleScrollInternal}\n                  inverted={true}\n                  maintainVisibleContentPosition={{\n                    minIndexForVisible: 0,\n                  }}\n                  onEndReachedThreshold={0.5}\n                  onEndReached={getPreviousMessages}\n                  onStartReached={fetchNextMessages}\n                  onStartReachedThreshold={0.5}\n                  scrollEventThrottle={16}\n                  keyboardShouldPersistTaps={(Platform.OS === \"ios\" ? \"handled\" : \"always\") as \"handled\" | \"always\"}\n                  data={messagesList}\n                  extraData={themeMode}\n                  keyExtractor={keyExtractor}\n                  renderItem={memoizedRenderItem}\n                  ListHeaderComponent={renderFooter}\n                  onScrollToIndexFailed={handleScrollToIndexFailed}\n                  viewabilityConfig={viewabilityConfig}\n                  onViewableItemsChanged={handleViewableItemsChanged}\n                  // Performance optimizations\n                  windowSize={10}\n                  removeClippedSubviews={Platform.OS === 'android'}\n                  maxToRenderPerBatch={5}\n                  updateCellsBatchingPeriod={100}\n                  initialNumToRender={15}\n                />\n\n\n                {CustomListHeader && <CustomListHeader />}\n                {ongoingCallView}\n                {FooterView && (\n                  <View style={[{ bottom: 0 }]}>\n                    <FooterView\n                      group={group}\n                      user={user}\n                      id={{\n                        guid: group && (group as any)[\"guid\"],\n                        uid: user && (user as any)[\"uid\"],\n                        parentMessageId: parentMessageId,\n                      }}\n                    />\n                  </View>\n                )}\n              </View>\n            )\n          )}\n\n          {!hideScrollToBottomButton && (\n            <Pressable\n              onPress={newMsgIndicatorPressed}\n              style={[mergedTheme.messageListStyles.scrollToBottomButtonStyle.containerStyle]}\n            >\n              {unreadCount > 0 && (\n                <CometChatBadge\n                  style={\n                    mergedTheme.messageListStyles.scrollToBottomButtonStyle.unreadCountBadgeStyle\n                  }\n                  count={unreadCount}\n                />\n              )}\n\n              <Icon\n                name='keyboard-arrow-down'\n                color={mergedTheme.messageListStyles.scrollToBottomButtonStyle.iconStyle.tintColor}\n                height={mergedTheme.messageListStyles.scrollToBottomButtonStyle.iconStyle.height}\n                width={mergedTheme.messageListStyles.scrollToBottomButtonStyle.iconStyle.width}\n                icon={mergedTheme.messageListStyles.scrollToBottomButtonStyle.icon}\n                imageStyle={mergedTheme.messageListStyles.scrollToBottomButtonStyle.iconStyle}\n                containerStyle={\n                  mergedTheme.messageListStyles.scrollToBottomButtonStyle.iconContainerStyle\n                }\n              ></Icon>\n            </Pressable>\n          )}\n\n          <MessageModals\n            showDeleteModal={showDeleteModal}\n            showReportDialog={showReportDialog}\n            deleteItem={deleteItem}\n            reportedMessageRef={reportedMessageRef}\n            theme={theme}\n            hideFlagRemarkField={hideFlagRemarkField}\n            t={t}\n            onDeleteCancel={() => {\n              deleteItem.current = undefined;\n              setShowDeleteModal(false);\n            }}\n            onDeleteConfirm={deleteMessage}\n            onReportCancel={() => {\n              setShowReportDialog(false);\n              reportedMessageRef.current = null;\n            }}\n            onReportSubmit={(payload) => {\n              setShowReportDialog(false);\n              reportedMessageRef.current = null;\n            }}\n          />\n\n          <MessageOptionsSheet\n            bottomSheetRef={bottomSheetRef}\n            isOpen={showMessageOptions.length > 0 || Boolean(ExtensionsComponent) || messageInfo}\n            showMessageOptions={showMessageOptions}\n            ExtensionsComponent={ExtensionsComponent}\n            messageInfo={messageInfo}\n            infoObject={infoObject}\n            selectedMessage={selectedMessage}\n            hideReactionOption={hideReactionOption}\n            quickReactionList={quickReactionList as [string, string?, string?, string?, string?]}\n            templatesMap={templatesMap}\n            mergedTheme={mergedTheme}\n            deleteItem={deleteItem}\n            pendingReportRef={pendingReportRef}\n            onClose={() => {\n              if (ExtensionsComponent) setExtensionsComponent(null);\n              setShowMessageOptions([]);\n              infoObject.current = null;\n              setMessageInfo(false);\n            }}\n            onReactionPress={reactToMessage}\n            onAddReactionPress={onAddReactionPress}\n            setShowDeleteModal={setShowDeleteModal}\n            setShowReportDialog={setShowReportDialog}\n            setShowMessageOptions={setShowMessageOptions}\n            setExtensionsComponent={setExtensionsComponent}\n            setMessageInfo={setMessageInfo}\n            setShowEmojiKeyboard={setShowEmojiKeyboard}\n          />\n\n          <ReactionModals\n            showEmojiKeyboard={showEmojiKeyboard}\n            showReactionList={showReactionList}\n            selectedMessage={selectedMessage}\n            selectedEmoji={selectedEmoji}\n            reactionsRequestBuilder={reactionsRequestBuilder}\n            onReactionListItemPress={onReactionListItemPressProp}\n            onEmojiSelect={reactToMessage}\n            onEmojiKeyboardClose={() => setShowEmojiKeyboard(false)}\n            onReactionListClose={() => {\n              setShowReactionList(false);\n              setSelectedEmoji(undefined);\n            }}\n            onReactionListEmpty={() => {\n              setShowReactionList(false);\n              setSelectedEmoji(undefined);\n            }}\n          />\n        </View>\n      );\n\n    }\n  )\n);\n\n\nconst staticStyles = StyleSheet.create({\n  container: {\n    flex: 1,\n    paddingHorizontal: 16,\n    paddingVertical: 8,\n    position: 'relative' as 'relative',\n  },\n  contentWrapper: {\n    position: 'relative' as 'relative',\n    zIndex: 2,\n  },\n  separatorContainer: {\n    alignItems: 'center' as 'center',\n    marginBottom: 8,\n  },\n  footerContainer: {\n    padding: 10,\n    alignItems: 'center' as 'center',\n  },\n  itemSeparator: {\n    height: 8,\n  },\n  loadingContainer: {\n    padding: 16,\n  },\n  listContainer: {\n    height: '100%',\n    width: '100%',\n  },\n  headerContainer: {\n    top: 0,\n  },\n  activityIndicatorContainer: {\n    position: 'absolute' as 'absolute',\n    alignSelf: 'center' as 'center',\n  },\n  flexOne: {\n    flex: 1,\n  },\n  emptyScrollView: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyContainer: {\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    padding: 24,\n  },\n  avatarContainer: {\n    width: 60,\n    height: 60,\n    borderRadius: 40,\n    marginBottom: 20,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  avatarImage: {\n    width: 60,\n    height: 60,\n    borderRadius: 40,\n  },\n  avatarText: {\n    fontSize: 32,\n    fontWeight: \"bold\",\n    textAlign: \"center\",\n    textAlignVertical: \"center\",\n  },\n})//30/dec/2025 6.48pm"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/Skeleton.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Path, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../theme\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonItem = () => {\n  const theme = useTheme();\n\n  const width = useRef(Math.floor(screenWidth / 2.5 + Math.random() * (screenWidth / 2.5))).current;\n  const alignSelf = useRef(\n    Math.floor(Math.random() * 2) == 0 ? (\"flex-end\" as const) : (\"flex-start\" as const)\n  ).current;\n\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: theme.conversationStyles.skeletonStyle.speed * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n  }, [animatedValue]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth, screenWidth],\n  });\n  return (\n    <View\n      style={{\n        overflow: \"hidden\",\n        borderRadius: 8,\n        width: width,\n        marginVertical: 12,\n        alignSelf: alignSelf,\n      }}\n    >\n      <Svg width={width} height={53} viewBox={`0 0 ${width} 53`} fill='none'>\n        <Path d={`M${width} 0H0v53h${width}V0z`} fill='url(#paint0)' />\n        <Defs>\n          <LinearGradient\n            id='paint0'\n            x1={0}\n            y1={26.5}\n            x2={width}\n            y2={26.5}\n            gradientUnits='userSpaceOnUse'\n          >\n            <Stop stopColor={theme.conversationStyles.skeletonStyle.linearGradientColors[0]} />\n            <Stop\n              offset={1}\n              stopColor={theme.conversationStyles.skeletonStyle.linearGradientColors[1]}\n            />\n          </LinearGradient>\n        </Defs>\n      </Svg>\n      <Animated.View\n        style={[\n          { transform: [{ translateX: shimmerTranslateX }] },\n          styles.animatedView,\n          {\n            backgroundColor: theme.conversationStyles.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.conversationStyles.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      ></Animated.View>\n    </View>\n  );\n};\n\nexport const MessageSkeleton = () => {\n  return (\n    <ScrollView showsVerticalScrollIndicator={false} scrollEnabled={false}>\n      {new Array(10).fill(0).map((_, i) => {\n        return <SkeletonItem key={i} />;\n      })}\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  animatedView: {\n    width: \"40%\",\n    top: 0,\n    bottom: 0,\n    position: \"absolute\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/components/MessageListItem.tsx",
    "content": "import React, { memo } from 'react';\nimport { View, Animated } from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatDateSeparator } from '../../shared/views/CometChatDateSeperator';\nimport { CometChatTheme } from '../../theme/type';\nimport { CometChatNewMessageIndicator, NewMessageIndicatorStyle } from '../../shared/views';\n\nconst SEPARATOR_HEIGHT = 40;\n\ninterface MessageListItemProps {\n  item: CometChat.BaseMessage;\n  index: number;\n  showSeparator: boolean;\n  isHighlighted: boolean;\n  highlightAnimatedValue: Animated.Value;\n  theme: CometChatTheme;\n  themeMode: string; \n  timestamp: number;\n  dayHeaderString: string | undefined;\n  RenderMessageItem: React.ComponentType<any>;\n  itemSeparator: () => React.ReactNode;\n  staticStyles: any;\n  onLayout?: (event: any, messageId: string) => void;\n  showNewMessageIndicator?: boolean;\n  NewMessageIndicatorView?: React.ComponentType<any>;\n  newMessageIndicatorStyle?: NewMessageIndicatorStyle;\n  newMessageIndicatorText?: string;\n}\n\nconst MessageListItemComponent: React.FC<MessageListItemProps> = ({\n  item,\n  index,\n  showSeparator,\n  isHighlighted,\n  highlightAnimatedValue,\n  theme,\n  themeMode,\n  timestamp,\n  dayHeaderString,\n  RenderMessageItem,\n  itemSeparator,\n  staticStyles,\n  onLayout,\n  showNewMessageIndicator,\n  NewMessageIndicatorView,\n  newMessageIndicatorStyle,\n  newMessageIndicatorText,\n}) => {\n\n  // Handle layout event to get item position\n  const handleLayout = (event: any) => {\n    if (onLayout) {\n      onLayout(event, String(item.getId()));\n    }\n  };\n\n  return (\n    <View style={staticStyles.container} onLayout={handleLayout}>\n      {isHighlighted && (\n        <Animated.View\n          style={{\n            backgroundColor: highlightAnimatedValue.interpolate({\n              inputRange: [0, 1],\n              outputRange: ['transparent', String(theme.color.extendedPrimary200)],\n            }),\n            position: 'absolute',\n            left: 0,\n            right: 0,\n            top: showSeparator ? SEPARATOR_HEIGHT : 0,\n            bottom: 0,\n            zIndex: 1,\n          }}\n        />\n      )}\n      <View style={staticStyles.contentWrapper}>\n        {showSeparator && (\n          <View style={staticStyles.separatorContainer}>\n            <CometChatDateSeparator\n              timeStamp={timestamp}\n              pattern=\"dayDateFormat\"\n              customDateString={dayHeaderString}\n              style={theme.messageListStyles.dateSeparatorStyle}\n            />\n          </View>\n        )}\n        {showNewMessageIndicator && (\n          <CometChatNewMessageIndicator\n            NewMessageIndicatorView={NewMessageIndicatorView}\n            style={newMessageIndicatorStyle}\n            text={newMessageIndicatorText}\n          />\n        )}\n        <RenderMessageItem item={item} theme={theme} idx={index} />\n        {itemSeparator()}\n      </View>\n    </View>\n  );\n};\n\n// Memoize with custom comparison\nexport const MessageListItem = memo(MessageListItemComponent, (prevProps, nextProps) => {\n  // Return true if props are equal (skip re-render)\n  // Return false if props changed (re-render needed)\n\n  const itemUnchanged = prevProps.item === nextProps.item;\n\n  // Theme mode change should trigger re-render for theme updates\n  const themeModeUnchanged = prevProps.themeMode === nextProps.themeMode;\n\n  const visualsUnchanged =\n    prevProps.isHighlighted === nextProps.isHighlighted &&\n    prevProps.showSeparator === nextProps.showSeparator &&\n    prevProps.timestamp === nextProps.timestamp &&\n    prevProps.dayHeaderString === nextProps.dayHeaderString &&\n    prevProps.showNewMessageIndicator === nextProps.showNewMessageIndicator &&\n    prevProps.newMessageIndicatorText === nextProps.newMessageIndicatorText;\n\n  const styleUnchanged =\n    prevProps.staticStyles === nextProps.staticStyles &&\n    prevProps.newMessageIndicatorStyle === nextProps.newMessageIndicatorStyle;\n\n  const functionsUnchanged =\n    prevProps.itemSeparator === nextProps.itemSeparator &&\n    prevProps.highlightAnimatedValue === nextProps.highlightAnimatedValue &&\n    prevProps.onLayout === nextProps.onLayout;\n\n  return itemUnchanged && themeModeUnchanged && visualsUnchanged && styleUnchanged && functionsUnchanged;\n});\n\nMessageListItem.displayName = 'MessageListItem';\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/components/MessageModals.tsx",
    "content": "import React from 'react';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { Icon } from '../../shared/icons/Icon';\nimport { CometChatConfirmDialog, CometChatReportDialog } from '../../shared/views';\nimport { CometChatTheme } from '../../theme/type';\n\ninterface MessageModalsProps {\n  showDeleteModal: boolean;\n  showReportDialog: boolean;\n  deleteItem: React.MutableRefObject<CometChat.BaseMessage | undefined>;\n  reportedMessageRef: React.MutableRefObject<CometChat.BaseMessage | null>;\n  theme: CometChatTheme;\n  hideFlagRemarkField?: boolean;\n  t: (key: string) => string;\n  onDeleteCancel: () => void;\n  onDeleteConfirm: (message: CometChat.BaseMessage) => void;\n  onReportCancel: () => void;\n  onReportSubmit: (payload: any) => void;\n}\n\nexport const MessageModals: React.FC<MessageModalsProps> = ({\n  showDeleteModal,\n  showReportDialog,\n  deleteItem,\n  reportedMessageRef,\n  theme,\n  hideFlagRemarkField,\n  t,\n  onDeleteCancel,\n  onDeleteConfirm,\n  onReportCancel,\n  onReportSubmit,\n}) => {\n  return (\n    <>\n      <CometChatConfirmDialog\n        titleText={t('DELETE_THIS_MESSAGE')}\n        icon={<Icon name=\"delete\" size={theme.spacing.spacing.s12} color={theme.color.error} />}\n        cancelButtonText={t('CANCEL')}\n        confirmButtonText={t('DELETE')}\n        messageText={t('DELETE_MESSAGE_CONFIRM')}\n        isOpen={showDeleteModal}\n        onCancel={onDeleteCancel}\n        onConfirm={() => {\n          if (deleteItem.current) {\n            onDeleteConfirm(deleteItem.current);\n          }\n        }}\n      />\n\n      <CometChatReportDialog\n        isOpen={showReportDialog}\n        message={reportedMessageRef.current || undefined}\n        onCancel={onReportCancel}\n        hideFlagRemarkField={hideFlagRemarkField}\n        onReport={onReportSubmit}\n      />\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/components/MessageOptionsSheet.tsx",
    "content": "import React from 'react';\nimport { View, Dimensions, Platform } from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatBottomSheet } from '../../shared/views';\nimport { CometChatMessageInformation } from '../../CometChatMessageInformation/CometChatMessageInformation';\nimport { CometChatQuickReactions } from '../../shared/views/CometChatQuickReactions';\nimport { CometChatActionSheet } from '../../shared';\nimport { CometChatTheme } from '../../theme/type';\nimport { CometChatMessageTemplate } from '../../shared/modals/CometChatMessageTemplate';\nimport { getModerationStatus } from '../../shared/utils/MessageUtils';\n\ninterface MessageOptionsSheetProps {\n  bottomSheetRef: React.RefObject<any>;\n  isOpen: boolean;\n  showMessageOptions: any[];\n  ExtensionsComponent: React.JSX.Element | null;\n  messageInfo: boolean;\n  infoObject: React.MutableRefObject<CometChat.BaseMessage | null | undefined>;\n  selectedMessage: CometChat.BaseMessage | null;\n  hideReactionOption: boolean;\n  quickReactionList?: [string, string?, string?, string?, string?];\n  templatesMap: Map<string, CometChatMessageTemplate>;\n  mergedTheme: CometChatTheme;\n  deleteItem: React.MutableRefObject<CometChat.BaseMessage | undefined>;\n  pendingReportRef: React.MutableRefObject<boolean>;\n  onClose: () => void;\n  onDismiss?: () => void;\n  onReactionPress: (emoji: string, message?: CometChat.BaseMessage) => void;\n  onAddReactionPress?: () => void;\n  setShowDeleteModal: (show: boolean) => void;\n  setShowReportDialog: (show: boolean) => void;\n  setShowMessageOptions: (options: any[]) => void;\n  setExtensionsComponent: (component: React.JSX.Element | null) => void;\n  setMessageInfo: (show: boolean) => void;\n  setShowEmojiKeyboard: (show: boolean) => void;\n}\n\nexport const MessageOptionsSheet: React.FC<MessageOptionsSheetProps> = ({\n  bottomSheetRef,\n  isOpen,\n  showMessageOptions,\n  ExtensionsComponent,\n  messageInfo,\n  infoObject,\n  selectedMessage,\n  hideReactionOption,\n  quickReactionList,\n  templatesMap,\n  mergedTheme,\n  deleteItem,\n  pendingReportRef,\n  onClose,\n  onDismiss,\n  onReactionPress,\n  onAddReactionPress,\n  setShowDeleteModal,\n  setShowReportDialog,\n  setShowMessageOptions,\n  setMessageInfo,\n  setShowEmojiKeyboard,\n}) => {\n  return (\n    <CometChatBottomSheet\n      ref={bottomSheetRef}\n      onClose={onClose}\n      onDismiss={() => {\n        // iOS: Open any deferred modals only after dismiss animation completes\n        if (Platform.OS === 'ios') {\n          if (deleteItem.current) {\n            setShowDeleteModal(true);\n          }\n          if (pendingReportRef.current) {\n            pendingReportRef.current = false;\n            setShowReportDialog(true);\n          }\n        }\n        onDismiss?.();\n      }}\n      isOpen={isOpen}\n      style={{\n        paddingHorizontal: 0,\n        maxHeight: messageInfo\n          ? Dimensions.get('window').height * 0.9\n          : Dimensions.get('window').height * 0.52,\n        minHeight: Dimensions.get('window').height * 0.5,\n      }}\n    >\n      {ExtensionsComponent ? (\n        ExtensionsComponent\n      ) : messageInfo && infoObject.current ? (\n        <CometChatMessageInformation\n          message={infoObject.current}\n          template={templatesMap.get(\n            `${infoObject.current?.getCategory()}_${infoObject.current?.getType()}`\n          )}\n          onBack={() => {\n            infoObject.current = null;\n            setMessageInfo(false);\n          }}\n          style={mergedTheme?.messageListStyles?.messageInformationStyles}\n        />\n      ) : (\n        <View style={{ flex: 1 }}>\n          {/* Show quick reactions for disapproved messages */}\n          {!hideReactionOption && getModerationStatus(selectedMessage) !== 'disapproved' && (\n            <CometChatQuickReactions\n              quickReactions={quickReactionList}\n              onReactionPress={onReactionPress}\n              onAddReactionPress={\n                onAddReactionPress ??\n                (() => {\n                  setShowMessageOptions([]);\n                  setTimeout(() => {\n                    setShowEmojiKeyboard(true);\n                  }, 200);\n                })\n              }\n              style={mergedTheme.quickReactionStyle}\n            />\n          )}\n\n          <CometChatActionSheet\n            actions={showMessageOptions}\n            style={mergedTheme.messageListStyles.messageOptionsStyles}\n          />\n        </View>\n      )}\n    </CometChatBottomSheet>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/components/ReactionModals.tsx",
    "content": "import React from 'react';\nimport { Dimensions } from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { CometChatBottomSheet } from '../../shared/views';\nimport { CometChatEmojiKeyboard } from '../../shared/views/CometChatEmojiKeyboard';\nimport { CometChatReactionList } from '../../shared/views/CometChatReactionList';\n\ninterface ReactionModalsProps {\n  showEmojiKeyboard: boolean;\n  showReactionList: boolean;\n  selectedMessage: CometChat.BaseMessage | null;\n  selectedEmoji: string | undefined;\n  reactionsRequestBuilder?: CometChat.ReactionsRequestBuilder;\n  onReactionListItemPress?: (reaction: CometChat.Reaction, message: CometChat.BaseMessage) => void;\n  onEmojiSelect: (emoji: string, message: CometChat.BaseMessage) => void;\n  onEmojiKeyboardClose: () => void;\n  onReactionListClose: () => void;\n  onReactionListEmpty: () => void;\n}\n\nexport const ReactionModals: React.FC<ReactionModalsProps> = ({\n  showEmojiKeyboard,\n  showReactionList,\n  selectedMessage,\n  selectedEmoji,\n  reactionsRequestBuilder,\n  onReactionListItemPress,\n  onEmojiSelect,\n  onEmojiKeyboardClose,\n  onReactionListClose,\n  onReactionListEmpty,\n}) => {\n  return (\n    <>\n      <CometChatBottomSheet\n        isOpen={showEmojiKeyboard}\n        onClose={onEmojiKeyboardClose}\n        style={{\n          maxHeight: Dimensions.get('window').height * 0.49,\n        }}\n      >\n        <CometChatEmojiKeyboard\n          onClick={(item) => {\n            onEmojiKeyboardClose();\n            if (selectedMessage) {\n              onEmojiSelect(item, selectedMessage);\n            }\n          }}\n        />\n      </CometChatBottomSheet>\n\n      <CometChatBottomSheet\n        isOpen={showReactionList}\n        onClose={onReactionListClose}\n        style={{\n          maxHeight: Dimensions.get('window').height * 0.7,\n          minHeight: Dimensions.get('window').height * 0.3,\n        }}\n      >\n        <CometChatReactionList\n          message={selectedMessage!}\n          selectedReaction={selectedEmoji}\n          onPress={onReactionListItemPress}\n          reactionsRequestBuilder={reactionsRequestBuilder}\n          onListEmpty={onReactionListEmpty}\n        />\n      </CometChatBottomSheet>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/index.ts",
    "content": "import {\n  CometChatMessageList,\n  CometChatMessageListActionsInterface,\n  CometChatMessageListProps,\n} from \"./CometChatMessageList\";\n\nexport {\n  CometChatMessageList,\n};\n\nexport type {\n  CometChatMessageListActionsInterface,\n  CometChatMessageListProps\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/resources/index.ts",
    "content": "import backIcon from \"./back.png\";\nimport closeIcon from \"./close.png\";\nimport downArrowIcon from \"./downArrowIcon.png\";\nimport LeftArrowCurve from \"./leftarrowcurve.png\";\nimport RightArrowCurve from \"./rightarrowcurve.png\";\nimport rightArrowIcon from \"./rightArrowIcon.png\";\nimport loadingIcon from \"./spineer.png\";\n\nexport {\n  LeftArrowCurve,\n  RightArrowCurve,\n  backIcon,\n  closeIcon,\n  downArrowIcon,\n  loadingIcon,\n  rightArrowIcon,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatMessageList/style.ts",
    "content": "import { Platform } from \"react-native\";\nimport {\n  getAudioBubbleStyleLight,\n  getAudioBubbleStyleDark,\n} from \"../shared/views/CometChatAudioBubble\";\nimport {\n  getFileBubbleStyleLight,\n  getFileBubbleStyleDark,\n} from \"../shared/views/CometChatFileBubble\";\nimport { ActionSheetStyle, CometChatTheme } from \"../theme/type\";\nimport { getStickerStyleLight, getStickerStyleDark } from \"../extensions/Stickers/style\";\nimport {\n  getCollabBubbleStyleDark,\n  getCollabBubbleStyleLight,\n} from \"../extensions/CollaborativeBubble/style\";\nimport {\n  getVideoBubbleStylesDark,\n  getVideoBubbleStylesLight,\n} from \"../shared/views/CometChatVideoBubble/style\";\nimport { getPollBubbleStyleDark, getPollBubbleStyleLight } from \"../extensions/Polls/style\";\nimport {\n  getReactionsStyleDark,\n  getReactionsStyleLight,\n} from \"../shared/views/CometChatReactions/style\";\nimport {\n  getLinkPreviewBubbleStyleDark,\n  getLinkPreviewBubbleStyleLight,\n} from \"../extensions/LinkPreview/style\";\nimport {\n  getGroupCallBubbleStyle,\n  getCallActionBubbleStyle,\n} from \"../calls/CometChatCallBubble/styles\";\nimport { getDeletedBubbleStyle } from \"../shared/views/CometChatDeletedBubble\";\nimport {\n  getMessageInormationListStyleDark,\n  getMessageInormationListStyleLight,\n} from \"../CometChatMessageInformation/styles\";\n\nconst groupActionBubbleStyles = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"groupActionBubbleStyles\"] => {\n  return {\n    containerStyle: {\n      paddingHorizontal: spacing.padding.p3,\n      paddingVertical: spacing.padding.p1,\n      borderRadius: spacing.radius.max,\n      backgroundColor: color.background2,\n      borderWidth: 1,\n      borderColor: color.borderDefault,\n    },\n    textStyle: {\n      ...typography.caption1.regular,\n      color: color.textSecondary,\n      textAlign: \"center\",\n    },\n  };\n};\n\nconst getMessageOptionsStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ActionSheetStyle => {\n  return {\n    optionsItemStyle: {\n      containerStyle: {\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p6,\n        backgroundColor: color.background1,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 5,\n      },\n      titleStyle: {\n        ...typography.body.regular,\n        color: color.textPrimary,\n      },\n      iconStyle: {\n        height: 24,\n        width: 24,\n      },\n      iconContainerStyle: {},\n    },\n  };\n};\n\nexport const getMessageListStylesLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"messageListStyles\"] => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      flex: 1,\n    },\n    scrollToBottomButtonStyle: {\n      containerStyle: {\n        position: \"absolute\",\n        zIndex: 1,\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p3,\n        flexDirection: \"column\",\n        alignItems: \"center\",\n        backgroundColor: color.background1,\n        right: 10,\n        bottom: 2,\n        gap: spacing.padding.p1,\n        borderRadius: spacing.radius.max,\n        ...(Platform.OS == \"ios\"\n          ? {\n              shadowOffset: {\n                width: 0,\n                height: 12,\n              },\n              shadowOpacity: 0.2,\n              shadowRadius: 16,\n            }\n          : {\n              elevation: 4,\n            }),\n      },\n      iconContainerStyle: {},\n      iconStyle: {\n        height: 24,\n        width: 24,\n        tintColor: color.iconSecondary,\n      },\n      unreadCountBadgeStyle: {\n        containerStyle: {\n          height: 20,\n          minWidth: 20,\n          maxWidth: 40,\n          backgroundColor: color.primary,\n          borderRadius: spacing.radius.r5,\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p1,\n        },\n        textStyle: {\n          ...typography.caption1.regular,\n          textAlign: \"center\",\n          color: color.primaryButtonIcon,\n        },\n      },\n    },\n    emptyStateStyle: {\n      containerStyle: {\n        backgroundColor: color.background2,\n        marginTop: spacing.margin.m0,\n        height: \"100%\",\n        width: \"100%\",\n      },\n    },\n    errorStateStyle: {\n      containerStyle: {\n        backgroundColor: color.background2,\n        marginTop: spacing.margin.m0,\n        height: \"100%\",\n        width: \"100%\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    },\n    dateSeparatorStyle: {\n      containerStyle: {\n        borderColor: color.borderDark,\n        backgroundColor: color.background2,\n        ...(Platform.OS == \"ios\"\n          ? {\n              shadowOffset: {\n                width: 0,\n                height: 12,\n              },\n              shadowOpacity: 0.2,\n              shadowRadius: 30,\n            }\n          : {\n              elevation: 2,\n            }),\n      },\n      textStyle: {\n        textAlign: \"center\",\n      },\n    },\n    incomingMessageBubbleStyles: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        backgroundColor: color.receiveBubbleBackground,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        minWidth: 90,\n      },\n      dateStyles: {\n        containerStyle: {\n          paddingTop: spacing.padding.p2,\n          paddingBottom: spacing.padding.p2,\n          paddingLeft: spacing.padding.p1,\n        },\n        textStyle: {\n          color: color.receiveBubbleTimestamp,\n          ...typography.caption2.regular,\n          textAlign: \"right\",\n          textTransform: \"lowercase\",\n        },\n      },\n      textBubbleStyles: {\n        textStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n          //lineHeight: 16.8,\n        },\n        translatedTextStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n          lineHeight: 16.8,\n        },\n        translatedTextContainerStyle: {\n          marginBottom: spacing.margin.m1,\n        },\n        translatedTextDividerStyle: {\n          height: 0.8,\n          backgroundColor: color.iconSecondary,\n          marginVertical: spacing.margin.m3,\n        },\n        mentionsStyle: {\n          textStyle: {\n            ...typography.body.bold,\n            color: color.receiveBubbleTextHighlight,\n          },\n          selfTextStyle: {\n            ...typography.body.bold,\n            color: color.warning,\n          },\n        },\n      },\n      imageBubbleStyles: {\n        containerStyle: {\n          padding: spacing.padding.p1,\n          paddingBottom: spacing.padding.p0,\n          borderRadius: spacing.radius.r3,\n          alignSelf: \"flex-start\",\n        },\n        imageStyle: {\n          borderRadius: spacing.radius.r2,\n          backgroundColor: color.background3,\n          height: 232,\n          width: 232,\n        },\n        dateReceiptContainerStyle: {\n          paddingRight: spacing.padding.p1,\n        },\n      },\n      assistantBubbleStyles: {\n        containerStyle: {\n          backgroundColor: \"transparent\",\n          borderRadius: spacing.radius.r3,\n          minWidth: 90,\n          alignSelf: \"flex-start\",\n          marginTop: -16,\n        },\n        textStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n        },\n        placeholderTextStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n          opacity: 0.6,\n        },\n        copyButtonStyle: {\n          backgroundColor: color.primary,\n          padding: spacing.padding.p1,\n          borderRadius: spacing.radius.r1,\n        },\n        errorContainerStyle: {\n          backgroundColor: color.error,\n          padding: spacing.padding.p2,\n          borderRadius: spacing.radius.r2,\n          marginTop: spacing.margin.m1,\n        },\n        errorTextStyle: {\n          color: color.background1,\n          fontFamily: typography.fontFamily,\n          ...typography.caption1.regular,\n        },\n      },\n      audioBubbleStyles: getAudioBubbleStyleLight(color, spacing, typography)\n        .incomingAudioBubbleStyle,\n      fileBubbleStyles: getFileBubbleStyleLight(color, spacing, typography).incomingFileBubbleStyle,\n      threadedMessageStyles: {\n        containerStyle: {\n          flexDirection: \"row\",\n          justifyContent: \"flex-start\",\n          alignItems: \"center\",\n          gap: spacing.spacing.s1,\n          marginTop: spacing.spacing.s1,\n          paddingVertical: spacing.spacing.s0_5,\n          paddingHorizontal: spacing.spacing.s1,\n        },\n        indicatorTextStyle: {\n          ...typography.caption1.regular,\n          textAlign: \"right\",\n          color: color.neutral900,\n          lineHeight: 12,\n          paddingTop: spacing.padding.p0_5,\n        },\n        iconStyle: {\n          height: 16,\n          width: 16,\n        },\n        unreadCountStyle: {\n          containerStyle: {\n            height: 16,\n            minWidth: 16,\n            maxWidth: 40,\n            flexDirection: \"column\",\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            gap: 10,\n            borderRadius: spacing.radius.r5,\n            backgroundColor: color.primary,\n            paddingTop: 1,\n            paddingRight: spacing.padding.p1,\n            paddingBottom: 3,\n            paddingLeft: spacing.padding.p1,\n          },\n          textStyle: {\n            ...typography.caption2.regular,\n            color: color.staticWhite,\n          },\n        },\n      },\n      avatarStyle: {\n        containerStyle: {\n          height: 32,\n          width: 32,\n        },\n        textStyle: {\n          textAlign: \"center\",\n          textAlignVertical: \"center\",\n          fontSize: typography.heading4.bold.fontSize,\n          color: color.primaryButtonIcon,\n          fontFamily: typography.fontFamily,\n          ...typography.heading4.bold,\n        },\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n        },\n      },\n      senderNameTextStyles: {\n        color: color.receiveBubbleTextHighlight,\n        ...typography.caption1.regular,\n      },\n      stickerBubbleStyles: getStickerStyleLight(color, spacing, typography),\n      collaborativeBubbleStyles: getCollabBubbleStyleLight(color, spacing, typography)\n        .incomingBubbleStyle,\n      meetCallBubbleStyles: getGroupCallBubbleStyle(color, spacing, typography).incomingBubbleStyle,\n      videoBubbleStyles: getVideoBubbleStylesLight(color, spacing, typography).incomingBubbleStyle,\n      pollBubbleStyles: getPollBubbleStyleLight(color, spacing, typography).incomingBubbleStyle,\n      reactionStyles: getReactionsStyleLight(color, spacing, typography).incomingBubbleStyle,\n      linkPreviewBubbleStyles: getLinkPreviewBubbleStyleLight(color, spacing, typography)\n        .incomingBubbleStyle,\n      deletedBubbleStyles: getDeletedBubbleStyle(color, spacing, typography).incomingBubbleStyle,\n    },\n    outgoingMessageBubbleStyles: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        backgroundColor: color.sendBubbleBackground,\n        borderRadius: spacing.radius.r3,\n        minWidth: 90,\n      },\n      moderationStyle: {\n        containerStyle: {\n          backgroundColor: \"#F9EAEF\",\n          paddingVertical: 6,\n          borderBottomLeftRadius: 8,\n          borderBottomRightRadius: 8,\n          borderTopLeftRadius: 0,\n          borderTopRightRadius: 0,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n        },\n        textStyle: {\n          color: color?.error,\n          ...typography?.body?.regular,\n          flexShrink: 1,\n          paddingHorizontal: 2,\n          alignSelf: \"center\",\n        },\n        iconTintColor: color?.error,\n      },\n      dateStyles: {\n        containerStyle: {\n          paddingTop: spacing.padding.p2,\n          paddingBottom: spacing.padding.p2,\n          paddingHorizontal: spacing.padding.p1,\n        },\n        textStyle: {\n          color: color.sendBubbleTimestamp,\n          ...typography.caption2.regular,\n          textAlign: \"right\",\n          textTransform: \"lowercase\",\n        },\n      },\n      receiptStyles: {\n        sentIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.iconSecondary,\n        },\n        waitIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.iconSecondary,\n        },\n        deliveredIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.iconSecondary,\n        },\n        readIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.success,\n        },\n        errorIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.error,\n        },\n      },\n      textBubbleStyles: {\n        textStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.sendBubbleText,\n          ...typography.body.regular,\n          //lineHeight: 16.8,\n        },\n        translatedTextStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.sendBubbleText,\n          ...typography.body.regular,\n          lineHeight: 16.8,\n        },\n        translatedTextContainerStyle: {\n          marginBottom: spacing.margin.m1,\n        },\n        translatedTextDividerStyle: {\n          height: 0.8,\n          backgroundColor: color.iconSecondary,\n          marginVertical: spacing.margin.m3,\n        },\n        mentionsStyle: {\n          textStyle: {\n            ...typography.body.bold,\n            color: color.sendBubbleTextHighlight,\n          },\n          selfTextStyle: {\n            ...typography.body.bold,\n            color: color.warning,\n          },\n        },\n      },\n      imageBubbleStyles: {\n        containerStyle: {\n          padding: spacing.padding.p1,\n          paddingBottom: spacing.padding.p0,\n          borderRadius: spacing.radius.r3,\n          overflow: \"hidden\",\n        },\n        imageStyle: {\n          borderRadius: spacing.radius.r2,\n          backgroundColor: color.background3,\n          height: 232,\n          width: '100%',\n          minWidth: 232,\n        },\n        dateReceiptContainerStyle: {\n          paddingRight: spacing.padding.p1,\n        },\n      },\n      audioBubbleStyles: getAudioBubbleStyleLight(color, spacing, typography)\n        .outgoingAudioBubbleStyle,\n      fileBubbleStyles: getFileBubbleStyleLight(color, spacing, typography).outgoingFileBubbleStyle,\n      threadedMessageStyles: {\n        containerStyle: {\n          flexDirection: \"row\",\n          justifyContent: \"flex-end\",\n          alignItems: \"center\",\n          gap: spacing.spacing.s1,\n          marginTop: spacing.spacing.s1,\n          paddingVertical: spacing.spacing.s0_5,\n          paddingHorizontal: spacing.spacing.s1,\n        },\n        indicatorTextStyle: {\n          ...typography.caption1.regular,\n          textAlign: \"right\",\n          color: color.neutral900,\n          lineHeight: 12,\n          paddingTop: spacing.padding.p0_5,\n        },\n        iconStyle: {\n          height: 16,\n          width: 16,\n        },\n        unreadCountStyle: {\n          containerStyle: {\n            height: 16,\n            minWidth: 16,\n            maxWidth: 40,\n            flexDirection: \"column\",\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            gap: 10,\n            borderRadius: spacing.radius.r5,\n            backgroundColor: color.primary,\n            paddingTop: 1,\n            paddingRight: spacing.padding.p1,\n            paddingBottom: 3,\n            paddingLeft: spacing.padding.p1,\n          },\n          textStyle: {\n            ...typography.caption2.regular,\n            color: color.staticWhite,\n          },\n        },\n      },\n      avatarStyle: {\n        containerStyle: {\n          height: 32,\n          width: 32,\n        },\n        textStyle: {\n          textAlign: \"center\",\n          textAlignVertical: \"center\",\n          fontSize: typography.heading4.bold.fontSize,\n          color: color.primaryButtonIcon,\n          fontFamily: typography.fontFamily,\n          ...typography.heading4.bold,\n        },\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n        },\n      },\n      senderNameTextStyles: {\n        color: color.receiveBubbleTextHighlight,\n        ...typography.caption1.regular,\n      },\n      stickerBubbleStyles: getStickerStyleLight(color, spacing, typography),\n      collaborativeBubbleStyles: getCollabBubbleStyleLight(color, spacing, typography)\n        .outgoingBubbleStyle,\n      meetCallBubbleStyles: getGroupCallBubbleStyle(color, spacing, typography).outgoingBubbleStyle,\n      videoBubbleStyles: getVideoBubbleStylesLight(color, spacing, typography).outgoingBubbleStyle,\n      pollBubbleStyles: getPollBubbleStyleLight(color, spacing, typography).outgoingBubbleStyle,\n      reactionStyles: getReactionsStyleLight(color, spacing, typography).outgoingBubbleStyle,\n      linkPreviewBubbleStyles: getLinkPreviewBubbleStyleLight(color, spacing, typography)\n        .outgoingBubbleStyle,\n      deletedBubbleStyles: getDeletedBubbleStyle(color, spacing, typography).outgoingBubbleStyle,\n    },\n    groupActionBubbleStyles: groupActionBubbleStyles(color, spacing, typography),\n    messageInformationStyles: getMessageInormationListStyleLight(color, spacing, typography),\n    messageOptionsStyles: getMessageOptionsStyle(color, spacing, typography),\n    callActionBubbleStyles: getCallActionBubbleStyle(color, spacing, typography),\n  };\n};\n\nexport const getMessageListStylesDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"messageListStyles\"] => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      flex: 1,\n    },\n    scrollToBottomButtonStyle: {\n      containerStyle: {\n        position: \"absolute\",\n        zIndex: 1,\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p3,\n        flexDirection: \"column\",\n        alignItems: \"center\",\n        backgroundColor: color.background3,\n        right: 10,\n        bottom: 2,\n        gap: spacing.padding.p1,\n        borderRadius: spacing.radius.max,\n        borderWidth: 1,\n        borderColor: color.borderDefault,\n        ...(Platform.OS == \"ios\"\n          ? {\n              shadowOffset: {\n                width: 0,\n                height: 12,\n              },\n              shadowOpacity: 0.2,\n              shadowRadius: 16,\n            }\n          : {\n              elevation: 4,\n            }),\n      },\n      iconContainerStyle: {},\n      iconStyle: {\n        height: 24,\n        width: 24,\n        tintColor: color.iconSecondary,\n      },\n      unreadCountBadgeStyle: {\n        containerStyle: {\n          height: 20,\n          minWidth: 20,\n          maxWidth: 40,\n          backgroundColor: color.primary,\n          borderRadius: spacing.radius.r5,\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p1,\n        },\n        textStyle: {\n          ...typography.caption1.regular,\n          textAlign: \"center\",\n          color: color.primaryButtonIcon,\n        },\n      },\n    },\n    emptyStateStyle: {\n      containerStyle: {\n        backgroundColor: color.background2,\n        marginTop: spacing.margin.m0,\n        height: \"100%\",\n        width: \"100%\",\n      },\n    },\n    errorStateStyle: {\n      containerStyle: {\n        backgroundColor: color.background2,\n        marginTop: spacing.margin.m0,\n        height: \"100%\",\n        width: \"100%\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    },\n    dateSeparatorStyle: {\n      containerStyle: {\n        borderColor: color.borderDark,\n        backgroundColor: color.staticBlack,\n        ...(Platform.OS == \"ios\"\n          ? {\n              shadowOffset: {\n                width: 0,\n                height: 12,\n              },\n              shadowOpacity: 0.2,\n              shadowRadius: 30,\n            }\n          : {\n              elevation: 2,\n            }),\n      },\n      textStyle: {\n        textAlign: \"center\",\n      },\n    },\n    incomingMessageBubbleStyles: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        backgroundColor: color.receiveBubbleBackground,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        minWidth: 90,\n      },\n      dateStyles: {\n        containerStyle: {\n          paddingTop: spacing.padding.p2,\n          paddingBottom: spacing.padding.p2,\n          paddingLeft: spacing.padding.p1,\n        },\n        textStyle: {\n          color: color.receiveBubbleTimestamp,\n          ...typography.caption2.regular,\n          textAlign: \"right\",\n          textTransform: \"lowercase\",\n        },\n      },\n      textBubbleStyles: {\n        textStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n          //lineHeight: 16.8,\n        },\n        translatedTextStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n          lineHeight: 16.8,\n        },\n        translatedTextContainerStyle: {\n          marginBottom: spacing.margin.m1,\n        },\n        translatedTextDividerStyle: {\n          height: 0.8,\n          backgroundColor: color.iconSecondary,\n          marginVertical: spacing.margin.m3,\n        },\n        mentionsStyle: {\n          textStyle: {\n            ...typography.body.bold,\n            color: color.receiveBubbleTextHighlight,\n          },\n          selfTextStyle: {\n            ...typography.body.bold,\n            color: color.warning,\n          },\n        },\n      },\n      imageBubbleStyles: {\n        containerStyle: {\n          padding: spacing.padding.p1,\n          paddingBottom: spacing.padding.p0,\n          borderRadius: spacing.radius.r3,\n          alignSelf: \"flex-start\",\n        },\n        imageStyle: {\n          borderRadius: spacing.radius.r2,\n          backgroundColor: color.background3,\n          height: 232,\n          width: 232,\n        },\n        dateReceiptContainerStyle: {\n          paddingRight: spacing.padding.p1,\n        },\n      },\n      assistantBubbleStyles: {\n        containerStyle: {\n          backgroundColor: \"transparent\", \n          borderRadius: spacing.radius.r3,\n          minWidth: 90,\n          alignSelf: \"flex-start\",\n          marginTop: -14,\n        },\n        textStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n        },\n        placeholderTextStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.receiveBubbleText,\n          ...typography.body.regular,\n          opacity: 0.6,\n        },\n        copyButtonStyle: {\n          backgroundColor: color.primary,\n          padding: spacing.padding.p1,\n          borderRadius: spacing.radius.r1,\n        },\n        errorContainerStyle: {\n          backgroundColor: color.error,\n          padding: spacing.padding.p2,\n          borderRadius: spacing.radius.r2,\n          marginTop: spacing.margin.m1,\n        },\n        errorTextStyle: {\n          color: color.background1,\n          fontFamily: typography.fontFamily,\n          ...typography.caption1.regular,\n        },\n      },\n      audioBubbleStyles: getAudioBubbleStyleDark(color, spacing, typography)\n        .incomingAudioBubbleStyle,\n      fileBubbleStyles: getFileBubbleStyleDark(color, spacing, typography).incomingFileBubbleStyle,\n      stickerBubbleStyles: getStickerStyleDark(color, spacing, typography),\n      collaborativeBubbleStyles: getCollabBubbleStyleDark(color, spacing, typography)\n        .incomingBubbleStyle,\n      meetCallBubbleStyles: getGroupCallBubbleStyle(color, spacing, typography).incomingBubbleStyle,\n      videoBubbleStyles: getVideoBubbleStylesDark(color, spacing, typography).incomingBubbleStyle,\n      pollBubbleStyles: getPollBubbleStyleDark(color, spacing, typography).incomingBubbleStyle,\n      reactionStyles: getReactionsStyleDark(color, spacing, typography).incomingBubbleStyle,\n      linkPreviewBubbleStyles: getLinkPreviewBubbleStyleDark(color, spacing, typography)\n        .incomingBubbleStyle,\n      threadedMessageStyles: {\n        containerStyle: {\n          flexDirection: \"row\",\n          justifyContent: \"flex-start\",\n          alignItems: \"center\",\n          gap: spacing.spacing.s1,\n          marginTop: spacing.spacing.s1,\n          paddingVertical: spacing.spacing.s0_5,\n          paddingHorizontal: spacing.spacing.s1,\n        },\n        indicatorTextStyle: {\n          ...typography.caption1.regular,\n          textAlign: \"right\",\n          color: color.staticWhite,\n          lineHeight: 12,\n          paddingTop: spacing.padding.p0_5,\n        },\n        iconStyle: {\n          height: 16,\n          width: 16,\n        },\n        unreadCountStyle: {\n          containerStyle: {\n            height: 16,\n            minWidth: 16,\n            maxWidth: 40,\n            flexDirection: \"column\",\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            gap: 10,\n            borderRadius: spacing.radius.r5,\n            backgroundColor: color.primary,\n            paddingTop: 1,\n            paddingRight: spacing.padding.p1,\n            paddingBottom: 3,\n            paddingLeft: spacing.padding.p1,\n          },\n          textStyle: {\n            ...typography.caption2.regular,\n            color: color.staticWhite,\n          },\n        },\n      },\n      deletedBubbleStyles: getDeletedBubbleStyle(color, spacing, typography).incomingBubbleStyle,\n      avatarStyle: {\n        containerStyle: {\n          height: 32,\n          width: 32,\n        },\n        textStyle: {\n          textAlign: \"center\",\n          textAlignVertical: \"center\",\n          fontSize: typography.heading4.bold.fontSize,\n          color: color.primaryButtonIcon,\n          fontFamily: typography.fontFamily,\n          ...typography.heading4.bold,\n        },\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n        },\n      },\n      senderNameTextStyles: {\n        color: color.receiveBubbleTextHighlight,\n        ...typography.caption1.regular,\n      },\n    },\n    outgoingMessageBubbleStyles: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        backgroundColor: color.sendBubbleBackground,\n        borderRadius: spacing.radius.r3,\n        minWidth: 90,\n      },\n      moderationStyle: {\n        containerStyle: {\n          backgroundColor: \"#3A0C05\",\n          paddingVertical: 6,\n          borderBottomLeftRadius: 8,\n          borderBottomRightRadius: 8,\n          borderTopLeftRadius: 0,\n          borderTopRightRadius: 0,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          overflow: 'hidden',\n        },\n        textStyle: {\n          color: color?.error,\n          ...typography?.body?.regular,\n          flexShrink: 1,\n          paddingHorizontal: 2,\n          alignSelf: \"center\",\n        },\n        iconTintColor: color?.error,\n      },\n      dateStyles: {\n        containerStyle: {\n          paddingTop: spacing.padding.p2,\n          paddingBottom: spacing.padding.p2,\n          paddingHorizontal: spacing.padding.p1,\n        },\n        textStyle: {\n          color: color.sendBubbleTimestamp,\n          ...typography.caption2.regular,\n          textAlign: \"right\",\n          textTransform: \"lowercase\",\n        },\n      },\n      receiptStyles: {\n        sentIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.iconSecondary,\n        },\n        waitIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.iconSecondary,\n        },\n        deliveredIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.iconSecondary,\n        },\n        readIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.success,\n        },\n        errorIconStyle: {\n          marginTop: spacing.margin.m1,\n          marginBottom: spacing.margin.m1,\n          tintColor: color.error,\n        },\n      },\n      textBubbleStyles: {\n        textStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.sendBubbleText,\n          ...typography.body.regular,\n          //lineHeight: 16.8,\n        },\n        translatedTextStyle: {\n          fontFamily: typography.fontFamily,\n          color: color.sendBubbleText,\n          ...typography.body.regular,\n          lineHeight: 16.8,\n        },\n        translatedTextContainerStyle: {\n          marginBottom: spacing.margin.m1,\n        },\n        translatedTextDividerStyle: {\n          height: 0.8,\n          backgroundColor: color.iconSecondary,\n          marginVertical: spacing.margin.m3,\n        },\n        mentionsStyle: {\n          textStyle: {\n            ...typography.body.bold,\n            color: color.sendBubbleTextHighlight,\n          },\n          selfTextStyle: {\n            ...typography.body.bold,\n            color: color.warning,\n          },\n        },\n      },\n      imageBubbleStyles: {\n        containerStyle: {\n          padding: spacing.padding.p1,\n          paddingBottom: spacing.padding.p0,\n          borderRadius: spacing.radius.r3,\n          overflow: \"hidden\",\n        },\n        imageStyle: {\n          borderRadius: spacing.radius.r2,\n          backgroundColor: color.background3,\n          height: 232,\n          width: '100%',\n          minWidth: 232,\n        },\n        dateReceiptContainerStyle: {\n          paddingRight: spacing.padding.p1,\n        },\n      },\n      audioBubbleStyles: getAudioBubbleStyleDark(color, spacing, typography)\n        .outgoingAudioBubbleStyle,\n      fileBubbleStyles: getFileBubbleStyleDark(color, spacing, typography).outgoingFileBubbleStyle,\n      stickerBubbleStyles: getStickerStyleDark(color, spacing, typography),\n      collaborativeBubbleStyles: getCollabBubbleStyleDark(color, spacing, typography)\n        .outgoingBubbleStyle,\n      meetCallBubbleStyles: getGroupCallBubbleStyle(color, spacing, typography).outgoingBubbleStyle,\n      videoBubbleStyles: getVideoBubbleStylesDark(color, spacing, typography).outgoingBubbleStyle,\n      pollBubbleStyles: getPollBubbleStyleDark(color, spacing, typography).outgoingBubbleStyle,\n      reactionStyles: getReactionsStyleDark(color, spacing, typography).outgoingBubbleStyle,\n      linkPreviewBubbleStyles: getLinkPreviewBubbleStyleDark(color, spacing, typography)\n        .outgoingBubbleStyle,\n      threadedMessageStyles: {\n        containerStyle: {\n          flexDirection: \"row\",\n          justifyContent: \"flex-end\",\n          alignItems: \"center\",\n          gap: spacing.spacing.s1,\n          marginTop: spacing.spacing.s1,\n          paddingVertical: spacing.spacing.s0_5,\n          paddingHorizontal: spacing.spacing.s1,\n        },\n        indicatorTextStyle: {\n          ...typography.caption1.regular,\n          textAlign: \"right\",\n          color: color.staticWhite,\n          lineHeight: 12,\n          paddingTop: spacing.padding.p0_5,\n        },\n        iconStyle: {\n          height: 16,\n          width: 16,\n        },\n        unreadCountStyle: {\n          containerStyle: {\n            height: 16,\n            minWidth: 16,\n            maxWidth: 40,\n            flexDirection: \"column\",\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            gap: 10,\n            borderRadius: spacing.radius.r5,\n            backgroundColor: color.primary,\n            paddingTop: 1,\n            paddingRight: spacing.padding.p1,\n            paddingBottom: 3,\n            paddingLeft: spacing.padding.p1,\n          },\n          textStyle: {\n            ...typography.caption2.regular,\n            color: color.staticWhite,\n          },\n        },\n      },\n      avatarStyle: {\n        containerStyle: {\n          height: 32,\n          width: 32,\n        },\n        textStyle: {\n          textAlign: \"center\",\n          textAlignVertical: \"center\",\n          fontSize: typography.heading4.bold.fontSize,\n          color: color.primaryButtonIcon,\n          fontFamily: typography.fontFamily,\n          ...typography.heading4.bold,\n        },\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n        },\n      },\n      senderNameTextStyles: {\n        color: color.receiveBubbleTextHighlight,\n        ...typography.caption1.regular,\n      },\n      deletedBubbleStyles: getDeletedBubbleStyle(color, spacing, typography).outgoingBubbleStyle,\n    },\n    groupActionBubbleStyles: groupActionBubbleStyles(color, spacing, typography),\n    messageInformationStyles: getMessageInormationListStyleDark(color, spacing, typography),\n    messageOptionsStyles: getMessageOptionsStyle(color, spacing, typography),\n    callActionBubbleStyles: getCallActionBubbleStyle(color, spacing, typography),\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatRichTextEditor/RichTextEditorViewNativeComponent.ts",
    "content": "import type { ViewProps, HostComponent } from 'react-native';\nimport { requireNativeComponent } from 'react-native';\n\ntype Double = number;\ntype Int32 = number;\ntype DirectEventHandler<T> = (event: { nativeEvent: T }) => void;\n\n// New prop types for CometChat integration\ntype SelectionProp = Readonly<{\n  start: Int32;\n  end: Int32;\n}>;\n\ntype TextStyleProp = Readonly<{\n  fontSize?: Double;\n  color?: string;\n  fontFamily?: string;\n}>;\n\nexport type StyleRange = Readonly<{\n  style: string;\n  start: Int32;\n  end: Int32;\n  url?: string;\n  highlightColor?: string;\n}>;\n\nexport type Block = Readonly<{\n  type: string;\n  text: string;\n  styles: ReadonlyArray<StyleRange>;\n  alignment?: string;\n  checked?: boolean;\n  indentLevel?: Int32;\n}>;\n\ntype ContentChangeEventData = Readonly<{\n  text: string;\n  blocksJson: string;\n}>;\n\ntype SelectionChangeEventData = Readonly<{\n  start: Int32;\n  end: Int32;\n}>;\n\ntype SizeChangeEventData = Readonly<{\n  height: Double;\n}>;\n\ntype ActiveStylesEventData = Readonly<{\n  bold: boolean;\n  italic: boolean;\n  underline: boolean;\n  strikethrough: boolean;\n  code: boolean;\n  codeBlock: boolean;\n  highlight: boolean;\n  blockType: string;\n  alignment: string;\n}>;\n\ntype LinkTapEventData = Readonly<{\n  url: string;\n  text: string;\n  location: Int32;\n  length: Int32;\n}>;\n\nexport interface NativeProps extends ViewProps {\n  placeholder?: string;\n  editable?: boolean;\n  maxHeight?: Double;\n  numberOfLines?: Int32;\n  showToolbar?: boolean;\n  toolbarOptions?: ReadonlyArray<string>;\n  variant?: string;\n  initialContentJson?: string;\n  toolbarMode?: string;\n  enterKeyBehavior?: string;\n\n  // New props for CometChat integration\n  selection?: SelectionProp;\n  textStyle?: TextStyleProp;\n  placeholderTextColor?: string;\n  text?: string;\n  showTextSelectionMenuItems?: boolean;\n  codeBackgroundColor?: string;\n  codeBorderColor?: string;\n  codeTextColor?: string;\n  codeFontSize?: Double;\n\n  onContentChange?: DirectEventHandler<ContentChangeEventData>;\n  onSelectionChange?: DirectEventHandler<SelectionChangeEventData>;\n  onEditorFocus?: DirectEventHandler<Readonly<{}>>;\n  onEditorBlur?: DirectEventHandler<Readonly<{}>>;\n  onSizeChange?: DirectEventHandler<SizeChangeEventData>;\n  onActiveStylesChange?: DirectEventHandler<ActiveStylesEventData>;\n  onLinkTap?: DirectEventHandler<LinkTapEventData>;\n  onSendRequest?: DirectEventHandler<Readonly<{}>>;\n}\n\nexport default requireNativeComponent<NativeProps>(\n  'RichTextEditorView',\n) as HostComponent<NativeProps>;\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatRichTextEditor/index.tsx",
    "content": "import React, { forwardRef, useImperativeHandle, useRef, useCallback, useState } from 'react';\nimport { StyleSheet, UIManager, findNodeHandle } from 'react-native';\nimport type {\n  Block,\n  TextAlignment,\n  ContentChangeEvent,\n  SelectionChangeEvent,\n  RichTextEditorProps,\n  RichTextEditorRef,\n} from './types';\nimport RichTextEditorViewNative from './RichTextEditorViewNativeComponent';\n\n// Command constants\nconst COMMANDS = {\n  focus: 'focus',\n  blur: 'blur',\n  clear: 'clear',\n  toggleBold: 'toggleBold',\n  toggleItalic: 'toggleItalic',\n  toggleUnderline: 'toggleUnderline',\n  toggleStrikethrough: 'toggleStrikethrough',\n  toggleCode: 'toggleCode',\n  toggleCodeBlock: 'toggleCodeBlock',\n  toggleHighlight: 'toggleHighlight',\n  setHeading: 'setHeading',\n  setBulletList: 'setBulletList',\n  setNumberedList: 'setNumberedList',\n  setQuote: 'setQuote',\n  setChecklist: 'setChecklist',\n  setParagraph: 'setParagraph',\n  insertLink: 'insertLink',\n  undo: 'undo',\n  redo: 'redo',\n  clearFormatting: 'clearFormatting',\n  indent: 'indent',\n  outdent: 'outdent',\n  alignLeft: 'alignLeft',\n  alignCenter: 'alignCenter',\n  alignRight: 'alignRight',\n  toggleChecklistItem: 'toggleChecklistItem',\n  setText: 'setText',\n  setSelection: 'setSelection',\n  setContent: 'setContent',\n  setMentionRanges: 'setMentionRanges',\n  removeLink: 'removeLink',\n  updateLink: 'updateLink',\n} as const;\n\n// Helper to dispatch commands to native view\nconst dispatchCommand = (\n  ref: React.RefObject<any>,\n  command: string,\n  args: any[] = []\n) => {\n  const viewTag = findNodeHandle(ref.current);\n  if (viewTag != null) {\n    UIManager.dispatchViewManagerCommand(viewTag, command, args);\n  }\n};\n\ninterface SizeChangeEvent {\n  nativeEvent: {\n    height: number;\n  };\n}\n\nexport interface ActiveStylesState {\n  bold: boolean;\n  italic: boolean;\n  underline: boolean;\n  strikethrough: boolean;\n  code: boolean;\n  codeBlock: boolean;\n  highlight: boolean;\n  blockType: string;\n  alignment: string;\n}\n\ninterface ActiveStylesChangeEvent {\n  nativeEvent: {\n    bold: boolean;\n    italic: boolean;\n    underline: boolean;\n    strikethrough: boolean;\n    code: boolean;\n    codeBlock: boolean;\n    highlight: boolean;\n    blockType: string;\n    alignment: string;\n  };\n}\n\nexport interface LinkTapEventData {\n  url: string;\n  text: string;\n  location: number;\n  length: number;\n}\n\ninterface LinkTapEvent {\n  nativeEvent: LinkTapEventData;\n}\n\nexport interface RichTextEditorPropsExtended extends RichTextEditorProps {\n  /** Test ID for testing frameworks (e.g. Detox, React Native Testing Library) */\n  testID?: string;\n  /** Accessibility label for screen readers */\n  accessibilityLabel?: string;\n  onActiveStylesChange?: (styles: ActiveStylesState) => void;\n  onSizeChange?: (height: number) => void;\n  onLinkTap?: (data: LinkTapEventData) => void;\n  /** Callback when Enter is pressed in \"sendMessage\" mode (Android only) */\n  onSendRequest?: () => void;\n  /** Enter key behavior: \"newLine\" (default) or \"sendMessage\" (Android only) */\n  enterKeyBehavior?: string;\n  /** Show Bold/Italic/Underline/Strikethrough in text selection context menu */\n  showTextSelectionMenuItems?: boolean;\n  /** Code block background color */\n  codeBackgroundColor?: string;\n  /** Code block border color */\n  codeBorderColor?: string;\n  /** Code block text color */\n  codeTextColor?: string;\n  /** Code block font size */\n  codeFontSize?: number;\n}\n\nconst RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended>((props, ref) => {\n  const nativeRef = useRef<React.ElementRef<typeof RichTextEditorViewNative>>(null);\n  const [height, setHeight] = useState<number | undefined>(undefined);\n\n  const handleSizeChange = useCallback((event: SizeChangeEvent) => {\n    const newHeight = event.nativeEvent?.height;\n    if (newHeight && newHeight > 0) {\n      setHeight(newHeight);\n      props.onSizeChange?.(newHeight);\n    }\n  }, [props.onSizeChange]);\n\n  useImperativeHandle(ref, () => ({\n    setContent: (blocks: Block[]) => {\n      dispatchCommand(nativeRef, COMMANDS.setContent, [blocks]);\n    },\n    getText: async (): Promise<string> => '',\n    getBlocks: async (): Promise<Block[]> => [],\n    clear: () => {\n      dispatchCommand(nativeRef, COMMANDS.clear);\n    },\n    focus: () => {\n      dispatchCommand(nativeRef, COMMANDS.focus);\n    },\n    blur: () => {\n      dispatchCommand(nativeRef, COMMANDS.blur);\n    },\n    toggleBold: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleBold);\n    },\n    toggleItalic: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleItalic);\n    },\n    toggleUnderline: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleUnderline);\n    },\n    toggleStrikethrough: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleStrikethrough);\n    },\n    toggleCode: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleCode);\n    },\n    toggleCodeBlock: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleCodeBlock);\n    },\n    toggleHighlight: (_color?: string) => {\n      dispatchCommand(nativeRef, COMMANDS.toggleHighlight);\n    },\n    setHeading: () => {\n      dispatchCommand(nativeRef, COMMANDS.setHeading);\n    },\n    setBulletList: () => {\n      dispatchCommand(nativeRef, COMMANDS.setBulletList);\n    },\n    setNumberedList: () => {\n      dispatchCommand(nativeRef, COMMANDS.setNumberedList);\n    },\n    setQuote: () => {\n      dispatchCommand(nativeRef, COMMANDS.setQuote);\n    },\n    setChecklist: () => {\n      dispatchCommand(nativeRef, COMMANDS.setChecklist);\n    },\n    setParagraph: () => {\n      dispatchCommand(nativeRef, COMMANDS.setParagraph);\n    },\n    insertLink: (url: string, text: string) => {\n      dispatchCommand(nativeRef, COMMANDS.insertLink, [url, text]);\n    },\n    undo: () => {\n      dispatchCommand(nativeRef, COMMANDS.undo);\n    },\n    redo: () => {\n      dispatchCommand(nativeRef, COMMANDS.redo);\n    },\n    clearFormatting: () => {\n      dispatchCommand(nativeRef, COMMANDS.clearFormatting);\n    },\n    indent: () => {\n      dispatchCommand(nativeRef, COMMANDS.indent);\n    },\n    outdent: () => {\n      dispatchCommand(nativeRef, COMMANDS.outdent);\n    },\n    setAlignment: (alignment: TextAlignment) => {\n      switch (alignment) {\n        case 'left':\n          dispatchCommand(nativeRef, COMMANDS.alignLeft);\n          break;\n        case 'center':\n          dispatchCommand(nativeRef, COMMANDS.alignCenter);\n          break;\n        case 'right':\n          dispatchCommand(nativeRef, COMMANDS.alignRight);\n          break;\n      }\n    },\n    toggleChecklistItem: () => {\n      dispatchCommand(nativeRef, COMMANDS.toggleChecklistItem);\n    },\n    setText: (text: string) => {\n      dispatchCommand(nativeRef, COMMANDS.setText, [text]);\n    },\n    setSelection: (start: number, end?: number) => {\n      dispatchCommand(nativeRef, COMMANDS.setSelection, [start, end ?? start]);\n    },\n    setMentionRanges: (ranges: Array<{ start: number; end: number }>) => {\n      dispatchCommand(nativeRef, COMMANDS.setMentionRanges, [ranges]);\n    },\n    removeLink: (location: number, length: number) => {\n      dispatchCommand(nativeRef, COMMANDS.removeLink, [location, length]);\n    },\n    updateLink: (location: number, length: number, newUrl: string, newText: string) => {\n      dispatchCommand(nativeRef, COMMANDS.updateLink, [location, length, newUrl, newText]);\n    },\n  }));\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const handleContentChange = useCallback(\n    (event: any) => {\n      // Parse blocksJson string (codegen doesn't support nested ReadonlyArray<Object>)\n      let blocks: Block[] = [];\n      try {\n        if (event.nativeEvent.blocksJson) {\n          blocks = JSON.parse(event.nativeEvent.blocksJson);\n        } else if (event.nativeEvent.blocks) {\n          // Fallback for backward compatibility\n          blocks = [...event.nativeEvent.blocks];\n        }\n      } catch {\n        blocks = [];\n      }\n\n      // Convert native event to our API format\n      const contentEvent: ContentChangeEvent = {\n        nativeEvent: {\n          text: event.nativeEvent.text,\n          blocks,\n          delta: event.nativeEvent.delta,\n        },\n      };\n      props.onContentChange?.(contentEvent);\n    },\n    [props.onContentChange],\n  );\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const handleSelectionChange = useCallback(\n    (event: any) => {\n      const selectionEvent: SelectionChangeEvent = {\n        nativeEvent: {\n          start: event.nativeEvent.start,\n          end: event.nativeEvent.end,\n        },\n      };\n      props.onSelectionChange?.(selectionEvent);\n    },\n    [props.onSelectionChange],\n  );\n\n  const handleFocus = useCallback(() => {\n    props.onFocus?.();\n  }, [props.onFocus]);\n\n  const handleBlur = useCallback(() => {\n    props.onBlur?.();\n  }, [props.onBlur]);\n\n  const handleActiveStylesChange = useCallback(\n    (event: ActiveStylesChangeEvent) => {\n      const native = event.nativeEvent;\n      const styles: ActiveStylesState = {\n        bold: native.bold,\n        italic: native.italic,\n        underline: native.underline,\n        strikethrough: native.strikethrough,\n        code: native.codeBlock ? false : native.code,\n        codeBlock: native.codeBlock,\n        highlight: native.highlight,\n        blockType: native.blockType,\n        alignment: native.alignment,\n      };\n      props.onActiveStylesChange?.(styles);\n    },\n    [props.onActiveStylesChange],\n  );\n\n  const handleLinkTap = useCallback(\n    (event: LinkTapEvent) => {\n      props.onLinkTap?.(event.nativeEvent);\n    },\n    [props.onLinkTap],\n  );\n\n  const handleSendRequest = useCallback(() => {\n    props.onSendRequest?.();\n  }, [props.onSendRequest]);\n\n  const combinedStyle = StyleSheet.flatten([props.style, height != null ? { height } : undefined]);\n\n  return (\n    <RichTextEditorViewNative\n      ref={nativeRef}\n      testID={props.testID}\n      accessibilityLabel={props.accessibilityLabel}\n      style={combinedStyle}\n      placeholder={props.placeholder}\n      initialContentJson={props.initialContent ? JSON.stringify(props.initialContent) : undefined}\n      editable={props.readOnly !== undefined ? !props.readOnly : true}\n      maxHeight={props.maxHeight}\n      numberOfLines={props.numberOfLines}\n      showToolbar={props.readOnly ? false : props.showToolbar ?? true}\n      toolbarOptions={props.toolbarOptions}\n      variant={props.variant ?? 'outlined'}\n      toolbarMode={props.toolbarMode}\n      selection={props.selection}\n      textStyle={props.textStyle}\n      placeholderTextColor={props.placeholderTextColor}\n      text={props.text}\n      codeBackgroundColor={props.codeBackgroundColor}\n      codeBorderColor={props.codeBorderColor}\n      codeTextColor={props.codeTextColor}\n      codeFontSize={props.codeFontSize}\n      onContentChange={handleContentChange}\n      onSelectionChange={handleSelectionChange}\n      onEditorFocus={handleFocus}\n      onEditorBlur={handleBlur}\n      onSizeChange={handleSizeChange}\n      onActiveStylesChange={handleActiveStylesChange}\n      onLinkTap={handleLinkTap}\n      onSendRequest={handleSendRequest}\n      enterKeyBehavior={props.enterKeyBehavior}\n      showTextSelectionMenuItems={props.showTextSelectionMenuItems ?? true}\n    />\n  );\n});\n\nRichTextEditor.displayName = 'RichTextEditor';\n\nexport default RichTextEditor;\nexport { DEFAULT_TOOLBAR_OPTIONS } from './types';\nexport type {\n  Block,\n  BlockType,\n  StyleRange,\n  TextAlignment,\n  EditorVariant,\n  ContentChangeEvent,\n  SelectionChangeEvent,\n  RichTextEditorRef,\n  RichTextEditorProps,\n  ToolbarOption,\n  ContentDelta,\n  DeltaType,\n  Selection,\n  TextStyle,\n} from './types';\nexport { dispatchCommand };\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatRichTextEditor/types.ts",
    "content": "import type { StyleProp, ViewStyle } from 'react-native';\n\n// New interfaces for CometChat integration\nexport interface Selection {\n  start: number;\n  end: number;\n}\n\nexport interface TextStyle {\n  fontSize?: number;\n  color?: string;\n  fontFamily?: string;\n}\n\nexport interface StyleRange {\n  style: 'bold' | 'italic' | 'underline' | 'strikethrough' | 'link' | 'code' | 'highlight';\n  start: number;\n  end: number;\n  url?: string;\n  highlightColor?: string;\n}\n\nexport type BlockType = 'paragraph' | 'bullet' | 'numbered' | 'heading' | 'quote' | 'checklist';\nexport type TextAlignment = 'left' | 'center' | 'right';\nexport type EditorVariant = 'outlined' | 'flat' | 'plain';\n\nexport interface Block {\n  type: BlockType;\n  text: string;\n  styles: StyleRange[];\n  alignment?: TextAlignment;\n  checked?: boolean;\n  indentLevel?: number;\n}\n\nexport interface ContentChangeEvent {\n  nativeEvent: {\n    text: string;\n    blocks: Block[];\n    delta?: ContentDelta;\n  };\n}\n\nexport type DeltaType = 'insert' | 'delete' | 'format' | 'replace';\n\nexport interface ContentDelta {\n  type: DeltaType;\n  position: number;\n  length?: number;\n  text?: string;\n  blockIndex?: number;\n  style?: string;\n}\n\nexport interface SelectionChangeEvent {\n  nativeEvent: {\n    start: number;\n    end: number;\n  };\n}\n\nexport type ToolbarOption =\n  | 'bold'\n  | 'italic'\n  | 'strikethrough'\n  | 'underline'\n  | 'code'\n  | 'highlight'\n  | 'heading'\n  | 'bullet'\n  | 'numbered'\n  | 'quote'\n  | 'checklist'\n  | 'link'\n  | 'undo'\n  | 'redo'\n  | 'clearFormatting'\n  | 'indent'\n  | 'outdent'\n  | 'alignLeft'\n  | 'alignCenter'\n  | 'alignRight';\n\nexport const DEFAULT_TOOLBAR_OPTIONS: ToolbarOption[] = [\n  'bold',\n  'italic',\n  'underline',\n  'strikethrough',\n  'code',\n  'highlight',\n  'heading',\n  'bullet',\n  'numbered',\n  'quote',\n  'checklist',\n  'link',\n  'undo',\n  'redo',\n  'clearFormatting',\n  'indent',\n  'outdent',\n  'alignLeft',\n  'alignCenter',\n  'alignRight',\n];\n\nexport interface RichTextEditorProps {\n  style?: StyleProp<ViewStyle>;\n  placeholder?: string;\n  initialContent?: Block[];\n  readOnly?: boolean;\n  maxHeight?: number;\n  numberOfLines?: number;\n  showToolbar?: boolean;\n  toolbarOptions?: ToolbarOption[];\n  variant?: EditorVariant;\n  toolbarMode?: 'floating' | 'inline';\n  // New props for CometChat integration\n  selection?: Selection;\n  textStyle?: TextStyle;\n  placeholderTextColor?: string;\n  text?: string;\n  onContentChange?: (event: ContentChangeEvent) => void;\n  onSelectionChange?: (event: SelectionChangeEvent) => void;\n  onFocus?: () => void;\n  onBlur?: () => void;\n}\n\nexport interface RichTextEditorRef {\n  setContent: (blocks: Block[]) => void;\n  getText: () => Promise<string>;\n  getBlocks: () => Promise<Block[]>;\n  clear: () => void;\n  focus: () => void;\n  blur: () => void;\n  toggleBold: () => void;\n  toggleItalic: () => void;\n  toggleUnderline: () => void;\n  toggleStrikethrough: () => void;\n  toggleCode: () => void;\n  toggleCodeBlock: () => void;\n  toggleHighlight: (color?: string) => void;\n  setHeading: () => void;\n  setBulletList: () => void;\n  setNumberedList: () => void;\n  setQuote: () => void;\n  setChecklist: () => void;\n  setParagraph: () => void;\n  insertLink: (url: string, text: string) => void;\n  undo: () => void;\n  redo: () => void;\n  clearFormatting: () => void;\n  indent: () => void;\n  outdent: () => void;\n  setAlignment: (alignment: TextAlignment) => void;\n  toggleChecklistItem: () => void;\n  setText: (text: string) => void;\n  setSelection: (start: number, end?: number) => void;\n  setMentionRanges: (ranges: Array<{ start: number; end: number }>) => void;\n  removeLink: (location: number, length: number) => void;\n  updateLink: (location: number, length: number, newUrl: string, newText: string) => void;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatSearch/CometChatSearch.tsx",
    "content": "import React, { useCallback, useEffect, useReducer, useRef, useState, useMemo } from 'react';\nimport { View, TextInput, TouchableOpacity, Text, Image, ImageBackground, FlatList, ImageSourcePropType } from 'react-native';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport {\n  CometChatAvatar,\n  CometChatStatusIndicator,\n  CometChatBadge,\n  CometChatDate,\n  useTheme\n} from '@cometchat/chat-uikit-react-native';\nimport { Icon } from '../shared/icons/Icon';\nimport { GroupTypeConstants, ReceiverTypeConstants, MessageCategoryConstants, MessageTypeConstants } from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';\nimport { CometChatSearchScope, CometChatSearchFilter, States } from './SearchConstants';\nimport { Skeleton } from './Skeleton';\nimport { SearchStyle, getSearchStyleLight, getSearchStyleDark } from './style';\nimport { deepMerge } from '../shared/helper/helperFunctions';\nimport { DeepPartial } from '../shared/helper/types';\nimport { getCometChatTranslation } from '../shared/resources/CometChatLocalizeNew/LocalizationManager';\nimport { CommonUtils } from '../shared/utils/CommonUtils';\nimport { stripMarkdown } from '../shared/utils/MarkdownUtils';\nimport { ExtensionConstants } from '../extensions/ExtensionConstants';\nimport { getExtensionData } from '../extensions/ExtensionModerator';\n\n\n// ERROR HANDLER \nfunction useCometChatErrorHandler(onError?: (error: CometChat.CometChatException) => void) {\n  return useCallback(\n    (error: any, source?: string) => {\n      console.error(`CometChat Error${source ? ` in ${source}` : ''}:`, error);\n\n      if (onError && error instanceof CometChat.CometChatException) {\n        onError(error);\n      }\n    },\n    [onError]\n  );\n}\n\n// SEARCH UTILITIES\nfunction hasValidConversationSearchCriteria(searchKeyword: string, filters: CometChatSearchFilter[]): boolean {\n  if (searchKeyword && searchKeyword.trim() !== \"\") {\n    return true;\n  }\n\n  const validFilters = [\n    CometChatSearchFilter.Unread,\n    CometChatSearchFilter.Groups,\n    CometChatSearchFilter.Conversations\n  ];\n\n  if (filters.length === 0) {\n    return false;\n  }\n\n  return filters.every(filter => validFilters.includes(filter));\n}\n\nfunction hasValidMessageSearchCriteria(searchKeyword: string, filters: CometChatSearchFilter[]): boolean {\n  if (searchKeyword && searchKeyword.trim() !== \"\") {\n    return true;\n  }\n\n  const validMessageFilters = [\n    CometChatSearchFilter.Photos,\n    CometChatSearchFilter.Videos,\n    CometChatSearchFilter.Documents,\n    CometChatSearchFilter.Audio,\n    CometChatSearchFilter.Links\n  ];\n\n  if (filters.length === 0) {\n    return false;\n  }\n\n  return filters.some(filter => validMessageFilters.includes(filter));\n}\n\ninterface SearchState {\n  searchText: string;\n  conversations: CometChat.Conversation[];\n  messages: CometChat.BaseMessage[];\n  fetchState: States;\n  activeFilters: CometChatSearchFilter[];\n}\n\ntype SearchAction =\n  | { type: \"setSearchText\"; searchText: string }\n  | { type: \"setResults\"; conversations?: CometChat.Conversation[]; messages?: CometChat.BaseMessage[] }\n  | { type: \"clearResults\" }\n  | { type: \"setFetchState\"; fetchState: States }\n  | { type: \"setActiveFilter\"; filterId: CometChatSearchFilter }\n  | { type: \"resetActiveFilters\" };\n\nconst initialState: SearchState = {\n  searchText: \"\",\n  conversations: [],\n  messages: [],\n  fetchState: States.loaded,\n  activeFilters: [],\n};\n\nfunction searchStateReducer(state: SearchState, action: SearchAction): SearchState {\n  switch (action.type) {\n    case \"setSearchText\":\n      return { ...state, searchText: action.searchText };\n\n    case \"setResults\":\n      return {\n        ...state,\n        conversations: action.conversations ?? state.conversations,\n        messages: action.messages ?? state.messages,\n        fetchState: States.loaded,\n      };\n\n    case \"clearResults\":\n      return { ...state, conversations: [], messages: [] };\n\n    case \"setFetchState\":\n      return { ...state, fetchState: action.fetchState };\n\n    case \"setActiveFilter\": {\n      const newActiveFilters = [...state.activeFilters];\n      const filterIndex = newActiveFilters.findIndex(f => f === action.filterId);\n\n      // Define filter pairs\n      const filterPairs = {\n        conversationPair: [CometChatSearchFilter.Unread, CometChatSearchFilter.Groups],\n        photosVideosPair: [CometChatSearchFilter.Photos, CometChatSearchFilter.Videos],\n        audioDocumentsPair: [CometChatSearchFilter.Audio, CometChatSearchFilter.Documents],\n        linkStandalone: [CometChatSearchFilter.Links]\n      };\n\n      if (filterIndex >= 0) {\n        // If filter is already active, remove it\n        newActiveFilters.splice(filterIndex, 1);\n      } else {\n        // Clear incompatible filters before adding new one\n\n        // If selecting Links (standalone), clear all other filters\n        if (action.filterId === CometChatSearchFilter.Links) {\n          newActiveFilters.length = 0; // Clear all filters\n        }\n        // If selecting conversation pair filter, clear other pairs\n        else if (filterPairs.conversationPair.includes(action.filterId)) {\n          // Remove all non-conversation filters\n          const filtersToRemove = [\n            ...filterPairs.photosVideosPair,\n            ...filterPairs.audioDocumentsPair,\n            ...filterPairs.linkStandalone\n          ];\n          filtersToRemove.forEach(filter => {\n            const idx = newActiveFilters.findIndex(f => f === filter);\n            if (idx >= 0) newActiveFilters.splice(idx, 1);\n          });\n        }\n        // If selecting photos/videos pair filter, clear other pairs\n        else if (filterPairs.photosVideosPair.includes(action.filterId)) {\n          const filtersToRemove = [\n            ...filterPairs.conversationPair,\n            ...filterPairs.audioDocumentsPair,\n            ...filterPairs.linkStandalone\n          ];\n          filtersToRemove.forEach(filter => {\n            const idx = newActiveFilters.findIndex(f => f === filter);\n            if (idx >= 0) newActiveFilters.splice(idx, 1);\n          });\n        }\n        // If selecting audio/documents pair filter, clear other pairs\n        else if (filterPairs.audioDocumentsPair.includes(action.filterId)) {\n          const filtersToRemove = [\n            ...filterPairs.conversationPair,\n            ...filterPairs.photosVideosPair,\n            ...filterPairs.linkStandalone\n          ];\n          filtersToRemove.forEach(filter => {\n            const idx = newActiveFilters.findIndex(f => f === filter);\n            if (idx >= 0) newActiveFilters.splice(idx, 1);\n          });\n        }\n\n        newActiveFilters.push(action.filterId);\n      }\n\n      return { ...state, activeFilters: newActiveFilters };\n    }\n\n    case \"resetActiveFilters\":\n      return { ...state, activeFilters: [] };\n\n    default:\n      return state;\n  }\n}\n\n/**\n * Props for the CometChatSearch component\n */\ninterface CometChatSearchProps {\n  /**\n   * Callback triggered when the back button is clicked\n   * Use this to handle navigation when user clicks the back button\n   */\n  onBack?: () => void;\n\n  /**\n   * Whether to hide the back button\n   */\n  hideBackButton?: boolean;\n\n  /**\n   * Callback triggered when a conversation is clicked in search results\n   * @param conversation - The conversation that was clicked\n   * @param searchKeyword - The keyword that was used in the search\n   */\n  onConversationClicked?: (conversation: CometChat.Conversation, searchKeyword?: string) => void;\n\n  /**\n   * Callback triggered when a message is clicked in search results\n   * @param message - The message that was clicked\n   * @param searchKeyword - The keyword that was used in the search\n   */\n  onMessageClicked?: (message: CometChat.BaseMessage, searchKeyword?: string) => void;\n\n  /**\n   * Array of search filters to display in the filter bar\n   */\n  searchFilters?: Array<CometChatSearchFilter>;\n\n  /**\n   * Filter that should be active by default when the component loads\n   */\n  initialSearchFilter?: CometChatSearchFilter;\n\n  /**\n   * Scopes to search in (Conversations, Messages, or both)\n   */\n  searchIn?: Array<CometChatSearchScope>;\n\n  /**\n   * Request builder for conversations search\n   */\n  conversationsRequestBuilder?: CometChat.ConversationsRequestBuilder;\n\n  /**\n   * Request builder for messages search\n   */\n  messagesRequestBuilder?: CometChat.MessagesRequestBuilder;\n\n  /**\n   * Custom error handler for search operations\n   */\n  onError?: (error: CometChat.CometChatException) => void;\n\n  /**\n   * User ID to search within specific user's messages\n   */\n  uid?: string;\n\n  /**\n   * Group ID to search within specific group's messages\n   */\n  guid?: string;\n\n  /**\n   * Search placeholder text\n   */\n  searchPlaceholder?: string;\n\n  /**\n   * Custom styles for the component\n   */\n  style?: DeepPartial<SearchStyle>;\n\n  /**\n   * Custom loading view component that will be shown during search operations\n   */\n  loadingView?: () => React.ReactElement;\n\n  /**\n   * Custom empty state view component that will be shown when no results are found\n   */\n  emptyView?: () => React.ReactElement;\n\n  /**\n   * Custom error state view component that will be shown when search fails\n   */\n  errorView?: () => React.ReactElement;\n\n  /**\n   * Custom view component for conversation items (both unread and group conversations)\n   * @param conversation - The conversation object to render\n   * @param searchKeyword - The search keyword used\n   */\n  conversationItemView?: (conversation: CometChat.Conversation, searchKeyword?: string) => React.ReactElement;\n\n  /**\n   * Custom view component for text message items\n   * @param message - The text message object to render\n   * @param searchKeyword - The search keyword used\n   */\n  textMessageItemView?: (message: CometChat.BaseMessage, searchKeyword?: string) => React.ReactElement;\n\n  /**\n   * Custom view component for image message items\n   * @param message - The image message object to render\n   * @param searchKeyword - The search keyword used\n   */\n  imageMessageItemView?: (message: CometChat.BaseMessage, searchKeyword?: string) => React.ReactElement;\n\n  /**\n   * Custom view component for audio message items\n   * @param message - The audio message object to render\n   * @param searchKeyword - The search keyword used\n   */\n  audioMessageItemView?: (message: CometChat.BaseMessage, searchKeyword?: string) => React.ReactElement;\n\n  /**\n   * Custom view component for video message items\n   * @param message - The video message object to render\n   * @param searchKeyword - The search keyword used\n   */\n  videoMessageItemView?: (message: CometChat.BaseMessage, searchKeyword?: string) => React.ReactElement;\n\n  /**\n   * Custom view component for document/file message items\n   * @param message - The document message object to render\n   * @param searchKeyword - The search keyword used\n   */\n  documentMessageItemView?: (message: CometChat.BaseMessage, searchKeyword?: string) => React.ReactElement;\n\n  /**\n   * Custom view component for link message items (text messages with link previews)\n   * @param message - The link message object to render\n   * @param searchKeyword - The search keyword used\n   */\n  linkMessageItemView?: (message: CometChat.BaseMessage, searchKeyword?: string) => React.ReactElement;\n}\nconst t = getCometChatTranslation();\n\ninterface LinkPreviewImageProps {\n  uri: string;\n  fallbackUri?: string;\n  mergedStyles: any;\n  theme: any;\n}\n\nconst LinkFallbackIcon = ({ mergedStyles, theme }: { mergedStyles: any; theme: any }) => (\n  <View style={mergedStyles.messageItemStyle?.iconContainerStyle}>\n    <Icon\n      name='link-fill'\n      size={48}\n      height={48}\n      width={48}\n      color={theme.color.iconSecondary}\n    />\n  </View>\n);\n\nconst LinkPreviewImage: React.FC<LinkPreviewImageProps> = ({ uri, fallbackUri, mergedStyles, theme }) => {\n  const [imageError, setImageError] = useState(false);\n  const [fallbackError, setFallbackError] = useState(false);\n\n  if (imageError && (!fallbackUri || fallbackError)) {\n    return <LinkFallbackIcon mergedStyles={mergedStyles} theme={theme} />;\n  }\n\n  return (\n    <View style={mergedStyles.messageItemStyle?.iconContainerStyle}>\n      <Image\n        source={{ uri: imageError ? fallbackUri! : uri }}\n        style={mergedStyles.messageItemStyle?.linkPreviewImageStyle}\n        resizeMode=\"cover\"\n        onError={() => imageError ? setFallbackError(true) : setImageError(true)}\n      />\n    </View>\n  );\n};\n\n// Helper function to check for extension-generated thumbnails\nconst checkThumbnail = (message: CometChat.MediaMessage): { uri: string } => {\n  let image: { uri: string } = { uri: \"\" };\n  const thumbnailData = getExtensionData(message, ExtensionConstants.thumbnailGeneration);\n\n  if (thumbnailData == undefined) {\n    // Fallback to attachment thumbnail for videos\n    if (message.getType() === \"video\") {\n      const attachment = message.getAttachment();\n      const thumbnailUrl = attachment && ((attachment as any).thumbnail || (attachment as any).url);\n      image = thumbnailUrl ? { uri: thumbnailUrl } : image;\n    }\n  } else {\n    // Extension-generated thumbnail exists\n    const attachmentData = thumbnailData[\"attachments\"];\n    if (attachmentData && attachmentData.length) {\n      const dataObj = attachmentData[0];\n      if (!dataObj[\"error\"]) {\n        const imageLink = dataObj?.[\"data\"]?.[\"thumbnails\"]?.[\"url_small\"];\n        if (imageLink) {\n          image = { uri: dataObj[\"data\"][\"thumbnails\"][\"url_small\"] };\n        }\n      }\n    }\n  }\n\n  return image;\n};\n\ninterface VideoThumbnailProps {\n  thumbnailUrl: ImageSourcePropType;\n  mergedStyles: any;\n}\n\nconst VideoThumbnail = React.memo(({ thumbnailUrl, mergedStyles, }: VideoThumbnailProps) => {\n\n    const [imageSource, setImageSource] = useState<ImageSourcePropType>(thumbnailUrl);\n\n    useEffect(() => {\n      if (thumbnailUrl && typeof thumbnailUrl === \"object\" && \"uri\" in thumbnailUrl) {\n        CommonUtils.prefetchThumbnail((thumbnailUrl as any).uri).then((success) => {\n          if (success) {\n            // console.log(\"success\", thumbnailUrl);\n            setImageSource(thumbnailUrl);\n\n          }\n        }).catch((error) => {\n          console.log(\"error\", error);\n        });\n      }\n    }, [thumbnailUrl]);\n\n    return (\n      <View\n        style={mergedStyles.messageItemStyle?.previewContainerStyle}\n        renderToHardwareTextureAndroid={true}\n        shouldRasterizeIOS={true}\n      >\n        <ImageBackground\n          source={imageSource}\n          style={mergedStyles.messageItemStyle?.previewVideoStyle}\n          resizeMode=\"cover\"\n          progressiveRenderingEnabled={true}\n          fadeDuration={0}\n        >\n          <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>\n            <View style={mergedStyles.messageItemStyle?.videoPlayIconStyle}>\n              <Icon\n                name='play-arrow'\n                size={16}\n                height={16}\n                width={16}\n                color=\"white\"\n              />\n            </View>\n          </View>\n        </ImageBackground>\n      </View>\n    );\n  },\n\n  /** Custom comparison function */\n  (prev, next) => {\n    const prevUri = (prev.thumbnailUrl as any)?.uri;\n    const nextUri = (next.thumbnailUrl as any)?.uri;\n\n    return prevUri === nextUri;\n  }\n);\n\n// Memoized Conversation Item Component\ninterface ConversationItemProps {\n  conversation: CometChat.Conversation;\n  searchText: string;\n  onPress: (conversation: CometChat.Conversation, searchText: string) => void;\n  mergedStyles: any;\n  theme: any;\n  conversationItemView?: (conversation: CometChat.Conversation, searchKeyword?: string) => React.ReactElement;\n}\n\nconst ConversationItem = React.memo<ConversationItemProps>((\n  { conversation, searchText, onPress, mergedStyles, theme, conversationItemView }\n) => {\n  const getStatusIndicator = () => {\n    const withObj = conversation.getConversationWith();\n\n    if (withObj instanceof CometChat.Group) {\n      if (withObj.getType() === GroupTypeConstants.password) return \"password\";\n      if (withObj.getType() === GroupTypeConstants.private) return \"private\";\n    } else if (withObj instanceof CometChat.User) {\n      if (withObj.getStatus() === 'online' && !withObj.getHasBlockedMe() && !withObj.getBlockedByMe()) {\n        return \"online\";\n      }\n      return \"offline\";\n    }\n    return undefined;\n  };\n\n  const renderLeadingView = () => {\n    const withObj = conversation.getConversationWith();\n    const avatarURL = withObj instanceof CometChat.User ? withObj.getAvatar() : withObj.getIcon();\n    const name = withObj.getName();\n\n    return (\n      <View style={{ position: 'relative' }}>\n        <View style={{ height: 48, width: 48 }}>\n          <CometChatAvatar\n            image={{ uri: avatarURL }}\n            name={name}\n          />\n          <View style={{\n            position: 'absolute',\n            bottom: 0,\n            right: 0,\n          }}>\n            <CometChatStatusIndicator\n              type={getStatusIndicator()}\n            />\n          </View>\n        </View>\n      </View>\n    );\n  };\n\n  const renderTrailingView = () => {\n    const timestamp = conversation.getLastMessage()?.getSentAt();\n    if (!timestamp) return null;\n\n    return (\n      <View style={mergedStyles.conversationItemStyle?.trailingContainerStyle}>\n        <CometChatDate\n          timeStamp={timestamp * 1000}\n          pattern={\"conversationDate\"}\n        />\n        <CometChatBadge\n          count={conversation.getUnreadMessageCount()}\n        />\n      </View>\n    );\n  };\n\n  const formatMentionsInText = (rawText: any, message?: CometChat.BaseMessage) => {\n    if (typeof rawText !== 'string') return rawText;\n\n    // Strip markdown syntax for clean search result preview while\n    // preserving mention tokens for processing below.\n    let text = stripMarkdown(rawText).replace(/<@all:(.*?)>/g, '@$1');\n\n    try {\n      const mentionedUsers: CometChat.User[] = (message && (message).getMentionedUsers && (message).getMentionedUsers()) || [];\n      if (mentionedUsers && mentionedUsers.length > 0) {\n        mentionedUsers.forEach((u: CometChat.User) => {\n          const uid = u.getUid();\n          const name = u.getName();\n          if (uid && name) {\n            const uidRegex = new RegExp(`<@uid:${uid}>`, 'g');\n            text = text.replace(uidRegex, `@${name}`);\n          }\n        });\n      }\n    } catch (e) {\n      console.log(e)\n    }\n\n    return text;\n  };\n\n  if (conversationItemView) {\n    return (\n      <TouchableOpacity onPress={() => onPress(conversation, searchText)}>\n        {conversationItemView(conversation, searchText)}\n      </TouchableOpacity>\n    );\n  }\n\n  return (\n    <TouchableOpacity\n      style={mergedStyles.conversationItemStyle?.containerStyle}\n      onPress={() => onPress(conversation, searchText)}\n    >\n      {renderLeadingView()}\n      <View style={mergedStyles.conversationItemStyle?.contentStyle}>\n        <Text style={mergedStyles.conversationItemStyle?.titleStyle} numberOfLines={1}>\n          {conversation.getConversationWith().getName()}\n        </Text>\n        <Text style={mergedStyles.conversationItemStyle?.subtitleStyle} numberOfLines={2}>\n          {formatMentionsInText((conversation.getLastMessage())?.getText?.(), conversation.getLastMessage())}\n        </Text>\n      </View>\n      {renderTrailingView()}\n    </TouchableOpacity>\n  );\n}, (prevProps, nextProps) => {\n  // Only re-render if conversation ID or search text changes\n  return prevProps.conversation.getConversationId() === nextProps.conversation.getConversationId() &&\n    prevProps.searchText === nextProps.searchText;\n});\n\nConversationItem.displayName = 'ConversationItem';\n\n/**\n * CometChatSearch component for searching conversations and messages in CometChat\n */\nexport const CometChatSearch: React.FC<CometChatSearchProps> = ({\n  onBack = () => { },\n  hideBackButton = false,\n  onConversationClicked,\n  onMessageClicked,\n  searchFilters = [\n    CometChatSearchFilter.Unread,\n    CometChatSearchFilter.Groups,\n    CometChatSearchFilter.Photos,\n    CometChatSearchFilter.Videos,\n    CometChatSearchFilter.Links,\n    CometChatSearchFilter.Documents,\n    CometChatSearchFilter.Audio\n  ],\n  initialSearchFilter,\n  searchIn = [],\n  conversationsRequestBuilder,\n  messagesRequestBuilder,\n  onError,\n  uid,\n  guid,\n  searchPlaceholder = t(\"SEARCH_PLACEHOLDER\") || \"Search...\",\n  style,\n  loadingView,\n  emptyView,\n  errorView,\n  conversationItemView,\n  textMessageItemView,\n  imageMessageItemView,\n  audioMessageItemView,\n  videoMessageItemView,\n  documentMessageItemView,\n  linkMessageItemView\n}) => {\n  const [searchState, dispatch] = useReducer(searchStateReducer, initialState);\n  const [searchValue, setSearchValue] = useState(\"\");\n  const [loggedInUser, setLoggedInUser] = useState<CometChat.User | null>(null);\n\n  const theme = useTheme();\n  const errorHandler = useCometChatErrorHandler(onError);\n  const timeoutIdRef = useRef<any | null>(null);\n  const searchInputRef = useRef<TextInput>(null);\n\n  // Merge theme styles with provided style overrides\n  const mergedStyles = useMemo(() => {\n    const baseStyles = getSearchStyleLight(theme.color, theme.spacing, theme.typography);\n    return deepMerge(baseStyles, style ?? {});\n  }, [theme, style]);\n\n  // Initialize active filters with initial filter if provided\n  useEffect(() => {\n    if (searchState.activeFilters.length === 0 && initialSearchFilter) {\n      dispatch({ type: \"setActiveFilter\", filterId: initialSearchFilter });\n    }\n  }, [initialSearchFilter, searchState.activeFilters.length]);\n\n  // Get logged in user\n  useEffect(() => {\n    const getLoggedInUser = async () => {\n      try {\n        const user = await CometChat.getLoggedinUser();\n        setLoggedInUser(user);\n      } catch (error) {\n        errorHandler(error as CometChat.CometChatException, \"getLoggedInUser\");\n      }\n    };\n\n    getLoggedInUser();\n  }, [errorHandler]);\n\n  // Handle search input changes with debouncing\n  const handleSearch = useCallback((text: string) => {\n    setSearchValue(text);\n    const newSearchText = text.trim();\n\n    if (timeoutIdRef.current) {\n      clearTimeout(timeoutIdRef.current);\n    }\n\n    timeoutIdRef.current = setTimeout(() => {\n      dispatch({ type: \"setSearchText\", searchText: newSearchText });\n      timeoutIdRef.current = null;\n    }, 500);\n  }, []);\n\n  // Clear search\n  const handleClearSearch = useCallback(() => {\n    setSearchValue(\"\");\n    dispatch({ type: \"setSearchText\", searchText: \"\" });\n    dispatch({ type: \"clearResults\" });\n  }, []);\n\n  // Toggle filter\n  const toggleFilter = useCallback((filterId: CometChatSearchFilter) => {\n    dispatch({ type: \"setActiveFilter\", filterId });\n  }, []);\n\n  // Auto-focus the search input when component mounts\n  useEffect(() => {\n    const timer = setTimeout(() => {\n      if (searchInputRef.current) {\n        searchInputRef.current.focus();\n      }\n    }, 100);\n    return () => clearTimeout(timer);\n  }, []);\n\n  // Cleanup timeout on unmount\n  useEffect(() => {\n    return () => {\n      if (timeoutIdRef.current) {\n        clearTimeout(timeoutIdRef.current);\n      }\n    };\n  }, []);\n\n  const renderHeader = () => (\n    <View style={[mergedStyles.headerStyle]}>\n      {!hideBackButton && (\n        <TouchableOpacity\n          style={[mergedStyles.backButtonStyle]}\n          onPress={onBack}\n        >\n          <Icon\n            name='arrow-back'\n            imageStyle={mergedStyles.backButtonIconStyle}\n            color={mergedStyles?.backButtonIconStyle?.tintColor}\n          />\n        </TouchableOpacity>\n      )}\n      <View style={[mergedStyles.searchContainerStyle]}>\n        <View style={[mergedStyles.searchInputContainerStyle]}>\n          <TextInput\n            ref={searchInputRef}\n            style={[mergedStyles.searchInputStyle]}\n            value={searchValue}\n            onChangeText={handleSearch}\n            placeholder={searchPlaceholder}\n            placeholderTextColor={theme.color.textTertiary}\n          />\n\n          <TouchableOpacity\n            style={[mergedStyles.clearButtonStyle]}\n            onPress={handleClearSearch}\n            accessibilityLabel=\"Clear search\"\n          >\n            <Icon\n              name='close'\n              imageStyle={mergedStyles.clearButtonIconStyle}\n            />\n          </TouchableOpacity>\n        </View>\n      </View>\n    </View>\n  );\n\n  const getVisibleFilters = useCallback(() => {\n    const conversationFilters = [\n      CometChatSearchFilter.Conversations,\n      CometChatSearchFilter.Unread,\n      CometChatSearchFilter.Groups\n    ];\n\n    const messageFilters = [\n      CometChatSearchFilter.Messages,\n      CometChatSearchFilter.Photos,\n      CometChatSearchFilter.Videos,\n      CometChatSearchFilter.Documents,\n      CometChatSearchFilter.Audio,\n      CometChatSearchFilter.Links\n    ];\n\n    // Define filter pairs\n    const filterPairs = {\n      conversationPair: [CometChatSearchFilter.Unread, CometChatSearchFilter.Groups],\n      photosVideosPair: [CometChatSearchFilter.Photos, CometChatSearchFilter.Videos],\n      audioDocumentsPair: [CometChatSearchFilter.Audio, CometChatSearchFilter.Documents],\n      linkStandalone: [CometChatSearchFilter.Links]\n    };\n\n    // If searchIn is empty, search in both conversations and messages\n    const effectiveSearchIn = searchIn.length === 0\n      ? [CometChatSearchScope.Conversations, CometChatSearchScope.Messages]\n      : searchIn;\n\n    let availableFilters: CometChatSearchFilter[] = [];\n\n    // Add filters based on searchIn scope\n    if (effectiveSearchIn.includes(CometChatSearchScope.Conversations)) {\n      availableFilters.push(...conversationFilters);\n    }\n    if (effectiveSearchIn.includes(CometChatSearchScope.Messages)) {\n      availableFilters.push(...messageFilters);\n    }\n\n    // Filter the searchFilters prop to only include available filters\n    let filteredAvailableFilters = searchFilters.filter(filter => availableFilters.includes(filter));\n\n    // Filter out conversation filters if uid or guid is present\n    if (uid || guid) {\n      filteredAvailableFilters = filteredAvailableFilters.filter(filter => !conversationFilters.includes(filter));\n    }\n\n    // If no filters are selected, return filtered available filters\n    if (searchState.activeFilters.length === 0) {\n      return filteredAvailableFilters;\n    }\n\n    // Handle pairing logic\n    const activeFilters = searchState.activeFilters;\n\n    // Check which pair is active\n    const hasConversationPair = activeFilters.some(filter => filterPairs.conversationPair.includes(filter));\n    const hasPhotosVideosPair = activeFilters.some(filter => filterPairs.photosVideosPair.includes(filter));\n    const hasAudioDocumentsPair = activeFilters.some(filter => filterPairs.audioDocumentsPair.includes(filter));\n    const hasLinkStandalone = activeFilters.includes(CometChatSearchFilter.Links);\n\n    // If conversation pair is active, show only conversation filters\n    if (hasConversationPair && effectiveSearchIn.includes(CometChatSearchScope.Conversations) && !uid && !guid) {\n      return filterPairs.conversationPair.filter(filter => filteredAvailableFilters.includes(filter));\n    }\n\n    // If photos/videos pair is active, show only photos and videos\n    if (hasPhotosVideosPair) {\n      return filterPairs.photosVideosPair.filter(filter => filteredAvailableFilters.includes(filter));\n    }\n\n    // If audio/documents pair is active, show only audio and documents\n    if (hasAudioDocumentsPair) {\n      return filterPairs.audioDocumentsPair.filter(filter => filteredAvailableFilters.includes(filter));\n    }\n\n    // If links is active (standalone), show only links\n    if (hasLinkStandalone) {\n      return filterPairs.linkStandalone.filter(filter => filteredAvailableFilters.includes(filter));\n    }\n\n    // Fallback: return filtered available filters\n    return filteredAvailableFilters;\n  }, [searchState.activeFilters, searchFilters, searchIn, uid, guid]);\n\n  const getFilterText = (filterId: CometChatSearchFilter): string => {\n    const filterTextMap = {\n      [CometChatSearchFilter.Audio]: t(\"SEARCH_FILTER_AUDIO\") || \"Audio\",\n      [CometChatSearchFilter.Conversations]: t(\"SEARCH_FILTER_CONVERSATIONS\") || \"Conversations\",\n      [CometChatSearchFilter.Documents]: t(\"SEARCH_FILTER_DOCUMENTS\") || \"Documents\",\n      [CometChatSearchFilter.Groups]: t(\"SEARCH_FILTER_GROUPS\") || \"Groups\",\n      [CometChatSearchFilter.Links]: t(\"SEARCH_FILTER_LINKS\") || \"Links\",\n      [CometChatSearchFilter.Messages]: t(\"SEARCH_FILTER_MESSAGES\") || \"Messages\",\n      [CometChatSearchFilter.Photos]: t(\"SEARCH_FILTER_PHOTOS\") || \"Photos\",\n      [CometChatSearchFilter.Unread]: t(\"SEARCH_FILTER_UNREAD\") || \"Unread\",\n      [CometChatSearchFilter.Videos]: t(\"SEARCH_FILTER_VIDEOS\") || \"Videos\",\n    };\n    return filterTextMap[filterId] || \"\";\n  };\n\n  const getFilterIcon = (filterId: CometChatSearchFilter) => {\n    const filterIconMap = {\n      [CometChatSearchFilter.Audio]: \"mic-fill\" as const,\n      [CometChatSearchFilter.Documents]: \"documents\" as const,\n      [CometChatSearchFilter.Groups]: \"group\" as const,\n      [CometChatSearchFilter.Links]: \"link\" as const,\n      [CometChatSearchFilter.Messages]: \"chat\" as const,\n      [CometChatSearchFilter.Photos]: \"photo\" as const,\n      [CometChatSearchFilter.Unread]: \"unread\" as const,\n      [CometChatSearchFilter.Videos]: \"videocam\" as const,\n      [CometChatSearchFilter.Conversations]: \"chat\" as const,\n    };\n    return filterIconMap[filterId];\n  };\n\n  const renderFilters = () => {\n    const visibleFilters = getVisibleFilters();\n\n    if (visibleFilters.length === 0) {\n      return null;\n    }\n\n    return (\n      <View style={[mergedStyles.filtersContainerStyle]}>\n        <View style={mergedStyles.filtersContentStyle}>\n          {visibleFilters.map((filterId) => {\n            const isActive = searchState.activeFilters.includes(filterId);\n            const iconName = getFilterIcon(filterId);\n            return (\n              <TouchableOpacity\n                key={filterId}\n                style={[\n                  mergedStyles.filterButtonStyle,\n                  isActive && mergedStyles.filterButtonActiveStyle\n                ]}\n                onPress={() => toggleFilter(filterId)}\n              >\n                <View style={mergedStyles.filterButtonContentStyle}>\n                  {iconName && (\n                    <Icon\n                      name={iconName}\n                      imageStyle={[\n                        mergedStyles.filterButtonIconStyle,\n                        isActive && mergedStyles.filterButtonIconActiveStyle\n                      ]}\n                      width={16}\n                      height={16}\n                    />\n                  )}\n                  <Text style={[\n                    mergedStyles.filterButtonTextStyle,\n                    isActive && mergedStyles.filterButtonTextActiveStyle\n                  ]}>\n                    {getFilterText(filterId)}\n                  </Text>\n                </View>\n              </TouchableOpacity>\n            );\n          })}\n        </View>\n      </View>\n    );\n  };\n\n  // ============================================================================\n  // CONVERSATIONS SEARCH LOGIC (moved from useCometChatSearchConversationsList.ts)\n  // ============================================================================\n  const [conversationState, setConversationState] = useState({\n    conversationList: [] as CometChat.Conversation[],\n    fetchState: States.loaded,\n    hasMoreResults: false,\n  });\n\n  const conversationRequestRef = useRef<CometChat.ConversationsRequest | null>(null);\n  const lastConversationSearchKeyword = useRef<string>(searchState.searchText);\n  const lastConversationActiveFilters = useRef<CometChatSearchFilter[]>(searchState.activeFilters);\n  const isConversationMoreResultsLoading = useRef<boolean>(false);\n\n  // ============================================================================\n  // MESSAGES SEARCH LOGIC (moved from useCometChatSearchMessagesList.ts)\n  // ============================================================================\n  const [messageState, setMessageState] = useState({\n    messageList: [] as CometChat.BaseMessage[],\n    fetchState: States.loaded,\n    hasMoreResults: false,\n  });\n\n  const messageRequestRef = useRef<CometChat.MessagesRequest | null>(null);\n  const lastMessageSearchKeyword = useRef<string>(searchState.searchText);\n  const lastMessageActiveFilters = useRef<CometChatSearchFilter[]>(searchState.activeFilters);\n  const isMessageMoreResultsLoading = useRef<boolean>(false);\n\n  // ============================================================================\n  // CONVERSATION SEARCH FUNCTIONS\n  // ============================================================================\n  const buildConversationsRequest = useCallback(() => {\n    let builder = conversationsRequestBuilder\n      ? Object.assign(Object.create(Object.getPrototypeOf(conversationsRequestBuilder)), conversationsRequestBuilder)\n      : new CometChat.ConversationsRequestBuilder();\n\n    if (searchState.searchText && searchState.searchText.trim() !== \"\") {\n      builder = builder.setSearchKeyword(searchState.searchText);\n    }\n\n    const limit = (guid || uid || searchState.activeFilters.length > 0) ? 30 : 3;\n    builder = builder.setLimit(limit);\n\n    // Apply filters - both can be active simultaneously for unread group conversations\n    if (searchState.activeFilters.includes(CometChatSearchFilter.Unread)) {\n      builder = builder.setUnread(true);\n    }\n\n    if (searchState.activeFilters.includes(CometChatSearchFilter.Groups)) {\n      builder = builder.setConversationType(ReceiverTypeConstants.group);\n    }\n\n    return builder.build();\n  }, [conversationsRequestBuilder, searchState.searchText, searchState.activeFilters, guid, uid]);\n\n  const loadMoreConversations = useCallback(async () => {\n    if (isConversationMoreResultsLoading.current || !conversationRequestRef.current || !conversationState.hasMoreResults) {\n      return;\n    }\n\n    try {\n      isConversationMoreResultsLoading.current = true;\n      const moreConversations = await conversationRequestRef.current.fetchNext();\n\n      if (moreConversations.length > 0) {\n        setConversationState(prev => ({\n          ...prev,\n          conversationList: [...prev.conversationList, ...moreConversations],\n          hasMoreResults: moreConversations.length === 3 || moreConversations.length === 30 // Check if more results might be available\n        }));\n      } else {\n        setConversationState(prev => ({\n          ...prev,\n          hasMoreResults: false\n        }));\n      }\n    } catch (error) {\n      errorHandler(error, \"loadMoreConversations\");\n    } finally {\n      isConversationMoreResultsLoading.current = false;\n    }\n  }, [errorHandler, conversationState.hasMoreResults]);\n\n  const searchConversations = useCallback(async () => {\n    try {\n      setConversationState(prev => ({ ...prev, fetchState: States.loading }));\n\n      if (!hasValidConversationSearchCriteria(searchState.searchText, searchState.activeFilters)) {\n        setConversationState(prev => ({\n          ...prev,\n          conversationList: [],\n          fetchState: States.loaded,\n          hasMoreResults: false\n        }));\n        return;\n      }\n\n      conversationRequestRef.current = buildConversationsRequest();\n      const conversations = await conversationRequestRef.current!.fetchNext();\n\n      if (conversations.length > 0) {\n        const limit = (guid || uid || searchState.activeFilters.length > 0) ? 30 : 3;\n        setConversationState({\n          conversationList: conversations,\n          fetchState: States.loaded,\n          hasMoreResults: conversations.length >= limit,\n        });\n      } else {\n        setConversationState({\n          conversationList: [],\n          fetchState: States.loaded,\n          hasMoreResults: false,\n        });\n      }\n    } catch (error) {\n      errorHandler(error, \"searchConversations\");\n      setConversationState(prev => ({ ...prev, fetchState: States.error }));\n    }\n  }, [searchState.searchText, searchState.activeFilters, buildConversationsRequest, guid, uid, errorHandler]);\n\n  // ============================================================================\n  // MESSAGE SEARCH FUNCTIONS\n  // ============================================================================\n  const buildMessagesRequest = useCallback(() => {\n    let builder = messagesRequestBuilder\n      ? Object.assign(Object.create(Object.getPrototypeOf(messagesRequestBuilder)), messagesRequestBuilder)\n      : new CometChat.MessagesRequestBuilder();\n\n    builder = builder.hideDeletedMessages(true);\n    const limit = (guid || uid || searchState.activeFilters.length > 0) ? 30 : 3;\n\n    if (!messagesRequestBuilder) {\n      builder = builder\n        .setCategories([MessageCategoryConstants.message, MessageCategoryConstants.custom])\n        .setTypes([MessageTypeConstants.text, MessageTypeConstants.image, MessageTypeConstants.video, MessageTypeConstants.audio, MessageTypeConstants.file])\n        .setLimit(limit);\n    }\n\n    if (searchState.searchText && searchState.searchText.trim() !== \"\") {\n      builder = builder.setSearchKeyword(searchState.searchText);\n    }\n\n    if (uid) {\n      builder = builder.setUID(uid);\n    } else if (guid) {\n      builder = builder.setGUID(guid);\n    }\n\n    if (searchState.activeFilters && searchState.activeFilters.length > 0) {\n      if (searchState.activeFilters.includes(CometChatSearchFilter.Links)) {\n        builder = builder.hasLinks(true);\n      }\n\n      const attachmentTypeMap = {\n        [CometChatSearchFilter.Photos]: CometChat.AttachmentType.IMAGE,\n        [CometChatSearchFilter.Videos]: CometChat.AttachmentType.VIDEO,\n        [CometChatSearchFilter.Documents]: CometChat.AttachmentType.FILE,\n        [CometChatSearchFilter.Audio]: CometChat.AttachmentType.AUDIO,\n      };\n\n      // Collect all attachment types for active filters\n      const activeAttachmentTypes: CometChat.AttachmentType[] = [];\n      for (const [filter, attachmentType] of Object.entries(attachmentTypeMap)) {\n        if (searchState.activeFilters.includes(filter as CometChatSearchFilter)) {\n          activeAttachmentTypes.push(attachmentType);\n        }\n      }\n\n      // Set all active attachment types (supports multiple types for paired filters)\n      if (activeAttachmentTypes.length > 0) {\n        builder = builder.setAttachmentTypes(activeAttachmentTypes);\n      }\n    }\n\n    return builder.build();\n  }, [messagesRequestBuilder, searchState.activeFilters, searchState.searchText, uid, guid]);\n\n  const loadMoreMessages = useCallback(async () => {\n    if (isMessageMoreResultsLoading.current || !messageRequestRef.current || !messageState.hasMoreResults) {\n      return;\n    }\n\n    try {\n      isMessageMoreResultsLoading.current = true;\n      const moreMessages = await messageRequestRef.current.fetchPrevious();\n\n      if (moreMessages.length > 0) {\n        \n        const filteredMoreMessages = moreMessages.filter(msg => {\n          const isDeleted = (msg as any).getDeletedAt && (msg as any).getDeletedAt() > 0;\n          const isActionMessage = msg.getCategory() === MessageCategoryConstants.action;\n          return !isDeleted && !isActionMessage;\n        });\n        \n        setMessageState(prev => ({\n          ...prev,\n          messageList: [...prev.messageList, ...filteredMoreMessages],\n          hasMoreResults: moreMessages.length === 3 || moreMessages.length === 30 // Check if more results might be available\n        }));\n      } else {\n        setMessageState(prev => ({\n          ...prev,\n          hasMoreResults: false\n        }));\n      }\n    } catch (error) {\n      errorHandler(error, \"loadMoreMessages\");\n    } finally {\n      isMessageMoreResultsLoading.current = false;\n    }\n  }, [errorHandler, messageState.hasMoreResults]);\n\n  const searchMessages = useCallback(async () => {\n    try {\n      setMessageState(prev => ({ ...prev, fetchState: States.loading }));\n\n      if (!hasValidMessageSearchCriteria(searchState.searchText, searchState.activeFilters)) {\n        setMessageState(prev => ({\n          ...prev,\n          messageList: [],\n          fetchState: States.loaded,\n          hasMoreResults: false\n        }));\n        return;\n      }\n\n      messageRequestRef.current = buildMessagesRequest();\n      const messages = await messageRequestRef.current!.fetchPrevious();\n\n      if (messages.length > 0) {\n        const reversedList = messages.reverse();\n        \n        const filteredMessages = reversedList.filter(msg => {\n          const isDeleted = (msg as any).getDeletedAt && (msg as any).getDeletedAt() > 0;\n          const isActionMessage = msg.getCategory() === MessageCategoryConstants.action;\n          return !isDeleted && !isActionMessage;\n        });\n        \n        const limit = (guid || uid || searchState.activeFilters.length > 0) ? 30 : 3;\n        setMessageState({\n          messageList: filteredMessages,\n          fetchState: States.loaded,\n          hasMoreResults: messages.length >= limit,\n        });\n      } else {\n        setMessageState({\n          messageList: [],\n          fetchState: States.loaded,\n          hasMoreResults: false,\n        });\n      }\n    } catch (error) {\n      errorHandler(error, \"searchMessages\");\n      setMessageState(prev => ({ ...prev, fetchState: States.error }));\n    }\n  }, [searchState.searchText, searchState.activeFilters, buildMessagesRequest, guid, uid, errorHandler]);\n\n  // ============================================================================\n  // SEARCH EFFECTS - Trigger searches when search text or filters change\n  // ============================================================================\n  useEffect(() => {\n    const hasKeywordChanged = searchState.searchText !== lastConversationSearchKeyword.current;\n    const haveFiltersChanged = JSON.stringify(searchState.activeFilters) !== JSON.stringify(lastConversationActiveFilters.current);\n\n    if (hasKeywordChanged || haveFiltersChanged) {\n      lastConversationSearchKeyword.current = searchState.searchText;\n      lastConversationActiveFilters.current = [...searchState.activeFilters];\n      searchConversations();\n    }\n  }, [searchState.searchText, searchState.activeFilters, searchConversations]);\n\n  useEffect(() => {\n    const hasKeywordChanged = searchState.searchText !== lastMessageSearchKeyword.current;\n    const haveFiltersChanged = JSON.stringify(searchState.activeFilters) !== JSON.stringify(lastMessageActiveFilters.current);\n\n    if (hasKeywordChanged || haveFiltersChanged) {\n      lastMessageSearchKeyword.current = searchState.searchText;\n      lastMessageActiveFilters.current = [...searchState.activeFilters];\n      searchMessages();\n    }\n  }, [searchState.searchText, searchState.activeFilters, searchMessages]);\n\n  const shouldRenderConversations = useCallback(() => {\n    // If searchIn is empty, search in both conversations and messages\n    const effectiveSearchIn = searchIn.length === 0\n      ? [CometChatSearchScope.Conversations, CometChatSearchScope.Messages]\n      : searchIn;\n\n    // Don't render conversations if searchIn doesn't include conversations scope\n    if (!effectiveSearchIn.includes(CometChatSearchScope.Conversations)) {\n      return false;\n    }\n\n    // Don't render conversations if uid or guid is present (searching within specific user/group)\n    if (uid || guid) {\n      return false;\n    }\n\n    // Define conversation filters\n    const conversationFilters = [CometChatSearchFilter.Unread, CometChatSearchFilter.Groups];\n\n    if (searchState.searchText && searchState.searchText.trim() !== \"\" && searchState.activeFilters.length === 0) {\n      return true;\n    }\n\n    if (searchState.activeFilters.length > 0) {\n      return searchState.activeFilters.some(filter => conversationFilters.includes(filter));\n    }\n\n    return false;\n  }, [searchState.activeFilters, searchState.searchText, uid, guid, searchIn]);\n\n  const shouldRenderMessages = useCallback(() => {\n    // If searchIn is empty, search in both conversations and messages\n    const effectiveSearchIn = searchIn.length === 0\n      ? [CometChatSearchScope.Conversations, CometChatSearchScope.Messages]\n      : searchIn;\n\n    // Don't render messages if searchIn doesn't include messages scope\n    if (!effectiveSearchIn.includes(CometChatSearchScope.Messages)) {\n      return false;\n    }\n\n    // Always render messages if uid or guid is present (searching within specific user/group)\n    if (uid || guid) {\n      return true;\n    }\n\n    // Define message filters\n    const messageFilters = [\n      CometChatSearchFilter.Photos,\n      CometChatSearchFilter.Videos,\n      CometChatSearchFilter.Documents,\n      CometChatSearchFilter.Audio,\n      CometChatSearchFilter.Links\n    ];\n\n    // Define conversation filters\n    const conversationFilters = [CometChatSearchFilter.Unread, CometChatSearchFilter.Groups];\n\n    if (searchState.searchText && searchState.searchText.trim() !== \"\" && searchState.activeFilters.length === 0) {\n      return true;\n    }\n\n    if (searchState.activeFilters.length > 0) {\n      // Show messages if any message filter is selected and no conversation filter is selected\n      return searchState.activeFilters.some(filter => messageFilters.includes(filter)) &&\n        !searchState.activeFilters.some(filter => conversationFilters.includes(filter));\n    }\n\n    return false;\n  }, [searchState.activeFilters, searchState.searchText, uid, guid, searchIn]);\n\n  // Handle scroll-based fetching for messages\n  const handleScrollEnd = useCallback((event: any) => {\n    if (!shouldRenderMessages() || !messageState.hasMoreResults) return;\n\n    const contentOffsetY = event.nativeEvent.contentOffset.y;\n    const contentHeight = event.nativeEvent.contentSize.height;\n    const layoutHeight = event.nativeEvent.layoutMeasurement.height;\n\n    if (contentOffsetY + layoutHeight >= contentHeight - 10) {\n      loadMoreMessages();\n    }\n  }, [shouldRenderMessages, messageState.hasMoreResults, loadMoreMessages]);\n\n  // Get status indicator for conversation\n  const getStatusIndicator = (conv: CometChat.Conversation) => {\n    const withObj = conv.getConversationWith();\n\n    if (withObj instanceof CometChat.Group) {\n      if (withObj.getType() === GroupTypeConstants.password) return \"password\";\n      if (withObj.getType() === GroupTypeConstants.private) return \"private\";\n    } else if (withObj instanceof CometChat.User) {\n      if (withObj.getStatus() === 'online' && !withObj.getHasBlockedMe() && !withObj.getBlockedByMe()) {\n        return \"online\";\n      }\n      return \"offline\";\n    }\n    return undefined;\n  };\n\n  // Render conversation avatar\n  const renderConversationLeadingView = (conversation: CometChat.Conversation) => {\n    const withObj = conversation.getConversationWith();\n    const avatarURL = withObj instanceof CometChat.User ? withObj.getAvatar() : withObj.getIcon();\n    const name = withObj.getName();\n\n    return (\n      <View style={{ position: 'relative' }}>\n        <View style={{ height: 48, width: 48 }}>\n          <CometChatAvatar\n            image={{ uri: avatarURL }}\n            name={name}\n          />\n          <View style={{\n            position: 'absolute',\n            bottom: 0,\n            right: 0,\n          }}>\n            <CometChatStatusIndicator\n              type={getStatusIndicator(conversation)}\n            />\n          </View>\n        </View>\n      </View>\n    );\n  };\n\n  // Render conversation trailing view (date and badge)\n  const renderConversationTrailingView = (conversation: CometChat.Conversation) => {\n    const timestamp = conversation.getLastMessage()?.getSentAt();\n    if (!timestamp) return null;\n\n    return (\n      <View style={mergedStyles.conversationItemStyle?.trailingContainerStyle}>\n        <CometChatDate\n          timeStamp={timestamp * 1000}\n          pattern={\"conversationDate\"}\n        />\n        <CometChatBadge\n          count={conversation.getUnreadMessageCount()}\n        />\n      </View>\n    );\n  };\n\n  // Get file type icon based on file extension\n  const getFileTypeIcon = (fileName: string): 'audio-file-type' | 'image-file-type' | 'video-file-type' | 'pdf-file-type' | 'presentation-file-type' | 'spreadsheet-file-type' | 'text-file-type' | 'zip-file-type' | 'document-file-type' | 'unknown-file-type' => {\n    if (!fileName) return 'unknown-file-type';\n\n    const extension = fileName.toLowerCase().split('.').pop() || '';\n\n    // Audio files\n    if (['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac'].includes(extension)) {\n      return 'audio-file-type';\n    }\n\n    // Image files\n    if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(extension)) {\n      return 'image-file-type';\n    }\n\n    // Video files\n    if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'].includes(extension)) {\n      return 'video-file-type';\n    }\n\n    // PDF files\n    if (extension === 'pdf') {\n      return 'pdf-file-type';\n    }\n\n    // Presentation files\n    if (['ppt', 'pptx', 'key'].includes(extension)) {\n      return 'presentation-file-type';\n    }\n\n    // Spreadsheet files\n    if (['xls', 'xlsx', 'csv', 'numbers'].includes(extension)) {\n      return 'spreadsheet-file-type';\n    }\n\n    // Text/Document files\n    if (['txt', 'rtf', 'doc', 'docx', 'pages'].includes(extension)) {\n      return 'text-file-type';\n    }\n\n    // ZIP/Archive files\n    if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) {\n      return 'zip-file-type';\n    }\n\n    // Default document type for other files\n    return 'document-file-type';\n  };\n\n  // Format mentions in raw message text: convert <@all:alias> to @alias\n  // and <@uid:UID> to @Name when message provides mentioned users.\n  // Also strips markdown syntax for clean search result previews.\n  const formatMentionsInText = (rawText: any, message?: CometChat.BaseMessage) => {\n    if (typeof rawText !== 'string') return rawText;\n\n    // Strip markdown syntax while preserving mention tokens for processing below.\n    let text = stripMarkdown(rawText).replace(/<@all:(.*?)>/g, '@$1');\n\n    try {\n      const mentionedUsers: CometChat.User[] = (message && (message).getMentionedUsers && (message).getMentionedUsers()) || [];\n      if (mentionedUsers && mentionedUsers.length > 0) {\n        mentionedUsers.forEach((u: CometChat.User) => {\n          const uid = u.getUid();\n          const name = u.getName();\n          if (uid && name) {\n            const uidRegex = new RegExp(`<@uid:${uid}>`, 'g');\n            text = text.replace(uidRegex, `@${name}`);\n          }\n        });\n      }\n    } catch (e) {\n      console.log(e)\n    }\n\n    return text;\n  };\n\n  // Render message leading view (for audio/file icons)\n  const renderMessageLeadingView = (message: CometChat.BaseMessage) => {\n    const messageType = message.getType();\n\n    switch (messageType) {\n      case 'audio': {\n        return (\n          <View style={mergedStyles.messageItemStyle?.iconContainerStyle}>\n            <View style={[mergedStyles.messageItemStyle?.audioPreviewStyle, {\n              backgroundColor: theme.color.primary,\n              borderRadius: 1000,\n              justifyContent: 'center',\n              alignItems: 'center',\n              height: 32,\n              width: 32,\n            }]}>\n              <Icon\n                name='play-arrow-fill'\n                size={theme.spacing.spacing.s7}\n                height={32}\n                width={32}\n                color={theme.color.staticWhite}\n              />\n            </View>\n          </View>\n        );\n      }\n\n      case 'file': {\n        const fileMessage = message as CometChat.MediaMessage;\n        const attachment = fileMessage.getAttachment();\n        const fileName = attachment?.getName?.() || '';\n        const fileTypeIcon = getFileTypeIcon(fileName);\n\n        return (\n          <View style={mergedStyles.messageItemStyle?.iconContainerStyle}>\n            <Icon\n              name={fileTypeIcon}\n              size={48}\n              height={48}\n              width={48}\n              color={theme.color.iconSecondary}\n            />\n          </View>\n        );\n      }\n\n      case 'text': {\n        // Check if this is a text message with link metadata (link preview)\n        const textMessage = message as CometChat.TextMessage;\n        const metadata = textMessage.getMetadata();\n        const linkPreview = metadata && (metadata as any)['@injected'] && (metadata as any)['@injected']['extensions'] && (metadata as any)['@injected']['extensions']['link-preview'];\n\n        if (linkPreview && linkPreview.links && linkPreview.links.length > 0) {\n          const firstLink = linkPreview.links[0];\n          const thumbnailUrl = firstLink.image || firstLink.favicon;\n\n          if (thumbnailUrl) {\n            return (\n              <LinkPreviewImage\n                uri={thumbnailUrl}\n                fallbackUri={firstLink.favicon !== thumbnailUrl ? firstLink.favicon : undefined}\n                mergedStyles={mergedStyles}\n                theme={theme}\n              />\n            );\n          } else {\n            // Fallback to link icon if no thumbnail\n            return <LinkFallbackIcon mergedStyles={mergedStyles} theme={theme} />;\n          }\n        }\n\n        // Show link icon for text messages in Links filter that lack preview metadata\n        if (searchState.activeFilters.includes(CometChatSearchFilter.Links)) {\n          return <LinkFallbackIcon mergedStyles={mergedStyles} theme={theme} />;\n        }\n\n        return null;\n      }\n\n      default:\n        return null;\n    }\n  };\n\n  // Render message trailing view (content preview or date)\n  const renderMessageTrailingView = (message: CometChat.BaseMessage) => {\n    const timestamp = message.getSentAt();\n    if (!timestamp) return null;\n\n    const messageType = message.getType();\n\n    // Handle different message types\n    switch (messageType) {\n      case 'image': {\n        const imageMessage = message as CometChat.MediaMessage;\n        const attachment = imageMessage.getAttachment();\n        const imageUrl = attachment && (attachment as any).url;\n        if (imageUrl) {\n          return (\n            <View style={mergedStyles.messageItemStyle?.previewContainerStyle}>\n              <Image\n                source={{ uri: imageUrl }}\n                style={mergedStyles.messageItemStyle?.previewImageStyle}\n                resizeMode=\"cover\"\n                progressiveRenderingEnabled={true}\n                fadeDuration={0}\n              />\n            </View>\n          );\n        }\n        break;\n      }\n\n      case 'video': {\n        const videoMessage = message as CometChat.MediaMessage;\n        const thumbnailImage = checkThumbnail(videoMessage);\n        \n        if (thumbnailImage.uri) {\n          return (\n            <VideoThumbnail\n              thumbnailUrl={thumbnailImage}\n              mergedStyles={mergedStyles}\n            />\n          );\n        }\n        break;\n      }\n\n\n\n      default:\n        // For text messages and other types, show timestamp\n        return (\n          <View style={mergedStyles.messageItemStyle?.trailingContainerStyle}>\n            <CometChatDate\n              timeStamp={timestamp * 1000}\n              pattern={\"dayDateFormat\"}\n            />\n          </View>\n        );\n    }\n\n    // Fallback to timestamp\n    return (\n      <View style={mergedStyles.messageItemStyle?.trailingContainerStyle}>\n        <CometChatDate\n          timeStamp={timestamp * 1000}\n          pattern={\"dayDateFormat\"}\n        />\n      </View>\n    );\n  };\n\n  // Memoized trailing view component\n  const MemoizedTrailingView = React.memo(({ message }: { message: CometChat.BaseMessage }) =>\n    renderMessageTrailingView(message),\n    (prev, next) => prev.message.getId() === next.message.getId()\n  );\n\n  // Render conversations section\n  const renderConversationsSection = () => {\n    const { conversationList, fetchState } = conversationState;\n\n    if (fetchState === States.loading && conversationList.length === 0) {\n      return loadingView ? loadingView() : <Skeleton />;\n    }\n\n    if (fetchState === States.empty || conversationList.length === 0) {\n      return null; // Don't show empty conversations section\n    }\n\n    if (fetchState === States.error) {\n      if (errorView) {\n        return errorView();\n      }\n\n      return (\n        <View style={[mergedStyles.errorStateStyle?.containerStyle]}>\n          <Icon\n            name=\"empty-search\"\n            size={120}\n            height={120}\n            width={120}\n            imageStyle={mergedStyles.errorStateStyle?.iconStyle}\n            containerStyle={mergedStyles.errorStateStyle?.iconContainerStyle}\n          />\n          <Text style={[mergedStyles.errorStateStyle?.titleStyle]}>\n            {t(\"SEARCH_ERROR_LOADING_CONVERSATIONS\") || \"Error Loading Conversations\"}\n          </Text>\n          <Text style={[mergedStyles.errorStateStyle?.subtitleStyle]}>\n            {t(\"SEARCH_TRY_AGAIN\") || \"Please try again later\"}\n          </Text>\n        </View>\n      );\n    }\n\n    return (\n      <View>\n        <Text style={mergedStyles.sectionTitleStyle}>{t(\"CHATS\") || \"Chats\"}</Text>\n        <FlatList\n          data={conversationList}\n          keyExtractor={(item) => item.getConversationId()}\n          scrollEnabled={false}\n          initialNumToRender={10}\n          maxToRenderPerBatch={5}\n          windowSize={5}\n          removeClippedSubviews={true}\n          updateCellsBatchingPeriod={50}\n          renderItem={({ item: conversation }) => {\n            // Use custom conversation item view if provided\n            if (conversationItemView) {\n              return (\n                <TouchableOpacity\n                  onPress={() => onConversationClicked?.(conversation, searchState.searchText)}\n                >\n                  {conversationItemView(conversation, searchState.searchText)}\n                </TouchableOpacity>\n              );\n            }\n\n            // Default conversation item rendering\n            return (\n              <TouchableOpacity\n                style={mergedStyles.conversationItemStyle?.containerStyle}\n                onPress={() => onConversationClicked?.(conversation, searchState.searchText)}\n              >\n                {renderConversationLeadingView(conversation)}\n                <View style={mergedStyles.conversationItemStyle?.contentStyle}>\n                  <Text style={mergedStyles.conversationItemStyle?.titleStyle} numberOfLines={1}>\n                    {conversation.getConversationWith().getName()}\n                  </Text>\n                  <Text style={mergedStyles.conversationItemStyle?.subtitleStyle} numberOfLines={2}>\n                      {formatMentionsInText((conversation.getLastMessage())?.getText?.(), conversation.getLastMessage())}\n                  </Text>\n                </View>\n                {renderConversationTrailingView(conversation)}\n              </TouchableOpacity>\n            );\n          }}\n          ListFooterComponent={\n            conversationState.hasMoreResults ? (\n              <TouchableOpacity\n                style={mergedStyles.seeMoreButtonStyle}\n                onPress={loadMoreConversations}\n                disabled={isConversationMoreResultsLoading.current}\n              >\n                <Text style={mergedStyles.seeMoreTextStyle}>\n                  {isConversationMoreResultsLoading.current ? t(\"SEARCH_LOADING\") || 'Loading...' : t(\"SEARCH_SEE_MORE\") || 'See More'}\n                </Text>\n              </TouchableOpacity>\n            ) : null\n          }\n        />\n      </View>\n    );\n  };\n\n  // Helper to format date for headers\n  const getDateHeaderDate = useCallback((timestamp: number) => {\n    return new Date(timestamp * 1000).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });\n  }, []);\n\n  // Render messages section\n  const renderMessagesSection = () => {\n    const { messageList, fetchState } = messageState;\n\n    if (fetchState === States.loading && messageList.length === 0) {\n      return loadingView ? loadingView() : <Skeleton />;\n    }\n\n    if (fetchState === States.empty || messageList.length === 0) {\n      return null; // Don't show empty messages section\n    }\n\n    if (fetchState === States.error) {\n      if (errorView) {\n        return errorView();\n      }\n\n      return (\n        <View style={[mergedStyles.errorStateStyle?.containerStyle]}>\n          <Icon\n            name=\"empty-search\"\n            size={120}\n            height={120}\n            width={120}\n            imageStyle={mergedStyles.errorStateStyle?.iconStyle}\n            containerStyle={mergedStyles.errorStateStyle?.iconContainerStyle}\n          />\n          <Text style={[mergedStyles.errorStateStyle?.titleStyle]}>\n            {t(\"SEARCH_ERROR_LOADING_MESSAGES\") || \"Error Loading M essages\"}\n          </Text>\n          <Text style={[mergedStyles.errorStateStyle?.subtitleStyle]}>\n            {t(\"SEARCH_TRY_AGAIN\") || \"Please try again later\"}\n          </Text>\n        </View>\n      );\n    }\n\n    return (\n      <View>\n        <Text style={mergedStyles.sectionTitleStyle}>{t(\"MESSAGES\") || \"Messages\"}</Text>\n        <FlatList\n          data={messageList}\n          keyExtractor={(item, index) => `${item.getId()}-${index}`}\n          initialNumToRender={10}\n          maxToRenderPerBatch={5}\n          windowSize={5}\n          removeClippedSubviews={true}\n          updateCellsBatchingPeriod={50}\n          renderItem={({ item: message, index }) => {\n            const messageType = message.getType();\n\n            // Date header logic for media items\n            let dateHeader: React.ReactNode = null;\n            if (['image', 'video'].includes(messageType)) {\n              const sentAt = message.getSentAt();\n              const currentDateStr = getDateHeaderDate(sentAt);\n              const prevMessage = index > 0 ? messageList[index - 1] : null;\n              const prevDateStr = prevMessage ? getDateHeaderDate(prevMessage.getSentAt()) : null;\n\n              if (currentDateStr !== prevDateStr) {\n                dateHeader = (\n                  <CometChatDate\n                    timeStamp={sentAt * 1000}\n                    customDateString={currentDateStr}\n                    style={{\n                      textStyle: mergedStyles.sectionTitleStyle\n                    }}\n                  />\n                );\n              }\n            }\n\n            // Check if message is a text message with link preview\n            const isLinkMessage = messageType === 'text' && (() => {\n              const textMessage = message as CometChat.TextMessage;\n              const metadata = textMessage.getMetadata();\n              return metadata && (metadata as any)['@injected'] && (metadata as any)['@injected']['extensions'] && (metadata as any)['@injected']['extensions']['link-preview'];\n            })();\n\n            // Determine which custom view to use based on message type\n            let customView: (() => React.ReactElement) | undefined;\n\n            if (isLinkMessage && linkMessageItemView) {\n              customView = () => linkMessageItemView(message, searchState.searchText);\n            } else if (messageType === 'text' && textMessageItemView) {\n              customView = () => textMessageItemView(message, searchState.searchText);\n            } else if (messageType === 'image' && imageMessageItemView) {\n              customView = () => imageMessageItemView(message, searchState.searchText);\n            } else if (messageType === 'video' && videoMessageItemView) {\n              customView = () => videoMessageItemView(message, searchState.searchText);\n            } else if (messageType === 'audio' && audioMessageItemView) {\n              customView = () => audioMessageItemView(message, searchState.searchText);\n            } else if (messageType === 'file' && documentMessageItemView) {\n              customView = () => documentMessageItemView(message, searchState.searchText);\n            }\n\n            // Use custom view if available\n            if (customView) {\n              return (\n                <View>\n                  {dateHeader}\n                  <TouchableOpacity\n                    onPress={() => onMessageClicked?.(message, searchState.searchText)}\n                  >\n                    {customView()}\n                  </TouchableOpacity>\n                </View>\n              );\n            }\n\n            // Default message item rendering\n            return (\n              <View>\n                {dateHeader}\n                <TouchableOpacity\n                  style={mergedStyles.messageItemStyle?.containerStyle}\n                  onPress={() => onMessageClicked?.(message, searchState.searchText)}\n                >\n                  {renderMessageLeadingView(message)}\n                  <View style={[\n                    mergedStyles.messageItemStyle?.contentStyle,\n                    (message.getType() === 'image' || message.getType() === 'video') && mergedStyles.messageItemStyle?.textContainerStyle\n                  ]}>\n                    <Text style={mergedStyles.messageItemStyle?.titleStyle} numberOfLines={1}>\n                      {message.getSender().getName()}\n                    </Text>\n                    <Text style={mergedStyles.messageItemStyle?.subtitleStyle} numberOfLines={2}>\n                      {(() => {\n                        const messageType = message.getType();\n\n                        // For media messages (image, video, file, audio), show file name\n                        if (['image', 'video', 'file', 'audio'].includes(messageType)) {\n                          const mediaMessage = message as CometChat.MediaMessage;\n                          const attachment = mediaMessage.getAttachment?.();\n                          return attachment?.getName?.() || messageType;\n                        }\n\n                        // For text messages, show the text content\n                        if (messageType === 'text') {\n                          const raw = (message as CometChat.TextMessage).getText?.();\n                          const formatted = formatMentionsInText(raw, message);\n                          return formatted || messageType || 'Message';\n                        }\n\n                        // For custom messages, try to localize the type\n                        const customTypeKey = `CUSTOM_MESSAGE_${messageType.toUpperCase()}`;\n                        const localizedType = t(customTypeKey);\n                        \n                        // If localization exists and is different from key, use it\n                        if (localizedType && localizedType !== customTypeKey) {\n                          return localizedType;\n                        }\n                        \n                        // Handle specific extension types mapping to existing keys\n                        if (messageType === 'extension_poll') return t(\"CUSTOM_MESSAGE_POLL\") || \"Poll\";\n                        if (messageType === 'extension_sticker') return t(\"CUSTOM_MESSAGE_STICKER\") || \"Sticker\";\n                        if (messageType === 'extension_whiteboard') return t(\"CUSTOM_MESSAGE_WHITEBOARD\") || \"Whiteboard\";\n                        if (messageType === 'extension_document') return t(\"CUSTOM_MESSAGE_DOCUMENT\") || \"Document\";\n                        if (messageType === 'meeting') return t(\"meeting\") || \"Meeting\";\n\n                        return messageType || 'Message';\n                      })()}\n                    </Text>\n                  </View>\n                  <MemoizedTrailingView message={message} />\n                </TouchableOpacity>\n              </View>\n            );\n          }}\n        />\n      </View>\n    );\n  };\n\n  const renderResults = () => {\n    const conversationsRendered = shouldRenderConversations();\n    const messagesRendered = shouldRenderMessages();\n\n    // Initial empty state - no search text and no filters\n    if ((!searchState.searchText || searchState.searchText.trim() === \"\") && searchState.activeFilters.length === 0) {\n      if (emptyView) {\n        return emptyView();\n      }\n\n      return (\n        <View style={[mergedStyles.emptyStateStyle?.containerStyle]}>\n          <Icon\n            name=\"empty-search\"\n            size={120}\n            height={120}\n            width={120}\n            imageStyle={mergedStyles.emptyStateStyle?.iconStyle}\n            containerStyle={mergedStyles.emptyStateStyle?.iconContainerStyle}\n          />\n          <Text style={[mergedStyles.emptyStateStyle?.titleStyle]}>\n            {t(\"SEARCH_NO_RESULTS_TITLE\") || \"No Results\"}\n          </Text>\n          <Text style={[mergedStyles.emptyStateStyle?.subtitleStyle]}>\n            {t(\"SEARCH_NO_RESULTS_SUBTITLE\") || \"Start typing to search for messages and conversations\"}\n          </Text>\n        </View>\n      );\n    }\n\n    // Show loading shimmer if we have search/filters active and both sections are loading\n    if ((searchState.searchText.trim() !== \"\" || searchState.activeFilters.length > 0) &&\n      (conversationState.fetchState === States.loading || messageState.fetchState === States.loading) &&\n      conversationState.conversationList.length === 0 &&\n      messageState.messageList.length === 0) {\n      return loadingView ? loadingView() : <Skeleton />;\n    }\n\n    // Check if we have search text or filters but no actual results\n    if ((searchState.searchText.trim() !== \"\" || searchState.activeFilters.length > 0) &&\n      conversationState.conversationList.length === 0 &&\n      messageState.messageList.length === 0 &&\n      conversationState.fetchState !== States.loading &&\n      messageState.fetchState !== States.loading &&\n      conversationState.fetchState !== States.error &&\n      messageState.fetchState !== States.error) {\n      // No results found state\n      if (emptyView) {\n        return emptyView();\n      }\n\n      return (\n        <View style={[mergedStyles.emptyStateStyle?.containerStyle]}>\n          <Icon\n            name=\"empty-search\"\n            size={120}\n            height={120}\n            width={120}\n            imageStyle={mergedStyles.emptyStateStyle?.iconStyle}\n            containerStyle={mergedStyles.emptyStateStyle?.iconContainerStyle}\n          />\n          <Text style={[mergedStyles.emptyStateStyle?.titleStyle]}>\n            {t(\"SEARCH_NO_RESULTS_FOUND_TITLE\") || \"No Results Found\"}\n          </Text>\n          <Text style={[mergedStyles.emptyStateStyle?.subtitleStyle]}>\n            {t(\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\") || \"We couldn't find any matches. Please try a different search keyword.\"}\n          </Text>\n        </View>\n      );\n    }\n\n    // Show search results with actual item lists - USING SINGLE FLATLIST FOR VIRTUALIZATION\n    // Create combined data array with section headers\n    type ListItem = \n      | { type: 'conversation-header' }\n      | { type: 'conversation'; data: CometChat.Conversation }\n      | { type: 'conversation-footer' }\n      | { type: 'message-header' }\n      | { type: 'message'; data: CometChat.BaseMessage; index: number }\n      | { type: 'message-footer' };\n\n    const combinedData: ListItem[] = [];\n\n    // Add conversations section if needed\n    if (conversationsRendered && conversationState.conversationList.length > 0) {\n      combinedData.push({ type: 'conversation-header' });\n      conversationState.conversationList.forEach(conv => {\n        combinedData.push({ type: 'conversation', data: conv });\n      });\n      if (conversationState.hasMoreResults) {\n        combinedData.push({ type: 'conversation-footer' });\n      }\n    }\n\n    // Add messages section if needed\n    if (messagesRendered && messageState.messageList.length > 0) {\n      combinedData.push({ type: 'message-header' });\n      messageState.messageList.forEach((msg, index) => {\n        combinedData.push({ type: 'message', data: msg, index });\n      });\n    }\n\n    const keyExtractor = (item: ListItem, index: number) => {\n      if (item.type === 'conversation') {\n        return `conversation-${item.data.getConversationId()}`;\n      } else if (item.type === 'message') {\n        return `message-${item.data.getId()}-${index}`;\n      }\n      return `${item.type}-${index}`;\n    };\n\n    const renderCombinedItem = ({ item }: { item: ListItem }) => {\n      switch (item.type) {\n        case 'conversation-header':\n          return <Text style={mergedStyles.sectionTitleStyle}>{t(\"CHATS\") || \"Chats\"}</Text>;\n        \n        case 'conversation':\n          return (\n            <ConversationItem\n              conversation={item.data}\n              searchText={searchState.searchText}\n              onPress={onConversationClicked || (() => {})}\n              mergedStyles={mergedStyles}\n              theme={theme}\n              conversationItemView={conversationItemView}\n            />\n          );\n        \n        case 'conversation-footer':\n          return (\n            <TouchableOpacity\n              style={mergedStyles.seeMoreButtonStyle}\n              onPress={loadMoreConversations}\n              disabled={isConversationMoreResultsLoading.current}\n            >\n              <Text style={mergedStyles.seeMoreTextStyle}>\n                {isConversationMoreResultsLoading.current ? t(\"SEARCH_LOADING\") || 'Loading...' : t(\"SEARCH_SEE_MORE\") || 'See More'}\n              </Text>\n            </TouchableOpacity>\n          );\n        \n        case 'message-header':\n          return <Text style={mergedStyles.sectionTitleStyle}>{t(\"MESSAGES\") || \"Messages\"}</Text>;\n        \n        case 'message': {\n          const message = item.data;\n          const messageIndex = item.index;\n          const messageType = message.getType();\n\n          // Date header logic for media items\n          let dateHeader: React.ReactNode = null;\n          if (['image', 'video'].includes(messageType)) {\n            const sentAt = message.getSentAt();\n            const currentDateStr = getDateHeaderDate(sentAt);\n            const prevItem = messageIndex > 0 ? messageState.messageList[messageIndex - 1] : null;\n            const prevDateStr = prevItem ? getDateHeaderDate(prevItem.getSentAt()) : null;\n\n            if (currentDateStr !== prevDateStr) {\n              dateHeader = (\n                <CometChatDate\n                  timeStamp={sentAt * 1000}\n                  customDateString={currentDateStr}\n                  style={{\n                    textStyle: mergedStyles.sectionTitleStyle\n                  }}\n                />\n              );\n            }\n          }\n\n          // Check if message is a text message with link preview\n          const isLinkMessage = messageType === 'text' && (() => {\n            const textMessage = message as CometChat.TextMessage;\n            const metadata = textMessage.getMetadata();\n            return metadata && (metadata as any)['@injected'] && (metadata as any)['@injected']['extensions'] && (metadata as any)['@injected']['extensions']['link-preview'];\n          })();\n\n          // Determine which custom view to use based on message type\n          let customView: (() => React.ReactElement) | undefined;\n\n          if (isLinkMessage && linkMessageItemView) {\n            customView = () => linkMessageItemView(message, searchState.searchText);\n          } else if (messageType === 'text' && textMessageItemView) {\n            customView = () => textMessageItemView(message, searchState.searchText);\n          } else if (messageType === 'image' && imageMessageItemView) {\n            customView = () => imageMessageItemView(message, searchState.searchText);\n          } else if (messageType === 'video' && videoMessageItemView) {\n            customView = () => videoMessageItemView(message, searchState.searchText);\n          } else if (messageType === 'audio' && audioMessageItemView) {\n            customView = () => audioMessageItemView(message, searchState.searchText);\n          } else if (messageType === 'file' && documentMessageItemView) {\n            customView = () => documentMessageItemView(message, searchState.searchText);\n          }\n\n          // Use custom view if available\n          if (customView) {\n            return (\n              <View>\n                {dateHeader}\n                <TouchableOpacity\n                  onPress={() => onMessageClicked?.(message, searchState.searchText)}\n                >\n                  {customView()}\n                </TouchableOpacity>\n              </View>\n            );\n          }\n\n          // Default message item rendering\n          return (\n            <View>\n              {dateHeader}\n              <TouchableOpacity\n                style={mergedStyles.messageItemStyle?.containerStyle}\n                onPress={() => onMessageClicked?.(message, searchState.searchText)}\n              >\n                {renderMessageLeadingView(message)}\n                <View style={[\n                  mergedStyles.messageItemStyle?.contentStyle,\n                  (message.getType() === 'image' || message.getType() === 'video') && mergedStyles.messageItemStyle?.textContainerStyle\n                ]}>\n                  <Text style={mergedStyles.messageItemStyle?.titleStyle} numberOfLines={1}>\n                    {message.getSender().getName()}\n                  </Text>\n                  <Text style={mergedStyles.messageItemStyle?.subtitleStyle} numberOfLines={2}>\n                    {(() => {\n                      const msgType = message.getType();\n\n                      // For media messages (image, video, file, audio), show file name\n                      if (['image', 'video', 'file', 'audio'].includes(msgType)) {\n                        const mediaMessage = message as CometChat.MediaMessage;\n                        const attachment = mediaMessage.getAttachment?.();\n                        return attachment?.getName?.() || msgType;\n                      }\n\n                      // For text messages, show the text content\n                      if (msgType === 'text') {\n                        const raw = (message as CometChat.TextMessage).getText?.();\n                        const formatted = formatMentionsInText(raw, message);\n                        return formatted || msgType || 'Message';\n                      }\n\n                      // For custom messages, try to localize the type\n                      const customTypeKey = `CUSTOM_MESSAGE_${msgType.toUpperCase()}`;\n                      const localizedType = t(customTypeKey);\n                      \n                      // If localization exists and is different from key, use it\n                      if (localizedType && localizedType !== customTypeKey) {\n                        return localizedType;\n                      }\n                      \n                      // Handle specific extension types mapping to existing keys\n                      if (msgType === 'extension_poll') return t(\"CUSTOM_MESSAGE_POLL\") || \"Poll\";\n                      if (msgType === 'extension_sticker') return t(\"CUSTOM_MESSAGE_STICKER\") || \"Sticker\";\n                      if (msgType === 'extension_whiteboard') return t(\"CUSTOM_MESSAGE_WHITEBOARD\") || \"Whiteboard\";\n                      if (msgType === 'extension_document') return t(\"CUSTOM_MESSAGE_DOCUMENT\") || \"Document\";\n                      if (msgType === 'meeting') return t(\"meeting\") || \"Meeting\";\n\n                      return msgType || 'Message';\n                    })()}\n                  </Text>\n                </View>\n                <MemoizedTrailingView message={message} />\n              </TouchableOpacity>\n            </View>\n          );\n        }\n        \n        default:\n          return null;\n      }\n    };\n\n    return (\n      <FlatList\n        data={combinedData}\n        keyExtractor={keyExtractor}\n        renderItem={renderCombinedItem}\n        style={[mergedStyles.resultsContainerStyle]}\n        contentContainerStyle={{ paddingHorizontal: theme.spacing.padding.p4 }}\n        showsVerticalScrollIndicator={false}\n        initialNumToRender={10}\n        maxToRenderPerBatch={10}\n        windowSize={10}\n        removeClippedSubviews={true}\n        updateCellsBatchingPeriod={100}\n        onEndReached={() => {\n          // Load more messages if we're at the end and messages section is visible\n          if (messagesRendered && messageState.hasMoreResults) {\n            loadMoreMessages();\n          }\n        }}\n        onEndReachedThreshold={0.5}\n      />\n    );\n  };\n\n  return (\n    <View style={[mergedStyles.containerStyle]}>\n      {renderHeader()}\n      {renderFilters()}\n      {renderResults()}\n    </View>\n  );\n};\n\nexport default CometChatSearch;"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatSearch/SearchConstants.ts",
    "content": "/**\n * Search related enums and constants for CometChatSearch component\n */\n\n/**\n * Enum for different search scopes\n */\nexport enum CometChatSearchScope {\n  Conversations = \"conversations\",\n  Messages = \"messages\",\n  All = \"all\"\n}\n\n/**\n * Enum for different search filters\n */\nexport enum CometChatSearchFilter {\n  Conversations = \"conversations\",\n  Messages = \"messages\",\n  Unread = \"unread\",\n  Groups = \"groups\",\n  Photos = \"photos\",\n  Videos = \"videos\",\n  Audio = \"audio\",\n  Documents = \"documents\",\n  Links = \"links\"\n}\n\n/**\n * Enum for component states\n */\nexport enum States {\n  loading = \"loading\",\n  loaded = \"loaded\",\n  empty = \"empty\",\n  error = \"error\"\n}\n\n/**\n * Interface for search filter items\n */\nexport interface CometChatSearchFilterItem {\n  id: CometChatSearchFilter;\n  title: string;\n  active?: boolean;\n}"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatSearch/Skeleton.tsx",
    "content": "/**\n * Skeleton.tsx\n *\n * A lightweight, theme-aware shimmer \"skeleton\" placeholder for\n * CometChat search results (conversations and messages).\n * ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––\n *  • 10 rows (top + bottom layers) drawn with react-native-svg.\n *  • Two Animated views slide across on a loop to create\n *    the shimmer illusion.\n *  • Colours, speed, opacity and gradient come from the active\n *    CometChatTheme but can be overridden per-instance via `style`.\n */\n\nimport React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, G, LinearGradient, Path, Stop } from \"react-native-svg\";\n\nimport { useTheme } from \"@cometchat/chat-uikit-react-native\";\nimport { CometChatTheme } from \"@cometchat/chat-uikit-react-native/src/theme/type\";\n\n// ───────────────────────────────────────────────────────────────\n// Consts & Types\n// ───────────────────────────────────────────────────────────────\nconst { width: screenWidth } = Dimensions.get(\"window\");\nconst SKELETON_ROW_COUNT = 10; // How many rows to render for search results\n\ntype SkeletonStyle = CometChatTheme[\"conversationStyles\"][\"skeletonStyle\"];\n\ninterface SkeletonProps {\n  style?: SkeletonStyle; // Optional per-instance overrides\n}\n\n// ───────────────────────────────────────────────────────────────\n// Reusable SVG snippets for search results\n// ───────────────────────────────────────────────────────────────\n\n/**\n * Bottom SVG layer – pure grey rectangles that will be *under* the shimmer.\n */\nconst SkeletonItemBottom: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n\n  return (\n    <Svg\n      height={screenWidth / 5}\n      viewBox='0 0 360 72'\n      fill='none'\n      preserveAspectRatio='xMidYMid meet'\n    >\n      <G>\n        {/* Avatar circle */}\n        <Path d='M64 12H16V60H64V12Z' fill='url(#paint0_linear)' />\n      </G>\n      {/* Main content line */}\n      <Path d='M236 16.5H76V35.5H236V35.5Z' fill='url(#paint0_linear)' />\n      {/* Trailing content (timestamp/badge area) */}\n      <Path d='M344 16.5H284V35.5H344V35.5Z' fill='url(#paint0_linear)' />\n      {/* Subtitle line */}\n      <Path d='M284 43.5H76V55.5H284V55.5Z' fill='url(#paint0_linear)' />\n      {/* Preview area for messages (right side) */}\n      <Path d='M344 36H304V60H344V36Z' fill='url(#paint0_linear)' />\n\n      {/* Gradient definition */}\n      <Defs>\n        <LinearGradient\n          id='paint0_linear'\n          x1={16}\n          y1={36}\n          x2={64}\n          y2={36}\n          gradientUnits='userSpaceOnUse'\n        >\n          <Stop\n            stopColor={\n              style?.linearGradientColors?.[0] ??\n              theme.conversationStyles.skeletonStyle.linearGradientColors[0]\n            }\n          />\n          <Stop\n            offset={1}\n            stopColor={\n              style?.linearGradientColors?.[1] ??\n              theme.conversationStyles.skeletonStyle.linearGradientColors[1]\n            }\n          />\n        </LinearGradient>\n      </Defs>\n    </Svg>\n  );\n};\n\n/**\n * Top SVG \"cookie-cutter\" layer – punched out shapes that mask out the\n * shimmer where content will eventually appear.\n */\nconst SkeletonItemTop: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n\n  return (\n    <Svg height={screenWidth / 5} viewBox='0 0 360 72' fill='none'>\n      {/* The giant path draws avatar circle + text bars + preview area */}\n      <Path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M0 0H360V72H0V0ZM16 36C16 22.7452 26.7452 12 40 12C53.2548 12 64 22.7452 64 36C64 49.2548 53.2548 60 40 60C26.7452 60 16 49.2548 16 36ZM84 16.5C79.5817 16.5 76 20.0817 76 24.5V27.5C76 31.9183 79.5817 35.5 84 35.5H228C232.418 35.5 236 31.9183 236 27.5V24.5C236 20.0817 232.418 16.5 228 16.5H84ZM284 24.5C284 20.0817 287.582 16.5 292 16.5H336C340.418 16.5 344 20.0817 344 24.5V27.5C344 31.9183 340.418 35.5 336 35.5H292C287.582 35.5 284 31.9183 284 27.5V24.5ZM82 43.5C78.6863 43.5 76 46.1863 76 49.5C76 52.8137 78.6863 55.5 82 55.5H278C281.314 55.5 284 52.8137 284 49.5C284 46.1863 281.314 43.5 278 43.5H82ZM312 40C308.686 40 306 42.6863 306 46V54C306 57.3137 308.686 60 312 60H336C339.314 60 342 57.3137 342 54V46C342 42.6863 339.314 40 336 40H312Z'\n        fill={\n          /* Mask must match background so the shimmer underneath shows */\n          style?.containerBackgroundColor ||\n          theme.conversationStyles.skeletonStyle.containerBackgroundColor\n        }\n      />\n    </Svg>\n  );\n};\n\n// ───────────────────────────────────────────────────────────────\n// Main Component\n// ───────────────────────────────────────────────────────────────\nexport const Skeleton: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  /* Kick-off the shimmer animation once on mount */\n  useEffect(() => {\n    animatedValue.setValue(0);\n    Animated.loop(\n      Animated.timing(animatedValue, {\n        toValue: 1,\n        duration: (1 / (style?.speed || theme.conversationStyles.skeletonStyle.speed)) * 1000,\n        easing: Easing.linear,\n        useNativeDriver: false, // translateX is layout-related\n      })\n    ).start();\n  }, [animatedValue, style?.speed || theme.conversationStyles.skeletonStyle.speed]);\n\n  /* Map 0-1 → off-screen-left → off-screen-right */\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth * 2, screenWidth],\n  });\n\n  /* Render ----------------------------------------------------- */\n  return (\n    <ScrollView showsVerticalScrollIndicator={false} scrollEnabled={false}>\n      {/* Bottom layer rectangles */}\n      {Array.from({ length: SKELETON_ROW_COUNT }, (_, i) => (\n        <SkeletonItemBottom style={style} key={`bottom-${i}`} />\n      ))}\n\n      {/* Two angled shimmer bars */}\n      {[0, screenWidth / 2].map((offset, i) => (\n        <Animated.View\n          key={`shimmer-${i}`}\n          style={[\n            styles.animatedView,\n            {\n              transform: [\n                { translateX: Animated.add(shimmerTranslateX, offset) },\n                { translateY: -20 },\n                { rotate: \"15deg\" },\n              ],\n              backgroundColor:\n                style?.shimmerBackgroundColor ??\n                theme.conversationStyles.skeletonStyle.shimmerBackgroundColor,\n              opacity:\n                style?.shimmerOpacity ?? theme.conversationStyles.skeletonStyle.shimmerOpacity,\n            },\n          ]}\n        />\n      ))}\n\n      {/* Top mask layer – sits above shimmer to carve out shapes */}\n      <View style={StyleSheet.absoluteFill}>\n        {Array.from({ length: SKELETON_ROW_COUNT }, (_, i) => (\n          <SkeletonItemTop style={style} key={`top-${i}`} />\n        ))}\n      </View>\n    </ScrollView>\n  );\n};\n\n// ───────────────────────────────────────────────────────────────\n// Styles\n// ───────────────────────────────────────────────────────────────\nconst styles = StyleSheet.create({\n  animatedView: {\n    width: \"25%\", // narrow bar looks nicer at an angle\n    top: 0,\n    bottom: 0,\n    position: \"absolute\",\n  },\n});"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatSearch/index.ts",
    "content": "export { CometChatSearch, default } from './CometChatSearch';"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatSearch/style.ts",
    "content": "import {\n  ColorValue,\n  ImageSourcePropType,\n  ImageStyle,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { AvatarStyle, getAvatarStyle } from \"../shared/views/CometChatAvatar\";\nimport { BadgeStyle } from \"../shared/views/CometChatBadge\";\nimport { DateStyle } from \"../shared/views/CometChatDate\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { JSX } from \"react\";\nimport { BorderStyle } from \"../shared/base/BorderStyle\";\n\nexport type SearchStyle = {\n  containerStyle: ViewStyle;\n  headerStyle: ViewStyle;\n  backButtonStyle: ViewStyle;\n  backButtonIcon?: ImageSourcePropType | JSX.Element;\n  backButtonIconStyle: ImageStyle;\n  searchContainerStyle: ViewStyle;\n  searchInputContainerStyle: ViewStyle;\n  searchInputStyle: TextStyle;\n  searchIconStyle: ImageStyle;\n  clearButtonStyle: ViewStyle;\n  clearButtonIconStyle: ImageStyle;\n  filtersContainerStyle: ViewStyle;\n  filtersContentStyle: ViewStyle;\n  filterButtonStyle: ViewStyle;\n  filterButtonActiveStyle: ViewStyle;\n  filterButtonContentStyle: ViewStyle;\n  filterButtonIconStyle: ImageStyle;\n  filterButtonIconActiveStyle: ImageStyle;\n  filterButtonTextStyle: TextStyle;\n  filterButtonTextActiveStyle: TextStyle;\n  emptyStateStyle: {\n    containerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    iconContainerStyle?: ViewStyle;\n    iconStyle?: ImageStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n  };\n  errorStateStyle: {\n    containerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    iconContainerStyle?: ViewStyle;\n    iconStyle?: ImageStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n  };\n  resultsContainerStyle: ViewStyle;\n  sectionTitleStyle: TextStyle;\n  seeMoreButtonStyle: ViewStyle;\n  seeMoreTextStyle: TextStyle;\n  conversationItemStyle: {\n    containerStyle: ViewStyle;\n    contentStyle: ViewStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    avatarStyle: AvatarStyle;\n    trailingContainerStyle: ViewStyle;\n    badgeStyle: Partial<BadgeStyle>;\n    dateStyle: Partial<DateStyle>;\n  };\n  messageItemStyle: {\n    containerStyle: ViewStyle;\n    contentStyle: ViewStyle;\n    textContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    iconContainerStyle: ViewStyle;\n    trailingContainerStyle: ViewStyle;\n    previewContainerStyle: ViewStyle;\n    previewImageStyle: ImageStyle;\n    previewVideoStyle: ViewStyle;\n    videoPlayIconStyle: ViewStyle;\n    audioPreviewStyle: ViewStyle;\n    filePreviewStyle: ViewStyle;\n    linkPreviewImageStyle: ImageStyle;\n  };\n  skeletonStyle: {\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n    containerBackgroundColor: ColorValue;\n  };\n};\n\nexport const getSearchStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<SearchStyle> => {\n  return {\n    containerStyle: {\n      flex: 1,\n      backgroundColor: color.background1,\n      marginVertical: spacing.margin.m3,\n    },\n    headerStyle: {\n      flexDirection: \"row\",\n      alignItems: \"center\",\n      paddingHorizontal: spacing.padding.p4,\n      paddingVertical: spacing.padding.p2,\n      backgroundColor: color.background1,\n    },\n    backButtonStyle: {\n      width: spacing.spacing.s10,\n      height: spacing.spacing.s10,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      marginRight: spacing.margin.m2,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconSecondary,\n      width: spacing.spacing.s6,\n      height: spacing.spacing.s6,\n    },\n    searchContainerStyle: {\n      flex: 1,\n    },\n    searchInputContainerStyle: {\n      backgroundColor: color.background3,\n      borderRadius: spacing.radius.max,\n      borderColor:color.borderDefault,\n      borderWidth:1,\n      paddingHorizontal: spacing.padding.p3,\n      flexDirection: \"row\",\n      alignItems: \"center\",\n      height: spacing.spacing.s12,\n    },\n    searchInputStyle: {\n      flex: 1,\n      ...typography.heading4.regular,\n      color: color.textPrimary,\n      paddingVertical: spacing.padding.p1,\n    },\n    searchIconStyle: {\n      tintColor: color.iconSecondary,\n      width: spacing.spacing.s6,\n      height: spacing.spacing.s6,\n    },\n    clearButtonStyle: {\n      width: spacing.spacing.s6,\n      height: spacing.spacing.s6,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      marginLeft: spacing.margin.m2,\n    },\n    clearButtonIconStyle: {\n      tintColor: color.iconSecondary,\n      width: spacing.spacing.s4,\n      height: spacing.spacing.s4,\n    },\n    filtersContainerStyle: {\n      paddingVertical: spacing.padding.p2,\n      paddingHorizontal: spacing.padding.p4,\n      backgroundColor: color.background1,\n    },\n    filtersContentStyle: {\n      flexDirection: \"row\",\n      flexWrap: \"wrap\",\n      alignItems: \"center\",\n    },\n    filterButtonStyle: {\n      paddingHorizontal: spacing.padding.p3,\n      paddingVertical: spacing.padding.p2,\n      marginRight: spacing.margin.m2,\n      marginBottom: spacing.margin.m2,\n      backgroundColor: color.background3,\n      borderRadius: spacing.radius.r5,\n      borderWidth: 1,\n      borderColor: color.borderLight,\n    },\n    filterButtonActiveStyle: {\n      backgroundColor: color.neutral900,\n      borderColor: color.neutral800,\n    },\n    filterButtonContentStyle: {\n      flexDirection: \"row\",\n      alignItems: \"center\",\n      gap: spacing.spacing.s1,\n    },\n    filterButtonIconStyle: {\n      width: 16,\n      height: 16,\n      tintColor: color.textSecondary,\n    },\n    filterButtonIconActiveStyle: {\n      tintColor: color.staticWhite,\n    },\n    filterButtonTextStyle: {\n      ...typography.body.regular,\n      color: color.textSecondary,\n    },\n    filterButtonTextActiveStyle: {\n      color: color.textWhite,\n    },\n    emptyStateStyle: {\n      containerStyle: {\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: spacing.padding.p8,\n      },\n      titleStyle: {\n        ...typography.heading2.bold,\n        color: color.textPrimary,\n        marginBottom: spacing.margin.m2,\n        textAlign: \"center\",\n      },\n      subtitleStyle: {\n        ...typography.body.regular,\n        color: color.textSecondary,\n        textAlign: \"center\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n    },\n    errorStateStyle: {\n      containerStyle: {\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: spacing.padding.p8,\n      },\n      titleStyle: {\n        ...typography.heading2.bold,\n        color: color.textPrimary,\n        marginBottom: spacing.margin.m2,\n        textAlign: \"center\",\n      },\n      subtitleStyle: {\n        ...typography.body.regular,\n        color: color.textSecondary,\n        textAlign: \"center\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n    },\n    resultsContainerStyle: {\n      flex: 1,\n      backgroundColor: color.background1,\n    },\n    sectionTitleStyle: {\n      ...typography.caption1.medium,\n      color: color.textTertiary,\n      paddingTop: spacing.padding.p4,\n    },\n    seeMoreButtonStyle: {\n      paddingVertical: spacing.padding.p2,\n      alignItems: 'flex-start',\n    },\n    seeMoreTextStyle: {\n      ...typography.body.medium,\n      color: color.primary,\n    },\n    conversationItemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        paddingVertical: spacing.padding.p3,\n      },\n      contentStyle: {\n        flex: 1,\n        marginLeft: spacing.margin.m3,\n      },\n      titleStyle: {\n        ...typography.heading4.medium,\n        color: color.textPrimary,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        ...typography.body.regular,\n        color: color.textSecondary,\n      },\n      avatarStyle: getAvatarStyle(color, spacing, typography),\n      trailingContainerStyle: {\n        alignItems: \"flex-end\",\n        justifyContent: \"flex-start\",\n        marginLeft: spacing.margin.m2,\n        minHeight: spacing.spacing.s12,\n      },\n      badgeStyle: {\n        containerStyle: {\n          backgroundColor: color.primary,\n          borderRadius: spacing.radius.max,\n          minWidth: spacing.spacing.s5,\n          maxWidth: spacing.spacing.s12,\n          height: spacing.spacing.s5,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          paddingHorizontal: spacing.spacing.s1,\n          paddingVertical: spacing.spacing.s0_5,\n          alignSelf: \"flex-end\",\n        },\n      },\n      dateStyle: {},\n    },\n    messageItemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        paddingVertical: spacing.padding.p3,\n      },\n      contentStyle: {\n        flex: 1,\n        maxWidth: '80%',\n      },\n      textContainerStyle: {\n        flex: 1,\n        minHeight: 80,\n        maxWidth: '100%',\n      },\n      titleStyle: {\n        ...typography.caption1.medium,\n        color: color.textSecondary,\n        marginBottom: spacing.margin.m0_5,\n      },\n      subtitleStyle: {\n        ...typography.body.regular,\n        color: color.textPrimary,\n        width: '90%',\n      },\n      iconContainerStyle: {\n        marginRight: spacing.margin.m3,\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      },\n      trailingContainerStyle: {\n        alignItems: \"flex-end\",\n        justifyContent: \"flex-start\",\n        marginLeft: spacing.margin.m2,\n        minHeight: spacing.spacing.s12,\n      },\n      previewContainerStyle: {\n        width: 80,\n        height: 80,\n        position: \"relative\",\n        justifyContent: \"center\",\n        alignItems: \"flex-end\",\n      },\n      previewImageStyle: {\n        width: 80,\n        height: 80,\n        borderRadius: spacing.radius.r2,\n        backgroundColor: color.background3,\n      },\n      previewVideoStyle: {\n        width: 80,\n        height: 80,\n        borderRadius: spacing.radius.r2,\n        backgroundColor: color.background3,\n        overflow: \"hidden\",\n      },\n      videoPlayIconStyle: {\n        position: \"absolute\",\n        top: 28,\n        left: 28,\n        backgroundColor: \"rgba(0,0,0,0.6)\",\n        borderRadius: spacing.radius.r3,\n        width: spacing.spacing.s6,\n        height: spacing.spacing.s6,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      },\n      audioPreviewStyle: {\n        width: spacing.spacing.s12,\n        height: spacing.spacing.s12,\n        backgroundColor: color.background3,\n        borderRadius: spacing.radius.r2,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      },\n      filePreviewStyle: {\n        width: spacing.spacing.s12,\n        height: spacing.spacing.s12,\n        backgroundColor: color.background3,\n        borderRadius: spacing.radius.r2,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      },\n      linkPreviewImageStyle: {\n        width: spacing.spacing.s12,\n        height: spacing.spacing.s12,\n        borderRadius: spacing.radius.r2,\n      },\n    },\n    skeletonStyle: {\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n      containerBackgroundColor: color.background1,\n    },\n  };\n};\n\nexport const getSearchStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<SearchStyle> => {\n  return deepMerge(getSearchStyleLight(color, spacing, typography), {\n    // Dark theme specific overrides can be added here if needed\n    // Currently, the light theme uses proper color tokens that adapt to dark mode\n  });\n};"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatThreadHeader/CometChatThreadHeader.tsx",
    "content": "import React, { useEffect, useState, useMemo, JSX } from \"react\";\nimport { ScrollView, Text, View } from \"react-native\";\nimport {\n  ChatConfigurator,\n  CometChatMentionsFormatter,\n  CometChatMessageTemplate,\n  CometChatTextFormatter,\n  CometChatUIKit,\n  CometChatUrlsFormatter,\n  MessageBubbleAlignmentType,\n} from \"../shared\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { messageStatus } from \"../shared/utils/CometChatMessageHelper\";\nimport { MessageUtils } from \"../shared/utils/MessageUtils\";\nimport { useTheme } from \"../theme\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { CommonUtils } from \"../shared/utils/CommonUtils\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\nimport { ExtensionConstants } from \"../extensions\";\nimport { getExtensionData } from \"../shared/helper/functions\";\nimport { urlPattern } from \"../shared/constants/UIKitConstants\";\n\n/**\n * Unique UI event identifier for CometChatUIEventHandler.\n */\nconst uiEventId = \"ccUiEvent\" + new Date().getTime();\n\n/**\n * Interface for the props of CometChatThreadHeader component.\n *\n * @interface CometChatThreadHeaderInterface\n */\nexport interface CometChatThreadHeaderInterface {\n  /**\n   * The parent message for the thread. It is a CometChat BaseMessage object.\n   *\n   * @type {CometChat.BaseMessage}\n   */\n  parentMessage: CometChat.BaseMessage;\n\n  /**\n   * Optional custom message template to render the message.\n   *\n   * @type {CometChatMessageTemplate}\n   */\n  template?: CometChatMessageTemplate;\n\n  /**\n   * Custom styles for the thread header, partially overriding the default theme.\n   *\n   * @type {DeepPartial<CometChatTheme[\"threadHeaderStyles\"]>}\n   */\n  style?: DeepPartial<CometChatTheme[\"threadHeaderStyles\"]>;\n  /**\n   * toggle visibility for the reply count.\n   *\n   * @type {boolean}\n   */\n  replyCountVisibility?: boolean;\n  /**\n   * toggle visibility for the reply count bar.\n   *\n   * @type {boolean}\n   */\n  replyCountBarVisibility?: boolean;\n  /**\n   * Custom styles for the thread header, partially overriding the default theme.\n   */\n  receiptsVisibility?: boolean;\n  /**\n   * toggle visibility for the receipts.\n   *\n   * @type {boolean}\n   */\n  avatarVisibility?: boolean;\n  /**\n   * Alignment type for the parent message bubble\n   */\n  alignment?: MessageBubbleAlignmentType;\n  /**\n   * Function that returns a custom string representation for a parent message's sent date.\n   *\n   * @param message - The base message object.\n   * @returns A string representing the custom date.\n   */\n  datePattern?: (message: CometChat.BaseMessage) => string;\n  /**\n   * Collection of text formatter classes to apply custom formatting.\n   *\n   * @type {Array<CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter>}\n   */\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n}\n\n/**\n * CometChatThreadHeader component renders the header of a message thread including\n * the message bubble and the reply count.\n *\n * @param {CometChatThreadHeaderInterface} props - The properties for the component.\n * @returns {JSX.Element} The rendered thread header.\n */\nexport const CometChatThreadHeader = (props: CometChatThreadHeaderInterface): JSX.Element => {\n  const {\n    parentMessage,\n    template,\n    replyCountVisibility = true,\n    replyCountBarVisibility = true,\n    textFormatters,\n    datePattern,\n    alignment,\n    receiptsVisibility = true,\n    avatarVisibility = true,\n  } = props;\n  const [message, setMessage] = useState<CometChat.BaseMessage>(parentMessage);\n  const [replyCount, setReplyCount] = useState<number>(parentMessage.getReplyCount() || 0);\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const style = deepMerge(theme.threadHeaderStyles, props.style ?? {});\n\n  /**\n   * Checks if an updated message belongs to the same thread as the current message.\n   *\n   * @param {CometChat.BaseMessage} updatedMessage - The message to be checked.\n   * @returns {boolean} True if the updated message belongs to the same thread.\n   */\n  const checkMessageBelongsToSameThread = (updatedMessage: CometChat.BaseMessage): boolean => {\n    return updatedMessage.getParentMessageId() == message.getId();\n  };\n\n  /**\n   * Checks the updated message and increments the reply count if it belongs to the same thread.\n   *\n   * @param {CometChat.BaseMessage} updatedMessage - The message to check.\n   */\n  const checkAndUpdateRepliesCount = (updatedMessage: CometChat.BaseMessage): void => {\n    if (checkMessageBelongsToSameThread(updatedMessage)) {\n      setReplyCount((prev) => prev + 1);\n    }\n  };\n\n  /**\n   * Handler for the message sent event.\n   *\n   * @param {{ message: CometChat.BaseMessage, status: string }} param0 - The message and its status.\n   */\n  const ccMessageSentFunc = ({ message: msg, status }: any): void => {\n    if (status === messageStatus.success) {\n      checkAndUpdateRepliesCount(msg);\n      if (message.getId() == msg.id) setMessage(message);\n    }\n  };\n\n  /**\n   * Handler for the message edited event.\n   *\n   * @param {{ message: CometChat.BaseMessage, status: string }} param0 - The edited message and its status.\n   */\n  const ccMessageEditedFunc = ({ message: msg, status }: any): void => {\n    if (message.getId() == msg.id && status == messageStatus.success) setMessage(message);\n  };\n\n  /**\n   * Handler for the message deleted event.\n   *\n   * @param {{ message: CometChat.BaseMessage }} param0 - The deleted message.\n   */\n  const ccMessageDeletedFunc = ({ message: msg }: any): void => {\n    if (message.getId() == msg.id) setMessage(message);\n  };\n\n  /**\n   * Handler for the message read event.\n   *\n   * @param {{ message: CometChat.BaseMessage }} param0 - The read message.\n   */\n  const ccMessageReadFunc = ({ message: msg }: any): void => {\n    if (message.getId() == msg.id) setMessage(message);\n  };\n\n  /**\n   * Merges the theme for the thread header with additional styles and maps some styles for MessageUtils.\n   *\n   * @returns {CometChatTheme} The merged theme.\n   */\n  const mergedTheme: CometChatTheme = useMemo(() => {\n    const themeClone = CommonUtils.clone(theme);\n    // Clone and map to messageListStyles in order to use these styles in MessageUtils.\n    themeClone.messageListStyles.incomingMessageBubbleStyles = deepMerge(\n      theme.threadHeaderStyles.incomingMessageBubbleStyles,\n      style.incomingMessageBubbleStyles\n    );\n    // Clone and map to messageListStyles in order to use these styles in MessageUtils.\n    themeClone.messageListStyles.outgoingMessageBubbleStyles = deepMerge(\n      theme.threadHeaderStyles.outgoingMessageBubbleStyles,\n      style.outgoingMessageBubbleStyles\n    );\n    return themeClone;\n  }, [theme, style]);\n\n  useEffect(() => {\n    CometChatUIEventHandler.addMessageListener(uiEventId, {\n      ccMessageSent: (item: any) => ccMessageSentFunc(item),\n      ccMessageEdited: (item: any) => ccMessageEditedFunc(item),\n      ccMessageDeleted: (item: any) => ccMessageDeletedFunc(item),\n      ccMessageRead: (item: any) => ccMessageReadFunc(item),\n      onTextMessageReceived: (textMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(textMessage);\n      },\n      onMediaMessageReceived: (mediaMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(mediaMessage);\n      },\n      onCustomMessageReceived: (customMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(customMessage);\n      },\n      onFormMessageReceived: (formMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(formMessage);\n      },\n      onCardMessageReceived: (cardMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(cardMessage);\n      },\n      onSchedulerMessageReceived: (schedulerMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(schedulerMessage);\n      },\n      onCustomInteractiveMessageReceived: (customInteractiveMessage: CometChat.BaseMessage) => {\n        checkAndUpdateRepliesCount(customInteractiveMessage);\n      },\n    });\n\n    return () => {\n      CometChatUIEventHandler.removeMessageListener(uiEventId);\n    };\n  }, []);\n\n  // Check if message has URLs (link preview or URLs in text)\n  const hasUrl = useMemo(() => {\n    // Check for link preview extension data\n    const linkData = getExtensionData(message, ExtensionConstants.linkPreview);\n    if (linkData && linkData.links && linkData.links.length > 0) {\n      return true;\n    }\n\n    // Check if text message contains URLs\n    if (message.getType() === \"text\") {\n      const textMessage = message as CometChat.TextMessage;\n      const text = textMessage.getText() || \"\";\n      const urlRegex = new RegExp(urlPattern);\n      return urlRegex.test(text);\n    }\n\n    return false;\n  }, [message]);\n\n  return (\n    <View style={[style?.containerStyle]}>\n      <ScrollView style={style?.messageBubbleContainerStyle}>\n        <View pointerEvents={hasUrl ? 'auto' : 'none'}>\n          {MessageUtils.getMessageView({\n            message,\n            templates:\n              template && template.type === parentMessage.getType()\n                ? [template]\n                : ChatConfigurator.dataSource.getAllMessageTemplates(mergedTheme, {\n                    textFormatters,\n                  }),\n            alignment: alignment\n              ? alignment\n              : message.getSender().getUid() === CometChatUIKit.loggedInUser!.getUid()\n                ? \"right\"\n                : \"left\",\n            theme: mergedTheme,\n            datePattern,\n            receiptsVisibility,\n            avatarVisibility\n            \n          })}\n        </View>\n      </ScrollView>\n      {replyCountBarVisibility && (\n        <View style={style?.replyCountBarStyle}>\n          <Text style={style?.replyCountTextStyle}>\n            {replyCountVisibility\n              ? replyCount.toString() +\n                \" \" +\n                (replyCount == 1 ? t(\"REPLY\") : t(\"REPLIES\"))\n              : \"\"}\n          </Text>\n        </View>\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatThreadHeader/Header.tsx",
    "content": "import React from \"react\";\nimport {\n  GestureResponderEvent,\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\n\nconst Header = (props: {\n  title: string;\n  showCloseButton?: boolean;\n  closeButtonIcon?: ImageSourcePropType;\n  onPress: (event: GestureResponderEvent) => void;\n  titleStyle?: TextStyle;\n  closeIconTint?: string;\n}) => {\n  const { title, showCloseButton, closeButtonIcon, onPress, titleStyle, closeIconTint } = props;\n  return (\n    <View style={styles.container}>\n      {showCloseButton && (\n        <TouchableOpacity style={styles.iconContainer} onPress={onPress}>\n          <Image\n            source={closeButtonIcon as ImageSourcePropType}\n            style={{ tintColor: closeIconTint ?? \"\", height: 24, width: 24 }}\n          />\n        </TouchableOpacity>\n      )}\n      <Text style={[styles.headingText, titleStyle]}>{title}</Text>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    height: 56,\n    paddingLeft: 15,\n  },\n  iconContainer: { paddingRight: 25, alignItems: \"center\" },\n  headingText: { fontSize: 20, fontWeight: \"600\", color: \"#000\" },\n});\nexport default Header;\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatThreadHeader/index.ts",
    "content": "import {\n  CometChatThreadHeaderInterface,\n  CometChatThreadHeader\n} from \"./CometChatThreadHeader\";\nexport { CometChatThreadHeader };\nexport type {\n  CometChatThreadHeaderInterface\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatThreadHeader/resources/index.ts",
    "content": "import BACK from \"./Back.png\";\nimport CLOSE from \"./close.png\";\nimport MORE from \"./more.png\";\nimport PROTECTED from \"./password.png\";\nimport PRIVATE from \"./private.png\";\nimport RIGHT_ARROW from \"./right-arrow.png\";\nexport const ICONS = {\n  BACK,\n  CLOSE,\n  PROTECTED,\n  PRIVATE,\n  RIGHT_ARROW,\n  MORE,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatThreadHeader/style.ts",
    "content": "import { Dimensions } from \"react-native\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { getMessageListStylesDark, getMessageListStylesLight } from \"../CometChatMessageList/style\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\n\nexport const getThreadHeaderStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"threadHeaderStyles\"] => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      maxHeight: Dimensions.get(\"window\").height * 0.25,\n    },\n    messageBubbleContainerStyle: {\n      backgroundColor: color.background3,\n      marginVertical: spacing.padding.p4,\n      paddingHorizontal: spacing.padding.p4,\n    },\n    replyCountBarStyle: {\n      paddingVertical: spacing.padding.p1,\n      paddingHorizontal: spacing.padding.p5,\n      backgroundColor: color.extendedPrimary100,\n      borderWidth: 1,\n      borderColor: color.borderDefault,\n    },\n    replyCountTextStyle: {\n      ...typography.body.regular,\n      color: color.textSecondary,\n    },\n    incomingMessageBubbleStyles: getMessageListStylesLight(color, spacing, typography)\n      .incomingMessageBubbleStyles,\n    outgoingMessageBubbleStyles: getMessageListStylesLight(color, spacing, typography)\n      .outgoingMessageBubbleStyles,\n  };\n};\n\nexport const getThreadHeaderStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"threadHeaderStyles\"] => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      maxHeight: Dimensions.get(\"window\").height * 0.25,\n    },\n    messageBubbleContainerStyle: {\n      backgroundColor: color.background3,\n      marginVertical: spacing.padding.p4,\n      paddingHorizontal: spacing.padding.p4,\n    },\n    replyCountBarStyle: {\n      paddingVertical: spacing.padding.p1,\n      paddingHorizontal: spacing.padding.p5,\n      backgroundColor: color.extendedPrimary100,\n      borderWidth: 1,\n      borderColor: color.borderDefault,\n    },\n    replyCountTextStyle: {\n      ...typography.body.regular,\n      color: color.textSecondary,\n    },\n    incomingMessageBubbleStyles: getMessageListStylesDark(color, spacing, typography)\n      .incomingMessageBubbleStyles,\n    outgoingMessageBubbleStyles: getMessageListStylesDark(color, spacing, typography)\n      .outgoingMessageBubbleStyles,\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatUsers/CometChatUsers.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX, useCallback, useEffect, useRef, useState } from \"react\";\nimport { ColorValue, KeyboardAvoidingView, Platform, View } from \"react-native\";\nimport {\n  CometChatList,\n  CometChatListActionsInterface,\n  CometChatListProps,\n  CometChatRetryButton,\n} from \"../shared\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { CometChatStatusIndicatorInterface } from \"../shared/views/CometChatStatusIndicator\";\nimport { ErrorEmptyView } from \"../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { useTheme } from \"../theme\";\nimport { Skeleton } from \"./Skeleton\";\nimport { UserStyle } from \"./style\";\nimport { CommonUtils } from \"../shared/utils/CommonUtils\";\nimport { CometChatTooltipMenu } from \"../shared/views/CometChatTooltipMenu\";\nimport { useCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew\";\n\n/**\n * Interface for the menu items (tooltip actions).\n */\nexport interface MenuItemInterface {\n  text: string;\n  onPress: () => void;\n  textColor?: ColorValue;\n  iconColor?: ColorValue;\n  disabled?: boolean;\n}\n\n/**\n * Interface for the props accepted by the CometChatUsers component.\n * @interface CometChatUsersInterface\n */\n\nexport interface CometChatUsersInterface\n  extends Omit<\n    CometChatListProps,\n    | \"title\"\n    | \"requestBuilder\"\n    | \"listStyle\"\n    | \"TitleView\"\n    | \"SubtitleView\"\n    | \"statusIndicatorStyle\"\n    | \"avatarStyle\"\n    | \"hideBackButton\"\n    | \"onListFetched\"\n    | \"listItemStyle\"\n    | \"ListItemView\"\n    | \"TailView\"\n    | \"disableUsersPresence\"\n    | \"ItemView\"\n    | \"onItemPress\"\n    | \"onItemLongPress\"\n    | \"listItemKey\"\n    | \"onSelection\"\n    | \"statusIndicatorType\"\n    | \"emptyStateText\"\n    | \"errorStateText\"\n    | \"hideStickyHeader\"\n  > {\n  /**\n   * Callback function when a list item (user) is pressed.\n   *\n   * @param {CometChat.User} user - The user object that was pressed.\n   */\n  onItemPress?: (user: CometChat.User) => void;\n  /**\n   * Callback function when a list item (user) is long pressed.\n   *\n   * @param {CometChat.User} user - The user object that was long pressed.\n   */\n  onItemLongPress?: (user: CometChat.User) => void;\n  /**\n   * Callback function providing the selected users list.\n   *\n   * @param {CometChat.User[]} list - The array of selected user objects.\n   */\n  onSelection?: (list: CometChat.User[]) => void;\n  /**\n   * Callback when submit selection button is pressed.\n   */\n  onSubmit?: (list: Array<CometChat.User>) => void;\n  /**\n   * Users request builder instance to customize the user request.\n   *\n   * @type {CometChat.UsersRequestBuilder}\n   */\n  usersRequestBuilder?: CometChat.UsersRequestBuilder;\n  /**\n   * Custom styling for the users list.\n   *\n   * @type {DeepPartial<UserStyle>}\n   */\n  style?: DeepPartial<UserStyle>;\n  /**\n   * Custom title view component.\n   *\n   * @param {CometChat.User} conversation - The user conversation object.\n   * @returns {JSX.Element}\n   */\n  TitleView?: (conversation: CometChat.User) => JSX.Element;\n  /**\n   * Function that returns a custom subtitle view for a user.\n   *\n   * @param {CometChat.User} item - The user object.\n   * @returns {JSX.Element}\n   */\n  SubtitleView?: (item: CometChat.User) => JSX.Element;\n  /**\n   * Function that returns a custom trailing view for a user.\n   *\n   * @param {CometChat.User} item - The user object.\n   * @returns {JSX.Element}\n   */\n  TrailingView?: (item: CometChat.User) => JSX.Element;\n  /**\n   * Function that returns a custom list item view.\n   *\n   * @param {CometChat.User} item - The user object.\n   * @returns {JSX.Element}\n   */\n  ItemView?: (item: CometChat.User) => JSX.Element;\n  /**\n   * Custom component to display for the empty state.\n   *\n   * @returns {JSX.Element}\n   */\n  EmptyView?: () => JSX.Element;\n  /**\n   * Custom component to display for the error state.\n   *\n   * @returns {JSX.Element}\n   */\n  ErrorView?: () => JSX.Element;\n  /**\n   * Custom component to display for the loading state.\n   *\n   * @returns {JSX.Element}\n   */\n  LoadingView?: () => JSX.Element;\n  /**\n   * Custom leading view component.\n   *\n   * @param {CometChat.User} item - The user object.\n   * @returns {JSX.Element}\n   */\n  LeadingView?: (item: CometChat.User) => JSX.Element;\n  /**\n   * Hide the header view.\n   *\n   * @type {boolean}\n   */\n  hideHeader?: boolean;\n  /**\n   * Placeholder text for the search input.\n   *\n   * @type {string}\n   */\n  searchPlaceholderText?: string;\n  /**\n   * Hide the users Status.\n   */\n  usersStatusVisibility?: boolean;\n  /**\n   * Search Keyword.\n   */\n  searchKeyword?: string;\n  /**\n   * Callback when an error occurs.\n   */\n  onError?: (e: CometChat.CometChatException) => void;\n  /**\n   * Callback triggered when the fetched list is empty.\n   */\n  onEmpty?: () => void;\n  /**\n   * Callback triggered once the users have loaded and are not empty.\n   */\n  onLoad?: (list: CometChat.User[]) => void;\n  /**\n   * Hide the loading skeleton.\n   */\n  hideLoadingState?: boolean;\n  /**\n   * Title for the header.\n   */\n  title?: string;\n  /**\n   * A function to **append** more menu items on top of the default menu items for a users.\n   */\n  addOptions?: (user: CometChat.User) => MenuItemInterface[];\n  /**\n   * A function to **replace** the default menu items entirely for a users.\n   */\n  options?: (user: CometChat.User) => MenuItemInterface[];\n  /**\n   * Toggle error view visibility.\n   */\n  hideError?: boolean;\n  /**\n   * Toggle back button visibility.\n   */\n  showBackButton?: boolean;\n  /**\n   * Toggle Sticky Header visibility.\n   */\n  stickyHeaderVisibility?: boolean;\n}\n\n/**\n * Interface for actions exposed by the CometChatUsers component.\n *\n * @interface CometChatUsersActionsInterface\n * @extends {CometChatListActionsInterface}\n */\nexport interface CometChatUsersActionsInterface extends CometChatListActionsInterface {}\n\n/**\n * CometChatUsers component renders a list of users with support for search,\n * selection, and custom empty/error/loading states.\n */\nexport const CometChatUsers = React.forwardRef<\n  CometChatUsersActionsInterface,\n  CometChatUsersInterface\n>((props, ref) => {\n  const userListenerId = \"userStatus_\" + new Date().getTime();\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const [hideSearchError, setHideSearchError] = useState(false);\n\n  const {\n    usersRequestBuilder,\n    LoadingView,\n    ErrorView,\n    EmptyView,\n    selectionMode = \"none\",\n    title,\n    addOptions,\n    options,\n    TrailingView,\n    LeadingView,\n    AppBarOptions,\n    hideSearch = false,\n    stickyHeaderVisibility = true,\n    style = {},\n    onError,\n    onLoad,\n    onEmpty,\n    hideError,\n    hideLoadingState,\n    usersStatusVisibility = true,\n    showBackButton = false,\n    searchKeyword = \"\",\n    searchRequestBuilder,\n    hideHeader,\n    onSelection,\n    onSubmit,\n    searchPlaceholderText = t(\"SEARCH\"),\n    ...newProps\n  } = props;\n  const userRef = useRef<CometChatUsersActionsInterface>(null);\n  const mergedStyle = deepMerge(theme.userStyles, style);\n\n  // ----- Tooltip functionality for users -----\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n  const [selectedUser, setSelectedUser] = useState<CometChat.User | null>(null);\n  const tooltipPosition = useRef({ pageX: 0, pageY: 0 });\n\n  const buildMenuItems = (user: CometChat.User): MenuItemInterface[] => {\n    if (options) return options(user);\n    if (addOptions) return addOptions(user);\n    return [];\n  };\n\n  const handleItemLongPress = (user: CometChat.User, e?: any) => {\n    if (props.onItemLongPress) {\n      props.onItemLongPress(user);\n      return;\n    }\n    const items = buildMenuItems(user);\n    if (items.length === 0) return;\n    if (e && e.nativeEvent) {\n      tooltipPosition.current = {\n        pageX: e.nativeEvent.pageX,\n        pageY: e.nativeEvent.pageY,\n      };\n    } else {\n      tooltipPosition.current = { pageX: 200, pageY: 100 };\n    }\n    setSelectedUser(user);\n    setTooltipVisible(true);\n  };\n\n  useEffect(() => {\n    // Listen for changes in user online/offline status.\n    CometChat.addUserListener(\n      userListenerId,\n      new CometChat.UserListener({\n        onUserOnline: (onlineUser: CometChat.User) => {\n          if (!onlineUser.getBlockedByMe()) {\n            userRef.current!.updateList(onlineUser);\n          }\n        },\n        onUserOffline: (offlineUser: CometChat.User) => {\n          if (!offlineUser.getBlockedByMe()) {\n            userRef.current!.updateList(offlineUser);\n          }\n        },\n      })\n    );\n    return () => CometChat.removeUserListener(userListenerId);\n  }, []);\n\n  /**\n   * Handler for when a user is blocked.\n   *\n   * @param {{ user: CometChat.User }} param0 - The user object that is blocked.\n   */\n  const handleccUserBlocked = ({ user }: any) => {\n    const clonedUser = CommonUtils.clone(user);\n    clonedUser.blockedByMe = true;\n    clonedUser.hasBlockedMe = true;\n    userRef.current!.updateList(clonedUser);\n  };\n\n  /**\n   * Handler for when a user is unblocked.\n   *\n   * @param {{ user: CometChat.User }} param0 - The user object that is unblocked.\n   */\n  const handleccUserUnBlocked = ({ user }: any) => {\n    const clonedUser = CommonUtils.clone(user);\n    clonedUser.blockedByMe = false;\n    clonedUser.hasBlockedMe = false;\n    userRef.current!.updateList(clonedUser);\n\n    CometChat.getUser(clonedUser.getUid()).then((updatedUser) => {\n      userRef.current!.updateList(updatedUser);\n    });\n  };\n\n  useEffect(() => {\n    CometChatUIEventHandler.addUserListener(userListenerId, {\n      ccUserBlocked: (item: any) => handleccUserBlocked(item),\n      ccUserUnBlocked: (item: any) => handleccUserUnBlocked(item),\n    });\n    return () => {\n      CometChatUIEventHandler.removeUserListener(userListenerId);\n    };\n  }, []);\n\n  /**\n   * Renders the empty state view when there are no users.\n   *\n   * @returns {JSX.Element} The empty state view.\n   */\n  const EmptyStateView = useCallback(() => {\n    useEffect(() => {\n      onEmpty?.();\n    }, []);\n    return (\n      <KeyboardAvoidingView\n        keyboardVerticalOffset={Platform.OS === \"ios\" ? 90 : 0}\n        behavior={Platform.select({ ios: \"padding\", android: \"height\" })}\n        style={{ flex: 1 }}\n      >\n        <ErrorEmptyView\n          title={t(\"NO_USERS_AVAILABLE\")}\n          subTitle={t(\"ADD_CONTACTS\")}\n          Icon={\n            <Icon\n              name='user-empty-icon'\n              icon={mergedStyle.emptyStateStyle.icon}\n              color={theme.color.neutral300}\n              size={theme.spacing.spacing.s15 << 1}\n              containerStyle={{\n                marginBottom: theme.spacing.spacing.s5,\n              }}\n            />\n          }\n          containerStyle={{\n            flex: 1,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: \"10%\",\n          }}\n          titleStyle={mergedStyle.emptyStateStyle.titleStyle}\n          subTitleStyle={mergedStyle.emptyStateStyle.subTitleStyle}\n        />\n      </KeyboardAvoidingView>\n    );\n  }, [theme, mergedStyle]);\n\n  /**\n   * Renders the error state view when there is an error fetching users.\n   *\n   * @returns {JSX.Element} The error state view.\n   */\n  const ErrorStateView = useCallback(() => {\n    useEffect(() => {\n      // Hide search when error view is active.\n      setHideSearchError(true);\n    }, []);\n    if (hideError) return null;\n    return (\n      <View style={{ flex: 1 }}>\n        <ErrorEmptyView\n          title={t(\"OOPS\")}\n          subTitle={t(\"SOMETHING_WENT_WRONG\")}\n          tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n          Icon={\n            <Icon\n              name='error-state'\n              size={theme.spacing.margin.m15 << 1}\n              containerStyle={{\n                marginBottom: theme.spacing.margin.m5,\n              }}\n              icon={mergedStyle.errorStateStyle.icon}\n            />\n          }\n          containerStyle={{\n            flex: 1,\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            paddingHorizontal: \"10%\",\n          }}\n          titleStyle={mergedStyle.errorStateStyle.titleStyle}\n          subTitleStyle={mergedStyle.errorStateStyle.subTitleStyle}\n          RetryView={<CometChatRetryButton onPress={() => userRef.current?.reload()} />}\n        />\n      </View>\n    );\n  }, [theme, mergedStyle, hideError]);\n\n  return (\n    <View style={theme.userStyles.containerStyle}>\n      <CometChatList\n        hideHeader={hideHeader ?? hideHeader}\n        searchPlaceholderText={searchPlaceholderText}\n        searchRequestBuilder={searchRequestBuilder}\n        hideBackButton={!showBackButton}\n        hideError={hideError}\n        hideSubmitButton={props.hideSubmitButton}\n        AppBarOptions={AppBarOptions}\n        title={title ? title : t(\"USERS\")}\n        onError={onError}\n        TrailingView={TrailingView}\n        selectionMode={selectionMode}\n        LeadingView={LeadingView}\n        onSelection={onSelection}\n        onSubmit={onSubmit}\n        // Pass our custom long press handler which shows tooltip if options exist.\n        onItemLongPress={(user: CometChat.User, e: any) => handleItemLongPress(user, e)}\n        ItemView={props.ItemView}\n        onListFetched={(users: CometChat.User[]) => {\n          if (users.length === 0) {\n            onEmpty?.();\n          } else {\n            onLoad?.(users);\n          }\n        }}\n        ref={userRef}\n        hideSearch={hideSearch ? hideSearch : hideSearchError}\n        requestBuilder={\n          (usersRequestBuilder && usersRequestBuilder.setSearchKeyword(searchKeyword)) ||\n          new CometChat.UsersRequestBuilder()\n            .setLimit(30)\n            .hideBlockedUsers(false)\n            .setRoles([])\n            .friendsOnly(false)\n            .setStatus(\"\")\n            .setTags([])\n            .setUIDs([])\n            .setSearchKeyword(searchKeyword)\n        }\n        listStyle={mergedStyle}\n        hideStickyHeader={!stickyHeaderVisibility}\n        listItemKey='uid'\n        LoadingView={\n          hideLoadingState\n            ? () => <></> // will not render anything if true\n            : LoadingView\n              ? LoadingView\n              : () => <Skeleton style={mergedStyle.skeletonStyle} />\n        }\n        EmptyView={EmptyView ? EmptyView : () => <EmptyStateView />}\n        ErrorView={ErrorView ? ErrorView : () => <ErrorStateView />}\n        statusIndicatorType={(user: CometChat.User) =>\n          usersStatusVisibility\n            ? user?.getBlockedByMe()\n              ? \"offline\"\n              : (user?.getStatus() as CometChatStatusIndicatorInterface[\"type\"])\n            : null\n        }\n        {...newProps}\n      />\n\n      {/* Tooltip Menu: shows on long press if options exist and selectionMode is \"none\" */}\n      {selectedUser && selectionMode === \"none\" && tooltipVisible && (\n        <View\n          style={{\n            position: \"absolute\",\n            top: tooltipPosition.current.pageY,\n            left: tooltipPosition.current.pageX,\n            zIndex: 9999,\n          }}\n        >\n          <CometChatTooltipMenu\n            visible={tooltipVisible}\n            onClose={() => setTooltipVisible(false)}\n            onDismiss={() => setTooltipVisible(false)}\n            event={{\n              nativeEvent: tooltipPosition.current,\n            }}\n            menuItems={buildMenuItems(selectedUser).map((menuItem) => ({\n              text: menuItem.text,\n              onPress: () => {\n                menuItem.onPress();\n                setTooltipVisible(false);\n              },\n              textColor: menuItem.textColor,\n              iconColor: menuItem.iconColor,\n              disabled: menuItem.disabled,\n            }))}\n          />\n        </View>\n      )}\n    </View>\n  );\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatUsers/Skeleton.tsx",
    "content": "import React, { useEffect, useMemo, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Circle, Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../theme\";\nimport type { ColorValue } from \"react-native\";\nimport { CometChatTheme } from \"../theme/type\";\n\n/**\n * Animated skeleton placeholder mimicking a *user list* in CometChat UI‑Kit.\n *\n * The component respects the defaults provided by `theme.userStyles.skeletonStyle`,\n * but every visual property can be **overridden per instance** using the\n * `style` prop.\n *\n * @example\n * ```tsx\n * <Skeleton\n *   style={{\n *     linearGradientColors: [\"#4c669f\", \"#3b5998\"],\n *     shimmerOpacity: 0.4,\n *   }}\n * />\n * ```\n */\nexport interface SkeletonProps {\n  /** Partial style overrides (theme fallback for omitted keys). */\n  style?: Partial<SkeletonStyle>;\n}\n\n/** Alias for the skeleton style slice inside the theme. */\ntype SkeletonStyle = CometChatTheme[\"userStyles\"][\"skeletonStyle\"];\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Utility helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\nfunction getStyleValue<K extends keyof SkeletonStyle>(\n  key: K,\n  overrides: Partial<SkeletonStyle> | undefined,\n  theme: CometChatTheme\n): NonNullable<SkeletonStyle[K]> {\n  // Guaranteed fallback to theme defaults; cast is safe as UI‑Kit defines them.\n  return ((overrides?.[key] as SkeletonStyle[K]) ??\n    theme.userStyles.skeletonStyle[key]) as NonNullable<SkeletonStyle[K]>;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Layout constants – tweak here if the design changes\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst { width: SCREEN_WIDTH } = Dimensions.get(\"window\");\n\nconst PADDING = 20;\nconst AVATAR_RADIUS = 25;\nconst LIST_ITEM_HEIGHT = 25;\nconst LIST_ITEM_SPACING = 54; // gap between items (includes avatar & text)\nconst LIST_ITEM_COUNT = 14;\n\n/** Total height required for the SVG canvas */\nconst TOTAL_HEIGHT = PADDING + LIST_ITEM_COUNT * (LIST_ITEM_HEIGHT + LIST_ITEM_SPACING);\n\n// ──────────────────────────────────────────────────────────────────────────────\n// SVG rows factory (memoized for perf)\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst useRows = (fill: ColorValue) =>\n  useMemo(\n    () =>\n      Array.from({ length: LIST_ITEM_COUNT }).map((_, index) => {\n        const y = PADDING + index * (LIST_ITEM_HEIGHT + LIST_ITEM_SPACING) - 20;\n        return (\n          // eslint-disable-next-line react/no-array-index-key\n          <React.Fragment key={index}>\n            <Circle\n              cx={PADDING + AVATAR_RADIUS}\n              cy={y + AVATAR_RADIUS}\n              r={AVATAR_RADIUS}\n              fill={fill as string}\n            />\n            <Rect\n              x={PADDING + AVATAR_RADIUS * 2 + 12}\n              y={y + 12}\n              width={SCREEN_WIDTH - (PADDING + AVATAR_RADIUS * 2 + 12 + PADDING)}\n              height={LIST_ITEM_HEIGHT}\n              rx={LIST_ITEM_HEIGHT / 2}\n              fill={fill as string}\n            />\n          </React.Fragment>\n        );\n      }),\n    [fill]\n  );\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Component Implementation\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport const Skeleton: React.FC<SkeletonProps> = ({ style }) => {\n  const theme = useTheme();\n  const get = <K extends keyof SkeletonStyle>(key: K) => getStyleValue(key, style, theme);\n\n  // Shimmer animation ------------------------------------------\n  const translate = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const speed = get(\"speed\");\n    const duration = 1000 / speed;\n    const loop = Animated.loop(\n      Animated.timing(translate, {\n        toValue: 1,\n        duration,\n        easing: Easing.linear,\n        useNativeDriver: false, // SVG not yet compatible with native driver\n      })\n    );\n\n    loop.start();\n    return () => loop.stop();\n  }, [get(\"speed\"), translate]);\n\n  const translateX = translate.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-SCREEN_WIDTH * 2, SCREEN_WIDTH],\n  });\n\n  // Pre‑build row shapes (bottom gradient + top mask)\n  const rowsGradient = useRows(\"url(#gradient)\");\n  const rowsSolid = useRows(String(get(\"backgroundColor\")));\n\n  return (\n    <ScrollView\n      scrollEnabled={false}\n      showsVerticalScrollIndicator={false}\n      style={{ backgroundColor: get(\"containerBackgroundColor\") }}\n    >\n      {/* Bottom layer (gradient fill) */}\n      <Svg\n        height={TOTAL_HEIGHT}\n        width={SCREEN_WIDTH}\n        fill='none'\n        preserveAspectRatio='xMidYMid meet'\n      >\n        <Defs>\n          <LinearGradient\n            id='gradient'\n            x1='0'\n            y1='0'\n            x2={SCREEN_WIDTH}\n            y2='0'\n            gradientUnits='userSpaceOnUse'\n          >\n            {(() => {\n              const colors = get(\"linearGradientColors\");\n              return [\n                <Stop key={0} stopColor={colors[0]} />,\n                <Stop key={1} offset='1' stopColor={colors[1]} />,\n              ];\n            })()}\n          </LinearGradient>\n        </Defs>\n        {rowsGradient}\n      </Svg>\n\n      {/* Moving shimmer highlight (rendered twice for coverage) */}\n      {[0, SCREEN_WIDTH / 2].map((offset) => (\n        // eslint-disable-next-line react/no-array-index-key\n        <Animated.View\n          key={offset}\n          style={[\n            styles.shimmer,\n            {\n              transform: [\n                { translateX: Animated.add(translateX, offset) },\n                { translateY: -20 },\n                { rotate: \"15deg\" },\n              ],\n              backgroundColor: get(\"shimmerBackgroundColor\"),\n              opacity: get(\"shimmerOpacity\"),\n            },\n          ]}\n        />\n      ))}\n\n      {/* Top mask – solid shapes clip the shimmer to list items */}\n      <View style={StyleSheet.absoluteFill} pointerEvents='none'>\n        <Svg\n          height={TOTAL_HEIGHT}\n          width={SCREEN_WIDTH}\n          fill='none'\n          preserveAspectRatio='xMidYMid meet'\n        >\n          {rowsSolid}\n        </Svg>\n      </View>\n    </ScrollView>\n  );\n};\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Styles\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst styles = StyleSheet.create({\n  shimmer: {\n    position: \"absolute\",\n    width: \"25%\", // thin bar for highlight\n    top: 0,\n    bottom: 0,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatUsers/index.ts",
    "content": "import {\n  CometChatUsers,\n  CometChatUsersActionsInterface,\n  CometChatUsersInterface,\n} from \"./CometChatUsers\";\n\nexport { CometChatUsers };\nexport type {\n  CometChatUsersInterface,\n  CometChatUsersActionsInterface\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/CometChatUsers/style.ts",
    "content": "import { ColorValue, ImageSourcePropType, ViewStyle } from \"react-native\";\nimport { CometChatListStylesInterface } from \"../shared\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { DeepPartial } from \"../shared/helper/types\";\n\nexport type UserStyle = CometChatListStylesInterface & {\n  skeletonStyle: {\n    backgroundColor?: ColorValue;\n    linearGradientColors?: [string, string];\n    shimmerBackgroundColor?: ColorValue;\n    shimmerOpacity?: number;\n    speed?: number;\n    containerBackgroundColor?: ColorValue;\n  };\n  headerContainerStyle: ViewStyle;\n};\n\nexport const getUserListStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<UserStyle> => {\n  return {\n    headerContainerStyle: {\n      alignItems: \"flex-start\",\n      justifyContent: \"center\",\n      width: \"100%\",\n      borderRadius: 0,\n      paddingHorizontal: 0,\n    },\n    titleSeparatorStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderLight,\n      flexDirection: \"row\",\n      justifyContent: \"space-between\",\n      alignItems: \"center\",\n      width: \"100%\",\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      flex: 1,\n    },\n    itemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        gap: spacing.spacing.s3,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.body.regular,\n      },\n      statusIndicatorStyle: {},\n      avatarStyle: {\n        containerStyle: {},\n        textStyle: {},\n        imageStyle: {},\n      },\n      headViewContainerStyle: {},\n      titleSubtitleContainerStyle: {\n        alignSelf: \"center\",\n      },\n      trailingViewContainerStyle: {},\n    },\n    confirmSelectionStyle: {},\n    selectionCancelStyle: undefined,\n    loadingIconTint: color.primary,\n    sectionHeaderTextStyle: {\n      marginHorizontal: spacing.spacing.s5,\n      color: color.primary,\n      ...typography.heading4.medium,\n    },\n    onlineStatusColor: color.success,\n    titleViewStyle: {\n      paddingVertical: spacing.spacing.s3,\n      paddingLeft: spacing.spacing.s3,\n      margin: spacing.spacing.s0,\n    },\n    titleStyle: {\n      color: color.textPrimary,\n      ...typography.heading1.bold,\n    },\n    backButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    searchStyle: {\n      textStyle: {\n        color: color.textPrimary,\n        ...typography.heading4.regular,\n        textAlignVertical: \"center\",\n        paddingVertical: 0,\n        height: spacing.spacing.s7,\n      },\n      containerStyle: {\n        backgroundColor: color.background3,\n        paddingVertical: spacing.spacing.s3,\n        marginTop: spacing.spacing.s3,\n        width: \"95%\",\n        gap: spacing.spacing.s1,\n        alignContent: \"space-around\",\n        alignSelf: \"center\",\n        flexDirection: \"row\",\n        alignItems: \"center\",\n      },\n      icon: undefined,\n      iconStyle: {\n        tintColor: color.iconSecondary,\n      },\n      placehodlerTextStyle: {\n        color: color.textTertiary,\n      },\n    },\n    emptyStateStyle: {\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\" as const,\n        ...typography.body.regular,\n      },\n      containerStyle: {\n        justifyContent: \"center\",\n        display: \"none\",\n        alignItems: \"center\",\n        padding: spacing.padding.p3,\n      },\n    },\n    errorStateStyle: {\n      containerStyle: {\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n        flexDirection: \"column\",\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subTitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    },\n    skeletonStyle: {\n      backgroundColor: \"transparent\",\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 1,\n      containerBackgroundColor: color.background1,\n    },\n  };\n};\n\nexport const getUserListStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<UserStyle> => {\n  return deepMerge(getUserListStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      backgroundColor: \"transparent\",\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n      containerBackgroundColor: color.background1,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CallEvents.ts",
    "content": "export const CallUIEvents = {\n  ccCallFailed: \"ccCallFailed\",\n  ccCallInitiated: \"ccCallInitiated\",\n  ccCallRejected: \"ccCallRejected\",\n  ccCallAccepted: \"ccCallAccepted\",\n  ccCallEnded: \"ccCallEnded\",\n  ccCallJoined: \"ccCallJoined\",\n  ccOutgoingCall: \"ccOutgoingCall\",\n  ccShowOngoingCall: \"ccShowOngoingCall\",\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CallUtils.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport {\n  CALL_BUSY,\n  CALL_CANCELLED,\n  CALL_ENDED,\n  CALL_INITIATED,\n  CALL_ONGOING,\n  CALL_REJECTED,\n  CALL_UNANSWERED,\n} from \"../shared/constants/UIKitConstants\";\nimport { getCometChatTranslation } from \"../shared/resources/CometChatLocalizeNew/LocalizationManager\";\nimport { IconName } from \"../shared/icons/Icon\";\nconst t = getCometChatTranslation()\nexport class CallUtils {\n  /**\n   * Checks if the given initiator is the same as the logged in user.\n   *\n   * @param initiator - The call initiator.\n   * @param loggedInUser - The logged in user.\n   * @returns True if the initiator is the logged in user.\n   */\n  private static isInitiator(initiator: CometChat.User, loggedInUser: CometChat.User): boolean {\n    return initiator.getUid() == loggedInUser?.getUid();\n  }\n\n  /**\n   * Retrieves the call status along with an appropriate icon.\n   *\n   * @param message - The call message.\n   * @param loggedInUser - The logged in user.\n   * @returns An object containing callMessageText and selectedIcon.\n   */\n  static getCallStatus(\n    message: CometChat.BaseMessage,\n    loggedInUser: CometChat.User\n  ): {\n    callMessageText: string;\n    selectedIcon: IconName;\n  } {\n    try {\n      // if (!(message instanceof CometChat.Call)) return '';\n      let call = message as CometChat.Call;\n      let callMessageText = \"\";\n      let selectedIcon: IconName = \"\" as any;\n      let initiator =\n        (call?.getCallInitiator && call?.getCallInitiator()) || (call as any).getInitiator();\n      switch (call.getStatus()) {\n        case CALL_INITIATED:\n          if (this.isInitiator(initiator, loggedInUser)) {\n            callMessageText = `${t(`OUTGOING_CALL`)}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"outgoing-audio\" : \"outgoing-video\";\n          } else {\n            callMessageText = `${t(`INCOMING_CALL`)}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"incoming-audio\" : \"incoming-video\";\n          }\n          break;\n        case CALL_ONGOING:\n          callMessageText = `${t(`CALL_ACCEPTED`)}`;\n          selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          break;\n        case CALL_ENDED:\n          callMessageText = `${t(`CALL_ENDED`)}`;\n          selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          break;\n        case CALL_UNANSWERED:\n          if (this.isInitiator(initiator, loggedInUser)) {\n            callMessageText = `${t(\"UNANSWERED_CALL\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          } else {\n            callMessageText = `${t(\"MISSED_CALL\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"phone-missed\" : \"missed-video-call\";\n          }\n          break;\n        case CALL_CANCELLED:\n          if (this.isInitiator(initiator, loggedInUser)) {\n            callMessageText = `${t(\"CANCELLED_CALL\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          } else {\n            callMessageText = `${t(\"MISSED_CALL\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"phone-missed\" : \"missed-video-call\";\n          }\n          break;\n        case CALL_REJECTED:\n          if (this.isInitiator(initiator, loggedInUser)) {\n            callMessageText = `${t(\"CALL_REJECTED\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          } else {\n            callMessageText = `${t(\"CALL_REJECTED\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          }\n          break;\n        case CALL_BUSY:\n          if (this.isInitiator(initiator, loggedInUser)) {\n            callMessageText = `${t(\"CALL_BUSY\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"call\" : \"video-call\";\n          } else {\n            callMessageText = `${t(\"MISSED_CALL\")}`;\n            selectedIcon = call[\"type\"] === \"audio\" ? \"phone-missed\" : \"missed-video-call\";\n          }\n          break;\n      }\n      if (callMessageText == undefined || callMessageText == \"undefined\")\n        console.log(message, loggedInUser);\n\n      return { callMessageText, selectedIcon };\n    } catch (e) {\n      console.log(\"__CATCH\", e);\n      return { callMessageText: \"\", selectedIcon: \"\" as any };\n    }\n  }\n\n  /**\n   * Determines if the given call is a missed call.\n   *\n   * @param call - The call object.\n   * @param loggedInUser - The logged in user.\n   * @returns True if it's a missed call.\n   */\n  static isMissedCall(call: CometChat.Call, loggedInUser: CometChat.User): boolean {\n    const callStatus: any = call.getStatus();\n    const iAmInitiator = this.isInitiator((call as any)?.getInitiator(), loggedInUser);\n\n    if (iAmInitiator) {\n      return callStatus === CALL_UNANSWERED;\n    }\n\n    // Incoming calls: treat BUSY and REJECTED as missed as well\n    return [CALL_UNANSWERED, CALL_CANCELLED, CALL_BUSY, CALL_REJECTED].includes(callStatus);\n  }\n\n  /**\n   * Returns a call status string for displaying in call logs.\n   *\n   * @param call - The call object.\n   * @param loggedInUser - The logged in user.\n   * @returns \"outgoing\", \"incoming\", or \"missed\".\n   */\n  static getCallStatusForCallLogs(\n    call: CometChat.Call,\n    loggedInUser: CometChat.User\n  ): \"outgoing\" | \"incoming\" | \"missed\" {\n    const callStatus: any = call.getStatus();\n    if (this.isInitiator((call as any)?.getInitiator(), loggedInUser)) {\n      return \"outgoing\";\n    } else {\n      if (this.isMissedCall(call, loggedInUser)) {\n        return \"missed\";\n      }\n      return \"incoming\";\n    }\n  }\n\n  /**\n   * Converts minutes to a formatted string (hours, minutes, seconds).\n   *\n   * @param minutes - The time in minutes.\n   * @returns A formatted string.\n   */\n  static convertMinutesToHoursMinutesSeconds(minutes: number): string {\n    const hours = Math.floor(minutes / 60);\n    const remainingMinutes = Math.floor(minutes % 60);\n    const seconds = Math.floor((minutes - Math.floor(minutes)) * 60);\n    let hoursString = \"\";\n    let minutesString = \"\";\n    let secondsString = \"\";\n    if (hours > 0) {\n      hoursString = `${hours}h`;\n    }\n    if (remainingMinutes > 0) {\n      minutesString = `${remainingMinutes}m`;\n    }\n    if (seconds >= 0) {\n      secondsString = `${seconds}s`;\n    }\n    return hoursString\n      ? `${hoursString} ${minutesString} ${secondsString}`\n      : minutesString\n        ? `${minutesString} ${secondsString}`\n        : secondsString;\n  }\n\n  /**\n   * Converts seconds to a formatted string (hours, minutes, seconds).\n   *\n   * @param seconds - The time in seconds.\n   * @returns A formatted string.\n   */\n  static convertSecondsToHoursMinutesSeconds(seconds: number): string {\n    const hours = Math.floor(seconds / 3600);\n    const remainingMinutes = Math.floor((seconds % 3600) / 60);\n    const remainingSeconds = Math.floor((seconds % 3600) % 60);\n    let hoursString = \"\";\n    let minutesString = \"\";\n    let secondsString = \"\";\n    if (hours > 0) {\n      hoursString = `${hours}h`;\n    }\n    if (remainingMinutes > 0) {\n      minutesString = `${remainingMinutes}m`;\n    }\n    if (remainingSeconds >= 0) {\n      secondsString = `${remainingSeconds}s`;\n    }\n    return hoursString\n      ? `${hoursString} ${minutesString} ${secondsString}`\n      : minutesString\n        ? `${minutesString} ${secondsString}`\n        : secondsString;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CallingConfiguration.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatCallButtonConfiguration } from \"./CometChatCallButtons\";\nimport { CallingPackage } from \"./CallingPackage\";\nconst CometChatCalls = CallingPackage.CometChatCalls;\nexport class CallingConfiguration {\n  callButtonsConfiguration?: CometChatCallButtonConfiguration;\n  groupCallSettingsBuilder?: (\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    isAudioOnly?: boolean\n  ) => typeof CometChatCalls.CallSettingsBuilder;\n\n  constructor(params: {\n    callButtonsConfiguration?: CometChatCallButtonConfiguration;\n    groupCallSettingsBuilder?: (\n      user?: CometChat.User,\n      group?: CometChat.Group,\n      isAudioOnly?: boolean\n    ) => typeof CometChatCalls.CallSettingsBuilder;\n  }) {\n    this.callButtonsConfiguration = params.callButtonsConfiguration;\n    this.groupCallSettingsBuilder = params.groupCallSettingsBuilder;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CallingExtension.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatUIKit } from \"../shared/CometChatUiKit/CometChatUIKit\";\nimport { ChatConfigurator, ExtensionsDataSource } from \"../shared/framework\";\nimport { CallingConfiguration } from \"./CallingConfiguration\";\nimport { CallingExtensionDecorator } from \"./CallingExtensionDecorator\";\nimport { CallingPackage } from \"./CallingPackage\";\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\nexport class CallingExtension extends ExtensionsDataSource {\n  configuration?: CallingConfiguration;\n\n  CallingExtension({ configuration }: { configuration?: CallingConfiguration }) {\n    if (configuration) this.configuration = configuration;\n  }\n\n  constructor(configuration?: CallingConfiguration ) {\n    super();\n    if (configuration) this.configuration = configuration;\n  }\n\n  enable() {\n    this.addExtension();\n  }\n\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource) => {\n      const callAppSettings = new CometChatCalls.CallAppSettingsBuilder()\n        .setAppId(CometChatUIKit.uiKitSettings.appId)\n        .setRegion(CometChatUIKit.uiKitSettings.region)\n        .build();\n\n      CometChatCalls.init(callAppSettings).then(\n        () => {\n          console.log(\"CometChatCalls initialization completed successfully\");\n        },\n        (error: CometChat.CometChatException) => {\n          console.log(\"CometChatCalls initialization failed with error:\", error);\n        }\n      );\n      return new CallingExtensionDecorator({\n        dataSource,\n        configuration: this.configuration,\n      });\n    });\n  }\n\n  override getExtensionId(): string {\n    return \"calling\";\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CallingExtensionDecorator.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { Modal, View } from \"react-native\";\nimport { ChatConfigurator } from \"../shared/framework/ChatConfigurator\";\nimport { CometChatUIEventHandler } from \"../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CometChatUIKit } from \"../shared/CometChatUiKit/CometChatUIKit\";\nimport { AdditionalAuxiliaryHeaderOptionsParams, AdditionalParams } from \"../shared/base/Types\";\nimport {\n  CallContstatnts,\n  MessageCategoryConstants,\n  MessageTypeConstants,\n} from \"../shared/constants/UIKitConstants\";\nimport { DataSource } from \"../shared/framework/DataSource\";\nimport { DataSourceDecorator } from \"../shared/framework/DataSourceDecorator\";\nimport { CometChatMessageTemplate } from \"../shared/modals\";\nimport { getCometChatTranslation, getCurrentLanguage } from \"../shared/resources/CometChatLocalizeNew/LocalizationManager\";\nimport { permissionUtil } from \"../shared/utils/PermissionUtil\";\nimport { CallUIEvents } from \"./CallEvents\";\nimport { CallingConfiguration } from \"./CallingConfiguration\";\nimport { CallingPackage } from \"./CallingPackage\";\nimport { CallUtils } from \"./CallUtils\";\nimport { CometChatMeetCallBubble } from \"./CometChatCallBubble\";\nimport { CometChatCallButtons } from \"./CometChatCallButtons\";\nimport { CometChatOngoingCall } from \"./CometChatOngoingCall\";\nimport { Icon } from \"../shared/icons/Icon\";\nimport { CometChatCallActionBubble } from \"./CometChatCallBubble/CometChatCallBubble\";\nimport { CometChatTheme } from \"../theme/type\";\nimport { LocalizedDateHelper, localizedDateHelperInstance } from \"../shared/helper/LocalizedDateHelper\";\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\nconst t = getCometChatTranslation();\n\n/**\n * CallingExtensionDecorator extends the DataSourceDecorator to add calling-specific\n * configurations and templates.\n *\n * @class CallingExtensionDecorator\n * @extends {DataSourceDecorator}\n */\nexport class CallingExtensionDecorator extends DataSourceDecorator {\n  /**\n   * Optional calling configuration.\n   */\n  configuration?: CallingConfiguration;\n  /**\n   * The logged in CometChat user.\n   */\n  loggedInUser!: CometChat.User;\n\n  /**\n   * Creates an instance of CallingExtensionDecorator.\n   *\n   * @param {object} props - The properties object.\n   * @param {DataSource} props.dataSource - The original data source.\n   * @param {CallingConfiguration} [props.configuration] - Optional calling configuration.\n   */\n  constructor(props: { dataSource: DataSource; configuration?: CallingConfiguration }) {\n    super(props.dataSource);\n    CometChat.getLoggedinUser()\n      .then((user: CometChat.User | null) => {\n        this.loggedInUser = user!;\n      })\n      .catch((err: CometChat.CometChatException) => {\n        console.log(\"unable to get logged in user.\");\n      });\n    if (props.configuration) {\n      this.configuration = props.configuration;\n    }\n  }\n\n  /**\n   * Returns a unique identifier for this decorator.\n   *\n   * @returns {string} The identifier.\n   */\n  getId(): string {\n    return \"call\";\n  }\n\n  /**\n   * Determines whether a message is deleted.\n   *\n   * @param {CometChat.BaseMessage} message - The message to check.\n   * @returns {boolean} True if the message is deleted; otherwise false.\n   */\n  isDeletedMessage(message: CometChat.BaseMessage): boolean {\n    return message.getDeletedBy() != null;\n  }\n\n  /**\n   * Retrieves all supported message types including call types.\n   *\n   * @returns An array of message type strings.\n   */\n  getAllMessageTypes() {\n    let types: string[] = super.getAllMessageTypes();\n    types.push(CallContstatnts.audioCall);\n    types.push(CallContstatnts.videoCall);\n    types.push(MessageTypeConstants.meeting);\n    return types;\n  }\n\n  /**\n   * Retrieves all supported message categories including call-related categories.\n   *\n   * @returns An array of message category strings.\n   */\n  getAllMessageCategories() {\n    let categories = super.getAllMessageCategories();\n    categories.push(MessageCategoryConstants.call);\n    categories.push(MessageCategoryConstants.custom);\n    return categories;\n  }\n\n  /**\n   * Renders the call bubble view for one-to-one call messages.\n   *\n   * @param {object} param0 - The props object.\n   * @param {any} param0.message - The call message.\n   * @param {CometChatTheme} param0.theme - The theme.\n   * @returns {JSX.Element|null} The rendered call bubble view.\n   */\n  UserCallBubbleView = ({ message, theme }: { message: any; theme: CometChatTheme }) => {\n    if (this.isDeletedMessage(message)) return null;\n    const callStatus = CallUtils.getCallStatus(message, this.loggedInUser);\n\n    return (\n      <CometChatCallActionBubble\n        titleText={callStatus.callMessageText}\n        style={theme?.messageListStyles.callActionBubbleStyles}\n        icon={\n          <Icon\n            name={callStatus.selectedIcon}\n            height={\n              callStatus.callMessageText === t(\"MISSED_CALL\")\n                ? theme.messageListStyles?.callActionBubbleStyles?.missedCallIconStyle?.height ?? 16\n                : theme.messageListStyles?.callActionBubbleStyles?.iconStyle?.height ?? 16\n            }\n            width={\n              callStatus.callMessageText === t(\"MISSED_CALL\")\n                ? theme.messageListStyles?.callActionBubbleStyles?.missedCallIconStyle?.width ?? 16\n                : theme.messageListStyles?.callActionBubbleStyles?.iconStyle?.width ?? 16\n            }\n            color={\n              callStatus.callMessageText === t(\"MISSED_CALL\")\n                ? theme.messageListStyles?.callActionBubbleStyles?.missedCallIconStyle?.tintColor\n                : theme.messageListStyles?.callActionBubbleStyles?.iconStyle?.tintColor\n            }\n            imageStyle={\n              callStatus.callMessageText === t(\"MISSED_CALL\")\n                ? theme.messageListStyles?.callActionBubbleStyles?.missedCallIconStyle\n                : theme.messageListStyles?.callActionBubbleStyles?.iconStyle\n            }\n            containerStyle={\n              callStatus.callMessageText === t(\"MISSED_CALL\")\n                ? theme.messageListStyles?.callActionBubbleStyles?.missedCallIconContainerStyle\n                : theme.messageListStyles?.callActionBubbleStyles?.iconContainerStyle\n            }\n          />\n        }\n      />\n    );\n  };\n\n  /**\n   * Returns the audio call message template for one-to-one chats.\n   *\n   * @param {CometChatTheme} theme - The theme to use for the template.\n   * @returns {CometChatMessageTemplate} The audio call message template.\n   */\n  getUserAudioCallTemplate = (theme: CometChatTheme) => {\n    return new CometChatMessageTemplate({\n      category: MessageCategoryConstants.call,\n      type: MessageTypeConstants.audio,\n      BubbleView: (message) => {\n        return this.UserCallBubbleView({\n          message,\n          theme,\n        });\n      },\n    });\n  };\n\n  /**\n   * Returns the video call message template for one-to-one chats.\n   *\n   * @param {CometChatTheme} theme - The theme to use for the template.\n   * @returns {CometChatMessageTemplate} The video call message template.\n   */\n  getUserVideoCallTemplates = (theme: CometChatTheme) => {\n    return new CometChatMessageTemplate({\n      category: MessageCategoryConstants.call,\n      type: MessageTypeConstants.video,\n      BubbleView: (message) => {\n        return this.UserCallBubbleView({\n          message,\n          theme,\n        });\n      },\n    });\n  };\n\n  /**\n   * Renders the group call bubble view.\n   *\n   * @param {object} props - The props for the group call bubble view.\n   * @param {CometChat.CustomMessage} props.message - The call message.\n   * @param {CometChatTheme} props.theme - The theme.\n   * @param {string} props.alignment - Alignment for the bubble.\n   * @returns {JSX.Element} The rendered group call bubble view.\n   */\n  GroupCallBubbleView = (props: {\n    message: CometChat.CustomMessage;\n    theme: CometChatTheme;\n    alignment: string;\n  }) => {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    const { message, theme, alignment } = props;\n\n    if (this.isDeletedMessage(message))\n      return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n\n    const isSentByMe = message.getSender().getUid() === loggedInUser!.getUid();\n\n    const _style = isSentByMe\n      ? theme.messageListStyles.outgoingMessageBubbleStyles?.meetCallBubbleStyles\n      : theme.messageListStyles.incomingMessageBubbleStyles?.meetCallBubbleStyles;\n\n    const sentAt = message?.getSentAt() ? message.getSentAt() * 1000 : Date.now();\n    const customData = message.getCustomData() as any;\n    const callType = (message.getCustomData() as any).callType;\n\n    // Extract session ID with cross-platform compatibility\n    const sessionId = customData.sessionId || customData.sessionID;\n\n    const BubbleIcon = (() => {\n      if (isSentByMe) {\n        if (callType === \"audio\") {\n          return <Icon name='outgoing-audio-fill' color={_style?.iconStyle?.tintColor} />;\n        }\n        return <Icon name='outgoing-video-fill' color={_style?.iconStyle?.tintColor} />;\n      } else {\n        if (callType === \"audio\") {\n          return <Icon name='incoming-audio-fill' color={_style?.iconStyle?.tintColor} />;\n        }\n        return <Icon name='incoming-video-fill' color={_style?.iconStyle?.tintColor} />;\n      }\n    })();\n\n    return (\n      <View>\n        <CometChatMeetCallBubble\n          buttonText={t(\"JOIN\")}\n          titleText={callType === \"audio\" ? t(\"AUDIO_CALL\") : t(\"VIDEO_CALL\")}\n          icon={BubbleIcon}\n          onClick={() =>\n            this.startDirectCall(\n              sessionId,\n              message,\n              theme,\n              callType\n            )\n          }\n          style={_style ?? {}}\n          subTitleText={\n            localizedDateHelperInstance.getFormattedDate(sentAt, LocalizedDateHelper.patterns.callBubble, getCurrentLanguage())\n          }\n        />\n      </View>\n    );\n  };\n\n  /**\n   * Initiates a direct call by checking permissions and showing the ongoing call screen.\n   *\n   * @param  sessionId - The call session ID.\n   * @param  message - The call message.\n   * @param  [theme] - The theme.\n   * @param  [callType] - The type of call (audio/video).\n   * @returns \n   */\n  async startDirectCall(\n    sessionId: string,\n    message: CometChat.BaseMessage,\n    theme?: CometChatTheme,\n    callType?: string\n  ) {\n    // Validate session ID\n    if (!sessionId || sessionId === 'undefined' || sessionId === 'null') {\n      console.error('[CallingExtensionDecorator] Invalid session ID provided:', sessionId);\n      return;\n    }\n    \n    // Request only the necessary permissions based on call type.\n    try {\n      const resources: (\"mic\" | \"camera\")[] = callType === \"audio\" ? [\"mic\"] : [\"mic\", \"camera\"];\n      const allowed = await permissionUtil.startResourceBasedTask(resources);\n      if (!allowed) {\n        console.warn(\n          `[CallingExtensionDecorator] Permission check failed for resources: ${resources.join(\",\")}. Aborting join.`\n        );\n        return;\n      }\n    } catch (e) {\n      console.error(\"[CallingExtensionDecorator] Unexpected error while requesting permissions\", e);\n      return;\n    }\n    \n    const callSettingsBuilder = (\n      this.configuration?.groupCallSettingsBuilder\n        ? this.configuration?.groupCallSettingsBuilder(\n            undefined,\n            message.getReceiver() as CometChat.Group,\n            callType === \"audio\"\n          )\n        : new CometChatCalls.CallSettingsBuilder().setIsAudioOnlyCall(callType === \"audio\")\n    ).setCallEventListener(\n      new CometChatCalls.OngoingCallListener({\n        onCallEndButtonPressed: () => {\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccShowOngoingCall, {\n            child: null,\n          });\n        },\n        onError: (error: CometChat.CometChatException) => {\n          console.error(\"[CallingExtensionDecorator] OngoingCallListener onError while joining\", error);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccShowOngoingCall, {\n            child: null,\n          });\n        },\n      })\n    );\n\n    const ongoingCallScreen = (\n      <Modal>\n        <CometChatOngoingCall\n          sessionID={sessionId}\n          callSettingsBuilder={callSettingsBuilder}\n          onError={(e) => {\n            console.error(\"[CallingExtensionDecorator] CometChatOngoingCall onError for session\", sessionId, e);\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccShowOngoingCall, {\n              child: null,\n            });\n          }}\n        />\n      </Modal>\n    );\n    CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccShowOngoingCall, {\n      child: ongoingCallScreen,\n    });\n  }\n\n  /**\n   * Renders the call buttons in the auxiliary header app bar.\n   *\n   * @param  [user] - The user for one-to-one chats.\n   * @param  [group] - The group for group chats.\n   * @param  [additionalParams] - Additional parameters.\n   * @returns The rendered call buttons or null if conditions are not met.\n   */\n  getAuxiliaryHeaderAppbarOptions(\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    additionalAuxiliaryHeaderOptionsParams?: AdditionalAuxiliaryHeaderOptionsParams\n  ) {\n    // For one-to-one chats: if a user exists and is blocked, don't render the call buttons.\n    if (user && !group && user.getBlockedByMe()) {\n      return null;\n    }\n\n    return (\n      <View>\n        <CometChatCallButtons\n          user={user}\n          group={group}\n          {...this.configuration?.callButtonsConfiguration}\n          style={additionalAuxiliaryHeaderOptionsParams?.callButtonStyle}\n          hideVoiceCallButton={additionalAuxiliaryHeaderOptionsParams?.hideVoiceCallButton}\n          hideVideoCallButton={additionalAuxiliaryHeaderOptionsParams?.hideVideoCallButton}\n        />\n      </View>\n    );\n  }\n\n  /**\n   * Returns the group call template to render group call messages.\n   *\n   * @param {CometChatTheme} theme - The theme.\n   * @returns {CometChatMessageTemplate} The group call message template.\n   */\n  getGroupCallTemplate = (theme: CometChatTheme) => {\n    return new CometChatMessageTemplate({\n      category: MessageCategoryConstants.custom,\n      type: MessageTypeConstants.meeting,\n      ContentView: (message, alignment) =>\n        this.GroupCallBubbleView({ message: message as CometChat.CustomMessage, alignment, theme }),\n      StatusInfoView: (message, alignment) => {\n        if (message.getDeletedAt()) {\n          return null;\n        }\n        return <></>;\n      },\n      // Pass theme explicitly so common options (e.g. Message Privately) validate group context correctly.\n      options: (loggedInUser, messageObject, _theme, group) => {\n        return super.getCommonOptions(loggedInUser, messageObject, _theme, group);\n      },\n    });\n  };\n\n  /**\n   * Retrieves all message templates including call-related templates.\n   *\n   * @param {CometChatTheme} theme - The theme.\n   * @param {AdditionalParams} [additionalParams] - Additional parameters.\n   * @returns {CometChatMessageTemplate[]} An array of message templates.\n   */\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    let templates = super.getAllMessageTemplates(theme, additionalParams);\n    templates.push(\n      this.getUserAudioCallTemplate(theme),\n      this.getUserVideoCallTemplates(theme),\n      this.getGroupCallTemplate(theme)\n    );\n    return templates;\n  }\n\n  /**\n   * Retrieves the last conversation message for display in the conversation list.\n   *\n   * @param {CometChat.Conversation} conversation - The conversation object.\n   * @param {CometChatTheme} [theme] - The theme.\n   * @returns {string | JSX.Element} The last conversation message.\n   */\n  getLastConversationMessage(conversation: CometChat.Conversation, theme?: CometChatTheme): string | JSX.Element {\n    if (conversation.getLastMessage()[\"category\"] != \"call\")\n      return super.getLastConversationMessage(conversation, theme);\n    let lastMesssageString = \"\";\n    if (conversation.getLastMessage()[\"type\"] == \"audio\")\n      lastMesssageString = t(\"AUDIO_CALL\");\n    if (conversation.getLastMessage()[\"type\"] == \"video\")\n      lastMesssageString = t(\"VIDEO_CALL\");\n    return lastMesssageString;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CallingPackage.ts",
    "content": "declare const require: any;\n\nlet isCallingPackageInstalled, CometChatCalls;\n\ntry {\n  // Attempt to require the package\n  CometChatCalls = require(\"@cometchat/calls-sdk-react-native\")?.CometChatCalls;\n  isCallingPackageInstalled = true;\n\n  // If no error is thrown, the package is installed\n  // console.log('calls sdk is installed.');\n} catch (error) {\n  isCallingPackageInstalled = false;\n  // If an error is thrown, the package is not installed\n  // console.log('calls sdk is not installed.');\n}\n\nexport const CallingPackage = {\n  isCallingPackageInstalled,\n  CometChatCalls,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallBubble/CometChatCallBubble.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { StyleSheet, Text, TouchableOpacity, View } from \"react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { useTheme } from \"../../theme\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatMeetCallBubble component.\n *\n * @interface CometChatMeetCallBubbleInterface\n */\nexport interface CometChatMeetCallBubbleInterface {\n  /** Title text to be displayed in the bubble */\n  titleText: string;\n  /** Subtitle text to be displayed under the title */\n  subTitleText: string;\n  /** Text to be displayed on the button */\n  buttonText: string;\n  /** JSX element to display as an icon */\n  icon: JSX.Element;\n  /** Callback function to be executed on button press */\n  onClick: () => void;\n  /** Custom style overrides for the meet call bubble */\n  style: DeepPartial<CometChatTheme[\"meetCallBubbleStyles\"]>;\n}\n\n/**\n * Component representing a meet call bubble.\n *\n * @param {CometChatMeetCallBubbleInterface} props - Component properties.\n * @returns {JSX.Element} The rendered component.\n */\nexport const CometChatMeetCallBubble = (props: CometChatMeetCallBubbleInterface) => {\n  const { icon, titleText, subTitleText, buttonText, onClick, style } = props;\n\n  return (\n    <View style={style?.containerStyle}>\n      {/* Row container for icon and texts */}\n      <View style={styles.row}>\n        <View style={style?.iconContainerStyle}>{icon}</View>\n        <View style={styles.textContainer}>\n          <Text style={style?.titleStyle}>{titleText}</Text>\n          <Text style={style?.subtitleStyle}>{subTitleText}</Text>\n        </View>\n      </View>\n      {/* Divider */}\n      <View style={style?.dividerStyle} />\n      {/* Action button */}\n      <TouchableOpacity style={style?.buttonStyle} onPress={onClick}>\n        <Text style={style?.buttonTextStyle}>{buttonText}</Text>\n      </TouchableOpacity>\n    </View>\n  );\n};\n\n/**\n * Props for the CometChatCallActionBubble component.\n *\n * @interface CometChatUserCallBubbleInterface\n */\nexport interface CometChatUserCallBubbleInterface {\n  /** Title text to be displayed (e.g., call status) */\n  titleText: string;\n  /** JSX element to display as an icon */\n  icon: JSX.Element;\n  /** Optional custom style overrides for the call action bubble */\n  style?: DeepPartial<CometChatTheme[\"messageListStyles\"][\"callActionBubbleStyles\"]>;\n}\n\n/**\n * Component representing a call action bubble.\n *\n * This component displays a call action with an icon and text.\n * It differentiates styles if the call is missed.\n *\n * @param {CometChatUserCallBubbleInterface} props - Component properties.\n * @returns {JSX.Element} The rendered component.\n */\nexport const CometChatCallActionBubble = (props: CometChatUserCallBubbleInterface) => {\n  const { titleText, icon, style } = props;\n  const theme = useTheme();\n  const {t} = useCometChatTranslation()\n\n  // Merge default theme styles with optional style overrides\n  const callActionBubbleStyles = useMemo(() => {\n    return deepMerge(theme.messageListStyles.callActionBubbleStyles, style ?? {});\n  }, [theme.messageListStyles.callActionBubbleStyles, style]);\n\n  return (\n    <View style={{ justifyContent: \"center\", alignItems: \"center\" }}>\n      <View\n        style={\n          titleText === t(\"MISSED_CALL\")\n            ? callActionBubbleStyles.missedCallContainerStyle\n            : callActionBubbleStyles.containerStyle\n        }\n      >\n        {icon}\n        <Text\n          style={\n            titleText === t(\"MISSED_CALL\")\n              ? callActionBubbleStyles.missedCallTextStyle\n              : callActionBubbleStyles.textStyle\n          }\n        >\n          {titleText}\n        </Text>\n      </View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  row: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    marginBottom: 16,\n    gap: 8,\n  },\n  textContainer: {\n    gap: 4,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallBubble/index.ts",
    "content": "import { CometChatMeetCallBubble, CometChatMeetCallBubbleInterface } from \"./CometChatCallBubble\";\nexport {\n  CometChatMeetCallBubble,\n};\nexport type {\n  CometChatMeetCallBubbleInterface\n};\n\nexport { getGroupCallBubbleStyle } from \"./styles\";\nexport type { GroupCallBubbleStyles } from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallBubble/resources/index.ts",
    "content": "import Avatar from \"./avatar.png\";\nimport IncomingAudioCall from \"./incomingaudiocall.png\";\nimport IncomingVideoCall from \"./incomingvideocall.png\";\n\nexport { Avatar, IncomingAudioCall, IncomingVideoCall };\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallBubble/styles.ts",
    "content": "import { ImageStyle, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { JSX } from \"react\";\n\nexport type GroupCallBubbleStyles = {\n  titleStyle: TextStyle;\n  subtitleStyle: TextStyle;\n  containerStyle: ViewStyle;\n  iconStyle: ImageStyle;\n  iconContainerStyle: ViewStyle;\n  buttonStyle: ViewStyle;\n  buttonTextStyle: TextStyle;\n  dividerStyle?: ViewStyle;\n};\n\nexport const getGroupCallBubbleStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: GroupCallBubbleStyles;\n  outgoingBubbleStyle: GroupCallBubbleStyles;\n} => {\n  return {\n    incomingBubbleStyle: {\n      titleStyle: {\n        color: color.receiveBubbleText,\n        ...typography.body.medium,\n      },\n      subtitleStyle: {\n        color: color.receiveBubbleTimestamp,\n        ...typography.caption1.regular,\n      },\n      containerStyle: {\n        minWidth: 240,\n      },\n      iconContainerStyle: {\n        backgroundColor: color.primaryButtonIcon,\n        borderRadius: spacing.radius.max,\n        width: spacing.spacing.s10,\n        height: spacing.spacing.s10,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      },\n      iconStyle: {\n        width: spacing.spacing.s5,\n        height: spacing.spacing.s5,\n        tintColor: color.iconHighlight,\n      },\n      buttonStyle: {\n        paddingVertical: spacing.padding.p3,\n        borderRadius: spacing.radius.r3,\n        alignItems: \"center\",\n      },\n      buttonTextStyle: {\n        color: color.primary,\n        ...typography.button.medium,\n      },\n      dividerStyle: {\n        height: 1,\n        backgroundColor: color.borderDark,\n      },\n    },\n    outgoingBubbleStyle: {\n      titleStyle: {\n        color: color.sendBubbleText,\n        ...typography.body.medium,\n      },\n      subtitleStyle: {\n        color: color.sendBubbleText,\n        ...typography.caption1.regular,\n      },\n      containerStyle: {\n        minWidth: 240,\n      },\n      iconContainerStyle: {\n        backgroundColor: color.primaryButtonIcon,\n        borderRadius: spacing.radius.max,\n        width: spacing.spacing.s10,\n        height: spacing.spacing.s10,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      },\n      iconStyle: {\n        width: spacing.spacing.s5,\n        height: spacing.spacing.s5,\n        tintColor: color.sendBubbleBackground,\n      },\n      buttonStyle: {\n        paddingVertical: spacing.padding.p3,\n        borderRadius: spacing.radius.r3,\n        alignItems: \"center\",\n      },\n      buttonTextStyle: {\n        color: color.sendBubbleText,\n        ...typography.button.medium,\n      },\n      dividerStyle: {\n        height: 1,\n        backgroundColor: color.extendedPrimary800,\n      },\n    },\n  };\n};\n\nexport type CallActionBubbleStyles = {\n  containerStyle: ViewStyle;\n  textStyle: TextStyle;\n  iconStyle: ImageStyle;\n  iconContainerStyle: ViewStyle;\n  missedCallContainerStyle: ViewStyle;\n  missedCallTextStyle: TextStyle;\n  missedCallIconStyle: ImageStyle;\n  missedCallIconContainerStyle: ViewStyle;\n};\n\nexport const getCallActionBubbleStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CallActionBubbleStyles> => {\n  return {\n    containerStyle: {\n      flexDirection: \"row\",\n      alignSelf: \"center\",\n      borderWidth: 1,\n      borderRadius: spacing.radius.max,\n      paddingVertical: spacing.padding.p1,\n      paddingHorizontal: spacing.padding.p3,\n      borderColor: color.borderDefault,\n      gap: spacing.spacing.s1,\n    },\n    textStyle: {\n      color: color.textSecondary,\n      ...typography.caption1.regular,\n    },\n    iconStyle: {},\n    missedCallContainerStyle: {\n      flexDirection: \"row\",\n      alignSelf: \"center\",\n      borderWidth: 1,\n      borderRadius: spacing.radius.max,\n      paddingVertical: spacing.padding.p1,\n      paddingHorizontal: spacing.padding.p3,\n      borderColor: color.borderDefault,\n      gap: spacing.spacing.s1,\n    },\n    missedCallTextStyle: {\n      color: color.error,\n      ...typography.caption1.regular,\n    },\n    missedCallIconStyle: {\n      tintColor: color.error,\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallButtons/CometChatCallButtonConfiguration.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { OutgoingCallConfiguration } from \"../CometChatOutgoingCall\";\nimport { CallingPackage } from \"../CallingPackage\";\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\nexport interface CometChatCallButtonConfigurationInterface {\n  callSettingsBuilder?: (\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    isAudioOnly?: boolean\n  ) => typeof CometChatCalls.CallSettingsBuilder;\n  outgoingCallConfiguration?: OutgoingCallConfiguration;\n}\n\nexport class CometChatCallButtonConfiguration implements CometChatCallButtonConfigurationInterface {\n  callSettingsBuilder?: (\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    isAudioOnly?: boolean\n  ) => typeof CometChatCalls.CallSettingsBuilder;\n\n  constructor({ callSettingsBuilder }: CometChatCallButtonConfigurationInterface) {\n    this.callSettingsBuilder = callSettingsBuilder;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallButtons/CometChatCallButtons.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n  ColorValue,\n  DimensionValue,\n  ImageSourcePropType,\n  ImageStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { CometChatUIKit } from \"../../shared/CometChatUiKit/CometChatUIKit\";\nimport { CallTypeConstants, MessageTypeConstants } from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { getUnixTimestampInMilliseconds } from \"../../shared/utils/CometChatMessageHelper\";\nimport { permissionUtil } from \"../../shared/utils/PermissionUtil\";\nimport { useTheme } from \"../../theme\";\nimport { useCompTheme } from \"../../theme/hook\";\nimport { CallUIEvents } from \"../CallEvents\";\nimport { CometChatOutgoingCall, OutgoingCallConfiguration } from \"../CometChatOutgoingCall\";\nimport { CallButtonStyle } from \"./style\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { CallingPackage } from \"../CallingPackage\";\nimport { JSX } from \"react\";\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\nconst listenerId = \"callEventListener_\" + new Date().getTime();\n\n/**\n * Props for the CometChatCallButtons component.\n *\n * @interface CometChatCallButtonsInterface\n */\nexport interface CometChatCallButtonsInterface {\n  /**\n   * CometChat.User object.\n   */\n  user?: CometChat.User;\n  /**\n   * CometChat.Group object.\n   */\n  group?: CometChat.Group;\n  /**\n   * Should the voice call icon be shown.\n   */\n  hideVoiceCallButton?: boolean;\n  /**\n   * Should the video call icon be shown.\n   */\n  hideVideoCallButton?: boolean;\n  /**\n   * Callback to handle errors.\n   *\n   * @param {CometChat.CometChatException} e - The exception object.\n   */\n  onError?: (e: CometChat.CometChatException) => void;\n  /**\n   * Function to build call settings.\n   *\n   * @param {CometChat.User} [user] - The user object.\n   * @param {CometChat.Group} [group] - The group object.\n   * @param {boolean} [isAudioOnly] - Flag indicating if the call is audio only.\n   * @returns {CometChatCalls.CallSettingsBuilder} The call settings builder.\n   */\n  callSettingsBuilder?: (\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    isAudioOnly?: boolean\n  ) => typeof CometChatCalls.CallSettingsBuilder;\n  /**\n   * Configuration for outgoing calls.\n   */\n  outgoingCallConfiguration?: OutgoingCallConfiguration;\n  /**\n   * Custom style overrides for the call button.\n   */\n  style?: DeepPartial<CallButtonStyle>;\n}\n\n/**\n * CometChatCallButtons component.\n *\n * This component renders call action buttons (voice and video) and handles call initiation,\n * outgoing call screen, and call events.\n *\n * @param {CometChatCallButtonsInterface} props - Component properties.\n * @returns {JSX.Element} The rendered component.\n */\nexport const CometChatCallButtons = (props: CometChatCallButtonsInterface): JSX.Element => {\n  const {\n    user,\n    group,\n    hideVoiceCallButton = false,\n    hideVideoCallButton = false,\n    onError,\n    callSettingsBuilder,\n    outgoingCallConfiguration,\n    style = {},\n  } = props;\n\n  const theme = useTheme();\n  const compTheme = useCompTheme();\n\n  // Merge default and custom styles for call buttons\n  const callButtonStyles = useMemo(() => {\n    return deepMerge(theme.callButtonStyles, compTheme.callButtonStyles ?? {}, style);\n  }, [theme, compTheme, style]);\n\n  const [disableButton, setDisableButton] = useState(false);\n  const [showOutgoingCallScreen, setShowOutgoingCallScreen] = useState(false);\n  const [callReceived, setCallReceived] = useState<CometChat.Call>();\n\n  const outGoingCall = useRef<CometChat.Call | CometChat.CustomMessage>(undefined);\n  const callType = useRef<typeof CometChat.CALL_TYPE.AUDIO | typeof CometChat.CALL_TYPE.VIDEO>(undefined);\n  const incomingCall = useRef<CometChat.Call>(undefined);\n  const loggedInUser = useRef<CometChat.User>(undefined);\n\n  /**\n   * Checks if there is an active call.\n   * If found, opens the outgoing call screen and returns true.\n   * Otherwise, returns false.\n   *\n   * @returns {boolean} Whether an active call exists.\n   */\n\n  const checkActiveCallOnly = () => {\n    return false;\n  };\n\n  /**\n   * Initiates a call based on the provided call type.\n   *\n   * @param {any} type - The type of call (audio or video).\n   */\n  const makeCall = (type: any): void => {\n    if (type == CallTypeConstants.audio || type == CallTypeConstants.video) {\n      var receiverID = user ? user.getUid() : group ? group.getGuid() : undefined;\n      var callType = type;\n      // For group calls, send a custom meeting message.\n      if (group) {\n        let customData = {\n          callType: callType,\n          sessionId: receiverID,\n        };\n        let customMessage: CometChat.CustomMessage = new CometChat.CustomMessage(\n          receiverID,\n          CometChat.RECEIVER_TYPE.GROUP,\n          MessageTypeConstants.meeting,\n          customData\n        );\n        customMessage.setCategory(CometChat.CATEGORY_CUSTOM as CometChat.MessageCategory);\n        customMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n        customMessage.setSender(loggedInUser.current!);\n        customMessage.setReceiver(group);\n        customMessage.setMetadata({\n          ...customMessage.getMetadata(),\n          incrementUnreadCount: true,\n          pushNotification: MessageTypeConstants.meeting,\n        });\n        customMessage.shouldUpdateConversation(true);\n        customMessage.setMetadata({ incrementUnreadCount: true });\n        customMessage.setCustomData(customData);\n        CometChatUIKit.sendCustomMessage(customMessage)\n          .then((res) => {\n            outGoingCall.current = res as CometChat.CustomMessage;\n            setShowOutgoingCallScreen(true);\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, { call: res });\n          })\n          .catch((rej) => {\n            console.log(\"custom msg error\", rej);\n            onError && onError(rej);\n          });\n      } else {\n        var receiverType = user\n          ? CometChat.RECEIVER_TYPE.USER\n          : group\n          ? CometChat.RECEIVER_TYPE.GROUP\n          : undefined;\n        if (!receiverID || !receiverType) return;\n\n        var call = new CometChat.Call(receiverID, callType, receiverType, CometChat.CATEGORY_CALL);\n\n        CometChat.initiateCall(call).then(\n          (initiatedCall) => {\n            outGoingCall.current = initiatedCall;\n            setDisableButton(true);\n            setShowOutgoingCallScreen(true);\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, {\n              call: outGoingCall.current,\n            });\n          },\n          (error) => {\n            console.log(\"Call initialization failed with exception:\", error);\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, { call });\n            onError && onError(error);\n          }\n        );\n      }\n    } else {\n      console.log(\n        \"Invalid call type.\",\n        type,\n        CallTypeConstants.audio,\n        type != CallTypeConstants.audio || type != CallTypeConstants.video\n      );\n      return;\n    }\n  };\n\n  /**\n   * Initiates a voice call.\n   *\n   * Checks for necessary permissions before making the call.\n   */\n  const makeVoiceCall = async (): Promise<void> => {\n    if (disableButton) return;\n\n    if (!(await permissionUtil.startResourceBasedTask([\"mic\"]))) {\n      return;\n    }\n\n    callType.current = CallTypeConstants.audio;\n    makeCall(CallTypeConstants.audio);\n  };\n\n  /**\n   * Initiates a video call.\n   *\n   * Checks for necessary permissions (mic and camera) before making the call.\n   */\n  const makeVideoCall = async (): Promise<void> => {\n    if (disableButton) return;\n\n    if (!(await permissionUtil.startResourceBasedTask([\"mic\", \"camera\"]))) {\n      return;\n    }\n\n    callType.current = CallTypeConstants.video;\n    makeCall(CallTypeConstants.video);\n  };\n\n  // Set up event listeners and logged-in user on mount.\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((user) => (loggedInUser.current = user!))\n      .catch((rej: CometChat.CometChatException) => {\n        onError && onError(rej);\n      });\n\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onIncomingCallReceived: (call: CometChat.Call) => {\n          incomingCall.current = call;\n          setDisableButton(true);\n          setCallReceived(call);\n        },\n        onOutgoingCallAccepted: (call: CometChat.Call) => {\n          console.log(\"call accepted\");\n        },\n        onOutgoingCallRejected: (call: CometChat.Call) => {\n          setShowOutgoingCallScreen(false);\n          outGoingCall.current = undefined;\n          setDisableButton(false);\n        },\n        onIncomingCallCancelled: (call: CometChat.Call) => {\n          setCallReceived(undefined);\n          incomingCall.current = undefined;\n          setDisableButton(false);\n        },\n      })\n    );\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallRejected: (call: CometChat.Call) => {\n        outGoingCall.current = undefined;\n        setShowOutgoingCallScreen(false);\n        setDisableButton(false);\n      },\n      ccCallEnded: () => {\n        outGoingCall.current = undefined;\n        setShowOutgoingCallScreen(false);\n        setDisableButton(false);\n      },\n    });\n\n    checkActiveCallOnly();\n\n    return () => {\n      CometChat.removeCallListener(listenerId);\n      CometChatUIEventHandler.removeCallListener(listenerId);\n    };\n  }, []);\n\n  return (\n    <View style={callButtonStyles.containerStyle as ViewStyle}>\n      {!hideVoiceCallButton && (\n        <TouchableOpacity onPress={() => makeVoiceCall()}>\n          <Icon\n            name='call'\n            height={callButtonStyles?.audioCallButtonIconStyle?.height as DimensionValue}\n            width={callButtonStyles?.audioCallButtonIconStyle?.width as DimensionValue}\n            color={callButtonStyles?.audioCallButtonIconStyle?.tintColor as ColorValue}\n            imageStyle={callButtonStyles?.audioCallButtonIconStyle as ImageStyle}\n            icon={callButtonStyles?.audioCallButtonIcon as JSX.Element | ImageSourcePropType}\n            containerStyle={callButtonStyles?.audioCallButtonIconContainerStyle as ImageStyle}\n          />\n        </TouchableOpacity>\n      )}\n      {!hideVideoCallButton && (\n        <TouchableOpacity onPress={() => makeVideoCall()}>\n          <Icon\n            name='videocam'\n            height={callButtonStyles?.videoCallButtonIconStyle?.height as DimensionValue}\n            width={callButtonStyles?.videoCallButtonIconStyle?.width as DimensionValue}\n            color={callButtonStyles?.videoCallButtonIconStyle?.tintColor as ColorValue}\n            imageStyle={callButtonStyles?.videoCallButtonIconStyle as ImageStyle}\n            icon={callButtonStyles?.videoCallButtonIcon as JSX.Element | ImageSourcePropType}\n            containerStyle={callButtonStyles?.videoCallButtonIconContainerStyle as ImageStyle}\n          />\n        </TouchableOpacity>\n      )}\n      {showOutgoingCallScreen && (\n        <CometChatOutgoingCall\n          call={outGoingCall.current}\n          onEndCallButtonPressed={(call) => {\n            CometChat.rejectCall(call?.getSessionId(), CometChat.CALL_STATUS.CANCELLED).then(\n              (rejectedCall) => {\n                CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallRejected, {\n                  call: rejectedCall,\n                });\n              },\n              (err) => {\n                onError && onError(err);\n              }\n            );\n          }}\n          {...(callSettingsBuilder\n            ? {\n                callSettingsBuilder: callSettingsBuilder(\n                  props.user,\n                  props.group,\n                  callType.current == CallTypeConstants.audio ? true : false\n                ),\n              }\n            : {})}\n          {...outgoingCallConfiguration}\n        />\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallButtons/index.ts",
    "content": "import {\n  CometChatCallButtonConfiguration,\n  CometChatCallButtonConfigurationInterface,\n} from \"./CometChatCallButtonConfiguration\";\nimport { CometChatCallButtons, CometChatCallButtonsInterface } from \"./CometChatCallButtons\";\n\nexport type { CallButtonStyle } from \"./style\";\n\nexport { CometChatCallButtonConfiguration, CometChatCallButtons };\n\nexport type { CometChatCallButtonsInterface, CometChatCallButtonConfigurationInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallButtons/style.ts",
    "content": "import { ImageSourcePropType, ImageStyle, StyleSheet, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { JSX } from \"react\";\n\nexport type CallButtonStyle = {\n  containerStyle: ViewStyle;\n\n  audioCallButtonIcon?: ImageSourcePropType | JSX.Element;\n  audioCallButtonIconStyle: ImageStyle;\n  audioCallButtonIconContainerStyle: ViewStyle;\n\n  videoCallButtonIcon?: ImageSourcePropType | JSX.Element;\n  videoCallButtonIconStyle: ImageStyle;\n  videoCallButtonIconContainerStyle: ViewStyle;\n};\n\nexport const getCallButtonStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n) =>\n  StyleSheet.create({\n    containerStyle: {\n      flexDirection: \"row\",\n      gap: 16,\n    },\n    audioCallButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n    videoCallButtonIconStyle: {\n      tintColor: color.iconPrimary,\n      height: spacing.spacing.s6,\n      width: spacing.spacing.s6,\n    },\n  }) as CometChatTheme['callButtonStyles'];\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallLogs/CometChatCallLogs.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n  FlatList,\n  Image,\n  Text,\n  TouchableOpacity,\n  View,\n  GestureResponderEvent,\n  ActivityIndicator,\n} from \"react-native\";\nimport { CometChatAvatar, CometChatRetryButton } from \"../../shared\";\nimport { CallTypeConstants } from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CallUIEvents } from \"../CallEvents\";\nimport { CallingPackage } from \"../CallingPackage\";\nimport { CallUtils } from \"../CallUtils\";\nimport { CometChatOutgoingCall, CometChatOutgoingCallInterface } from \"../CometChatOutgoingCall\";\nimport { BackIcon } from \"./resources\";\nimport { Style } from \"./style\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { useTheme } from \"../../theme\";\nimport { DateHelper, dateHelperInstance } from \"../../shared/helper/dateHelper\";\nimport { ErrorEmptyView } from \"../../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { Skeleton } from \"./Skeleton\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { DeepPartial, ValueOf } from \"../../shared/helper/types\";\nimport { CometChatTooltipMenu, MenuItemInterface } from \"../../shared/views/CometChatTooltipMenu\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation, useLocalizedDate, LocalizedDateHelper } from \"../../shared\";\n\nconst listenerId = \"callEventListener_\" + new Date().getTime();\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\n/**\n * Props for configuring the CometChatCallLogs component.\n *\n * @interface CometChatCallLogsConfigurationInterface\n */\nexport interface CometChatCallLogsConfigurationInterface {\n  /** Custom component to render as the leading view for each call log item */\n  LeadingView?: (call?: any) => JSX.Element;\n  /** Custom component to render as the title view for each call log item */\n  TitleView?: (call?: any) => JSX.Element;\n  /** Custom component to render as the subtitle view for each call log item */\n  SubtitleView?: (call?: any) => JSX.Element;\n  /** Custom component to render as the entire item view for each call log */\n  ItemView?: (call?: any) => JSX.Element;\n  /** Custom component to render as the trailing view for each call log item */\n  TrailingView?: (call?: any, defaultOnPress?: (call: any) => void) => JSX.Element;\n  /** Custom options to render in the AppBar */\n  AppBarOptions?: () => JSX.Element;\n  /** Builder for custom call log requests */\n  callLogRequestBuilder?: any;\n  /** Date format pattern for call logs */\n  datePattern?: ValueOf<typeof DateHelper.patterns>;\n  /** Flag to hide the back button in the header */\n  showBackButton?: boolean;\n  /** Custom component to render when the call log list is empty */\n  EmptyView?: () => JSX.Element;\n  /** Custom component to render in case of an error */\n  ErrorView?: (e: CometChat.CometChatException) => JSX.Element;\n  /** Custom component to render while loading call logs */\n  LoadingView?: () => JSX.Element;\n  /** Flag to hide the error view */\n  hideError?: boolean;\n  /** Callback when the call icon is pressed */\n  onCallIconPress?: (item: any) => void;\n  /** Callback for handling errors */\n  onError?: (e: CometChat.CometChatException) => void;\n  /** Callback for when the back button is pressed */\n  onBack?: () => void;\n  /**\n   * Callback for when a call log item is pressed.\n   * Receives the raw call log object.\n   */\n  onItemPress?: (call: any) => void;\n  /** Custom style overrides for the call logs */\n  style?: DeepPartial<CometChatTheme[\"callLogsStyles\"]>;\n  /** Configuration for outgoing calls */\n  outgoingCallConfiguration?: CometChatOutgoingCallInterface;\n  /** Callback when the list is fetched and loaded */\n  onLoad?: (list: any[]) => void;\n  /** Callback when the list is empty (no items) */\n  onEmpty?: () => void;\n  /** Called on a long press of the default list item view */\n  onItemLongPress?: (prop: { call: any }) => void;\n  /** Hide the toolbar header */\n  hideHeader?: boolean;\n  /** Hide the loading state */\n  hideLoadingState?: boolean;\n  /**\n   * A function to **append** more menu items on top of the default menu items for a call log.\n   */\n  addOptions?: (call: any) => MenuItemInterface[];\n  /**\n   * A function to **replace** the default menu items entirely for a call log.\n   */\n  options?: (call: any) => MenuItemInterface[];\n}\n\n/**\n * CometChatCallLogs component.\n *\n * This component displays a list of call logs with support for custom item views,\n * pull-to-refresh, error and empty states, as well as outgoing call initiation.\n *\n * @param {CometChatCallLogsConfigurationInterface} props - Component configuration props.\n * @returns {JSX.Element} The rendered call logs component.\n */\nexport const CometChatCallLogs = (props: CometChatCallLogsConfigurationInterface): JSX.Element => {\n  const {\n    LeadingView,\n    TitleView,\n    SubtitleView,\n    ItemView,\n    TrailingView,\n    AppBarOptions,\n    callLogRequestBuilder,\n    showBackButton = false,\n    EmptyView,\n    ErrorView,\n    LoadingView,\n    hideError,\n    onCallIconPress,\n    onItemPress,\n    onError,\n    onBack,\n    style,\n    outgoingCallConfiguration,\n    datePattern,\n    onLoad,\n    onEmpty,\n    onItemLongPress,\n    hideHeader,\n    hideLoadingState,\n    addOptions,\n    options,\n  } = props;\n\n  const [list, setList] = useState<any[]>([]);\n  const [listState, setListState] = useState<\"loading\" | \"error\" | \"done\">(\"loading\");\n  const [showOutgoingCallScreen, setShowOutgoingCallScreen] = useState(false);\n\n  const theme = useTheme();\n  const {t} = useCometChatTranslation()\n  const { formatDate } = useLocalizedDate();\n  const mergedCallLogsStyle = useMemo(() => {\n    return deepMerge(theme.callLogsStyles, style ?? {});\n  }, [theme, style]);\n\n  const loggedInUser = useRef<CometChat.User>(undefined);\n  const callLogRequestBuilderRef = useRef<any>(undefined);\n  const outGoingCall = useRef<CometChat.Call | CometChat.CustomMessage>(undefined);\n\n  // State for tooltip functionality\n  const [tooltipVisible, setTooltipVisible] = useState<boolean>(false);\n  const [selectedCall, setSelectedCall] = useState<any>(null);\n  const tooltipPosition = useRef<{ pageX: number; pageY: number }>({\n    pageX: 0,\n    pageY: 0,\n  });\n  const [hasMoreData, setHasMoreData] = useState(true);\n\n  /**\n   * Function to build the list of menu items for the tooltip:\n   * - `options(call)` completely replaces defaults\n   * - `addOptions(call)` appends to the default\n   * - No default menu items in this snippet; so defaults is an empty array\n   */\n  const buildMenuItems = (call: any): MenuItemInterface[] => {\n    if (options) {\n      return options(call);\n    }\n    let defaultMenuItems: MenuItemInterface[] = []; // no default items here\n    if (addOptions) {\n      return [...defaultMenuItems, ...addOptions(call)];\n    }\n    return defaultMenuItems;\n  };\n\n  /**\n   * Show tooltip if user hasn't provided a custom onItemLongPress.\n   */\n  const handleItemLongPress = (call: any, e?: GestureResponderEvent) => {\n    if (onItemLongPress) {\n      // If the developer has provided a custom long-press handler, call that and return.\n      onItemLongPress({ call });\n      return;\n    }\n    // Otherwise, show the tooltip if there are menu items\n    const items = buildMenuItems(call);\n    if (items.length === 0) return;\n\n    if (e && e.nativeEvent) {\n      tooltipPosition.current = {\n        pageX: e.nativeEvent.pageX,\n        pageY: e.nativeEvent.pageY,\n      };\n    } else {\n      tooltipPosition.current = { pageX: 200, pageY: 100 };\n    }\n    setSelectedCall(call);\n    setTooltipVisible(true);\n  };\n\n  /**\n   * Initializes the call log request builder.\n   */\n  function setRequestBuilder() {\n    const reqBuilder = callLogRequestBuilder\n      ? callLogRequestBuilder.setAuthToken(loggedInUser.current!.getAuthToken())\n      : new CometChatCalls.CallLogRequestBuilder()\n        .setLimit(30)\n        .setAuthToken(loggedInUser.current!.getAuthToken() || \"\")\n        .setCallCategory(\"call\");\n    callLogRequestBuilderRef.current = reqBuilder.build();\n  }\n\n  /**\n   * Fetches the call logs using the configured request builder.\n   * @param isAppending - If true, appends to existing list (for pagination). If false, replaces the list (for initial load/reload).\n   */\n  const fetchCallLogs = (isAppending: boolean = true) => {\n    setListState(\"loading\");\n    callLogRequestBuilderRef\n      .current!.fetchNext()\n      .then((callLogs: any) => {\n        if (callLogRequestBuilderRef.current!.limit > callLogs.length) {\n          setHasMoreData(false);\n        }\n        if (callLogs.length > 0) {\n          const updatedList = isAppending ? [...list, ...callLogs] : callLogs;\n          setList(updatedList);\n          onLoad && onLoad(updatedList);\n        } else {\n          // If no new logs are returned and the current list is empty, trigger onEmpty\n          if (list.length === 0 || !isAppending) {\n            onEmpty && onEmpty();\n          }\n        }\n        setListState(\"done\");\n      })\n      .catch((err: CometChat.CometChatException) => {\n        onError && onError(err);\n        setListState(\"error\");\n      });\n  };\n\n  /**\n   * Reloads the call logs by resetting the request builder and fetching fresh data.\n   * This matches the behavior of other tabs (Chats, Groups, Users).\n   */\n  const reloadCallLogs = () => {\n    setList([]);\n    setHasMoreData(true);\n    setRequestBuilder();\n    fetchCallLogs(false);\n  };\n\n  // Setup logged-in user and call listeners on mount.\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((u: CometChat.User | null) => {\n        loggedInUser.current = u!;\n        setRequestBuilder();\n        fetchCallLogs();\n      })\n      .catch((e) => {\n        onError && onError(e);\n      });\n\n    // Listener for outgoing call rejection\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onOutgoingCallRejected: (call: CometChat.Call) => {\n          setShowOutgoingCallScreen(false);\n          outGoingCall.current = undefined;\n        },\n      })\n    );\n\n    // UI event listener for call rejection and call end\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallRejected: (call: CometChat.Call) => {\n        outGoingCall.current = undefined;\n        setShowOutgoingCallScreen(false);\n      },\n      ccCallEnded: () => {\n        outGoingCall.current = undefined;\n        setShowOutgoingCallScreen(false);\n      },\n    });\n\n    return () => {\n      // Cleanup call listeners when component unmounts\n      CometChat.removeCallListener(listenerId);\n      CometChatUIEventHandler.removeCallListener(listenerId);\n    };\n  }, []);\n\n  /**\n   * Initiates a call based on the provided call log and type.\n   *\n   * @param {any} call - The call log item.\n   * @param {any} type - The type of call (audio or video).\n   */\n  const makeCall = (call: any, type: any) => {\n    if (type == CallTypeConstants.audio || type == CallTypeConstants.video) {\n      let user =\n        call?.getReceiverType() == \"user\"\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n      let group =\n        call?.getReceiverType() == \"group\"\n          ? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()\n            ? call.getReceiver()\n            : call?.getInitiator()\n          : undefined;\n\n      var receiverID = user ? user.getUid() : group ? group.getGuid() : undefined;\n      var callType = type;\n      var receiverType = user\n        ? CometChat.RECEIVER_TYPE.USER\n        : group\n          ? CometChat.RECEIVER_TYPE.GROUP\n          : undefined;\n      if (!receiverID || !receiverType) return;\n\n      var callObject = new CometChat.Call(\n        receiverID,\n        callType,\n        receiverType,\n        CometChat.CATEGORY_CALL\n      );\n\n      CometChat.initiateCall(callObject).then(\n        (initiatedCall) => {\n          outGoingCall.current = initiatedCall;\n          setShowOutgoingCallScreen(true);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, {\n            call: outGoingCall.current,\n          });\n        },\n        (error) => {\n          console.log(\"Call initialization failed with exception:\", error);\n          CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, { call });\n          onError && onError(error);\n        }\n      );\n    } else {\n      console.log(\"Invalid call type.\", type);\n      return;\n    }\n  };\n\n  /**\n   * Handles the press event on the call icon.\n   */\n  const onPress = (item: any) => {\n    if (onCallIconPress) {\n      onCallIconPress(item);\n    } else {\n      if (item?.getReceiverType() == \"user\") {\n        makeCall(item, item.getType());\n      }\n    }\n  };\n\n  /**\n   * Extracts and returns call details for display.\n   */\n  const getCallDetails = (call: any) => {\n    const { mode, initiator, receiver, receiverType } = call;\n\n    if (mode == \"meet\") {\n      return {\n        title: receiver[\"name\"],\n        avatarUrl: receiver[\"icon\"],\n      };\n    } else if (mode == \"call\") {\n      return {\n        title:\n          receiverType === \"group\"\n            ? receiver[\"name\"]\n            : loggedInUser.current?.getUid() == initiator?.getUid()\n              ? receiver[\"name\"]\n              : initiator[\"name\"],\n        avatarUrl:\n          receiverType === \"group\"\n            ? receiver[\"avatar\"]\n            : loggedInUser.current?.getUid() == initiator?.getUid()\n              ? receiver[\"avatar\"]\n              : initiator[\"avatar\"],\n      };\n    }\n    return { title: \"\", avatarUrl: undefined };\n  };\n\n  /**\n   * Renders each call log item.\n   */\n  const _render = ({ item, index }: any) => {\n    // If user provides a custom item view, use that\n    if (ItemView) return ItemView(item);\n\n    const { title, avatarUrl } = getCallDetails(item);\n    const callStatus = CallUtils.getCallStatusForCallLogs(item, loggedInUser.current!);\n\n    return (\n      <TouchableOpacity\n        onPress={() => onItemPress?.(item)}\n        onLongPress={(e) => handleItemLongPress(item, e)}\n      >\n        <View style={mergedCallLogsStyle.itemStyle.containerStyle}>\n          {LeadingView ? (\n            LeadingView(item)\n          ) : (\n            <CometChatAvatar\n              name={title}\n              image={{ uri: avatarUrl }}\n              style={{\n                ...mergedCallLogsStyle.itemStyle.avatarStyle,\n              }}\n            />\n          )}\n          <View>\n            {TitleView ? (\n              TitleView(item)\n            ) : (\n              <Text\n                style={[\n                  mergedCallLogsStyle.itemStyle.titleTextStyle,\n                  callStatus === \"missed\"\n                    ? mergedCallLogsStyle.itemStyle.missedCallTitleTextStyle\n                    : {},\n                ]}\n              >\n                {title}\n              </Text>\n            )}\n            <View style={{ flexDirection: \"row\", alignItems: \"center\", gap: 4 }}>\n              <Icon\n                name={\n                  callStatus === \"missed\"\n                    ? \"call-missed-outgoing\"\n                    : callStatus === \"incoming\"\n                      ? \"call-received\"\n                      : \"call-made\"\n                }\n                size={16}\n                color={\n                  callStatus === \"missed\"\n                    ? mergedCallLogsStyle.itemStyle.missedCallStatusIconStyle.tintColor\n                    : callStatus === \"incoming\"\n                      ? mergedCallLogsStyle.itemStyle.incomingCallStatusIconStyle.tintColor\n                      : mergedCallLogsStyle.itemStyle.outgoingCallStatusIconStyle.tintColor\n                }\n                containerStyle={{ marginTop: 2 }}\n              />\n              {SubtitleView ? (\n                SubtitleView(item)\n              ) : (\n                <Text style={mergedCallLogsStyle.itemStyle.subTitleTextStyle}>\n                  {formatDate(\n                    item[\"initiatedAt\"] * 1000,\n                    datePattern ?? LocalizedDateHelper.patterns.callLogs\n                  )}\n                </Text>\n              )}\n            </View>\n          </View>\n          {TrailingView ? (\n            TrailingView(item,onPress)\n          ) : (\n            <TouchableOpacity\n              onPress={() => onPress(item)}\n              style={{\n                marginLeft: \"auto\",\n              }}\n            >\n              <Icon\n                name={item.type === \"audio\" ? \"call\" : \"videocam\"}\n                size={24}\n                color={mergedCallLogsStyle.itemStyle.callIconStyle.tintColor}\n                imageStyle={mergedCallLogsStyle.itemStyle.callIconStyle}\n              />\n            </TouchableOpacity>\n          )}\n        </View>\n      </TouchableOpacity>\n    );\n  };\n\n  /**\n   * Renders the error view for the call logs.\n   */\n  const ErrorStateView = useCallback(() => {\n    if (hideError) return null;\n    return (\n      <ErrorEmptyView\n        title={t(\"OOPS\")}\n        subTitle={t(\"SOMETHING_WENT_WRONG\")}\n        tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n        Icon={\n          <Icon\n            name='error-state'\n            size={theme.spacing.margin.m15 << 1}\n            containerStyle={{\n              marginBottom: theme.spacing.margin.m5,\n            }}\n          />\n        }\n        containerStyle={Style.errorEmptyContainer}\n        titleStyle={mergedCallLogsStyle.errorStateStyle?.titleStyle}\n        subTitleStyle={mergedCallLogsStyle.errorStateStyle?.subTitleStyle}\n        RetryView={<CometChatRetryButton onPress={reloadCallLogs} />}\n      />\n    );\n  }, [theme]);\n\n  /**\n   * Renders the empty state view for call logs.\n   */\n  const EmptyStateView = useCallback(() => {\n    if (EmptyView) return <EmptyView />;\n    return (\n      <ErrorEmptyView\n        title={t(\"NO_CALL_LOGS\")}\n        subTitle={t(\"CALL_LOGS_EMPTY_MESSAGE\")}\n        Icon={\n          <Icon\n            name='call-fill'\n            size={theme.spacing.spacing.s15 << 1}\n            color={theme.color.neutral300}\n            containerStyle={{\n              marginBottom: theme.spacing.spacing.s5,\n            }}\n          />\n        }\n        containerStyle={Style.errorEmptyContainer}\n        titleStyle={mergedCallLogsStyle.emptyStateStyle?.titleStyle}\n        subTitleStyle={mergedCallLogsStyle.emptyStateStyle?.subTitleStyle}\n      />\n    );\n  }, [theme]);\n\n  const renderFooter = useCallback(() => {\n    if (listState !== \"loading\" || !hasMoreData) return null;\n    return (\n      <View\n        style={{\n          paddingVertical: 20,\n          flex: 1,\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        <ActivityIndicator size='small' color={theme.color.primary} />\n      </View>\n    );\n  }, [theme, listState]);\n\n  return (\n    <View\n      style={{\n        backgroundColor: theme.color.background1,\n        height: \"100%\",\n        width: \"100%\",\n      }}\n    >\n      <>\n        {/* Header with optional back button and app bar options */}\n        {!hideHeader && (\n          <View\n            style={[\n              Style.row,\n              Style.headerStyle,\n              {\n                padding: theme.spacing.spacing.s4,\n                borderBottomWidth: 1,\n                borderBottomColor: theme.color.borderLight,\n                ...mergedCallLogsStyle.titleSeparatorStyle,\n              },\n            ]}\n          >\n            <View style={Style.row}>\n              {showBackButton ? (\n                <TouchableOpacity style={Style.imageStyle} onPress={onBack}>\n                  <Image\n                    source={BackIcon}\n                    style={[Style.imageStyle, { tintColor: theme.color.iconPrimary }]}\n                  />\n                </TouchableOpacity>\n              ) : null}\n              <Text style={mergedCallLogsStyle.titleTextStyle}>{t(\"CALLS\")}</Text>\n            </View>\n            <View style={Style.row}>{AppBarOptions && <AppBarOptions />}</View>\n          </View>\n        )}\n\n        {/* Render call logs based on state */}\n        {listState === \"loading\" && list.length === 0 ? (\n          !hideLoadingState ? (\n            LoadingView ? (\n              <LoadingView />\n            ) : (\n              <Skeleton style={mergedCallLogsStyle.skeletonStyle} />\n            )\n          ) : (\n            <View />\n          )\n        ) : listState === \"error\" && list.length === 0 ? (\n          <ErrorStateView />\n        ) : list.length === 0 ? (\n          <EmptyStateView />\n        ) : (\n          <FlatList\n            data={list}\n            keyExtractor={(item, index) => item.sessionId + \"_\" + index}\n            extraData={{ list, listState }}\n            renderItem={_render}\n            onEndReached={() => fetchCallLogs(true)}\n            ListFooterComponent={renderFooter}\n          />\n        )}\n      </>\n      {/* Outgoing call screen */}\n      {showOutgoingCallScreen && (\n        <CometChatOutgoingCall\n          call={outGoingCall.current}\n          onEndCallButtonPressed={(call) => {\n            CometChat.rejectCall(call?.getSessionId(), CometChat.CALL_STATUS.CANCELLED).then(\n              (rejectedCall) => {\n                CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallRejected, {\n                  call: rejectedCall,\n                });\n              },\n              (err) => {\n                onError && onError(err);\n              }\n            );\n          }}\n          {...outgoingCallConfiguration}\n        />\n      )}\n\n      {/* Tooltip menu for calls (on default long-press) */}\n      {selectedCall && tooltipVisible && (\n        <View\n          style={{\n            position: \"absolute\",\n            top: tooltipPosition.current.pageY,\n            left: tooltipPosition.current.pageX,\n            zIndex: 9999,\n          }}\n        >\n          <CometChatTooltipMenu\n            visible={tooltipVisible}\n            onClose={() => setTooltipVisible(false)}\n            onDismiss={() => setTooltipVisible(false)}\n            event={{\n              nativeEvent: tooltipPosition.current,\n            }}\n            menuItems={buildMenuItems(selectedCall).map((menuItem) => ({\n              text: menuItem.text,\n              onPress: () => {\n                menuItem.onPress();\n                setTooltipVisible(false);\n              },\n              textColor: menuItem.textStyle?.color,\n              iconColor: menuItem.iconStyle?.tintColor,\n              disabled: menuItem.disabled,\n            }))}\n          />\n        </View>\n      )}\n      {/* End tooltip menu */}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallLogs/Skeleton.tsx",
    "content": "/**\n * @file Skeleton.tsx\n * @description\n * Shimmer / skeleton loading component for the **Call Logs** list in CometChat UIKit.\n * It renders a column of 20 placeholder rows that mimic an avatar, name, subtitle and a\n * trailing action icon. A diagonal animated gradient (“shimmer”) sweeps across the rows\n * to hint that data is loading.\n *\n * The component is completely theme-aware and can be customised via the `style` prop\n * or by tweaking the UIKit theme’s `callLogsStyles.skeletonStyle` object.\n *\n */\n\nimport React, { useEffect, useRef } from \"react\";\nimport {\n  Animated,\n  Dimensions,\n  Easing,\n  ScrollView,\n  StyleSheet,\n  View,\n} from \"react-native\";\nimport Svg, {\n  Defs,\n  G,\n  LinearGradient,\n  Path,\n  Rect,\n  Stop,\n} from \"react-native-svg\";\nimport { useTheme } from \"../../theme\";\nimport { CometChatTheme } from \"../../theme/type\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\n/**\n * Shape of the style object accepted by this component.\n * It is taken straight from the UIKit theme definition so that consumers\n * may override only the bits they need.\n */\ntype SkeletonStyle = CometChatTheme[\"callLogsStyles\"][\"skeletonStyle\"];\n\ninterface SkeletonProps {\n  /**\n   * Per-instance style overrides.  **All keys are optional**; any missing keys\n   * fall back to the values provided by the current CometChat theme.\n   */\n  style?: Partial<SkeletonStyle>;\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                Row layouts                                 */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Bottom SVG layer – a set of simple `Rect`s that will receive the animated\n * gradient fill.  Placed below the solid colour mask from `SkeletonItemTop`.\n */\nconst SkeletonItemBottom: React.FC<SkeletonProps> = ({ style = {} }) => {\n  const theme = useTheme();\n  const defaults = theme.callLogsStyles.skeletonStyle;\n\n  /*   always-defined fallback */\n  const gradientColors =\n    style.linearGradientColors ??\n    defaults.linearGradientColors ??\n    [\"#E8E8E8\", \"#F5F5F5\"];\n\n  return (\n    <Svg\n      height={screenWidth / 5}\n      viewBox=\"0 0 360 72\"\n      fill=\"none\"\n      preserveAspectRatio=\"xMidYMid meet\"\n    >\n      {/* Avatar circle replacement */}\n      <Rect x=\"16\" y=\"12\" width=\"48\" height=\"48\" fill=\"url(#shimmerGradient)\" />\n      {/* Primary text placeholder */}\n      <Rect x=\"76\" y=\"15\" width=\"160\" height=\"22\" fill=\"url(#shimmerGradient)\" />\n      {/* Secondary text placeholder */}\n      <Rect x=\"76\" y=\"45\" width=\"80\" height=\"12\" fill=\"url(#shimmerGradient)\" />\n      {/* Trailing icon placeholder */}\n      <Rect x=\"312\" y=\"20\" width=\"32\" height=\"32\" fill=\"url(#shimmerGradient)\" />\n\n      <Defs>\n        <LinearGradient\n          id=\"shimmerGradient\"\n          x1={16}\n          y1={36}\n          x2={64}\n          y2={36}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <Stop stopColor={gradientColors[0]} />\n          <Stop offset={1} stopColor={gradientColors[1]} />\n        </LinearGradient>\n      </Defs>\n    </Svg>\n  );\n};\n\n/**\n * Top SVG layer – a solid colour mask with transparent “holes” cut out\n * where the animated gradient from `SkeletonItemBottom` should be visible.\n */\nconst SkeletonItemTop: React.FC<SkeletonProps> = ({ style = {} }) => {\n  const theme = useTheme();\n  const defaults = theme.callLogsStyles.skeletonStyle;\n\n  return (\n    <Svg height={screenWidth / 5} viewBox=\"0 0 360 72\" fill=\"none\">\n      <Path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M0 0H360V72H0V0ZM16 36C16 22.7452 26.7452 12 40 12C53.2548 12 64 22.7452 64 36C64 49.2548 53.2548 60 40 60C26.7452 60 16 49.2548 16 36ZM87 15C80.9249 15 76 19.9249 76 26C76 32.0751 80.9249 37 87 37H225C231.075 37 236 32.0751 236 26C236 19.9249 231.075 15 225 15H87ZM76 51C76 47.6863 78.6863 45 82 45H150C153.314 45 156 47.6863 156 51C156 54.3137 153.314 57 150 57H82C78.6863 57 76 54.3137 76 51ZM322 20C316.477 20 312 24.4772 312 30V42C312 47.5228 316.477 52 322 52H334C339.523 52 344 47.5228 344 42V30C344 24.4772 339.523 20 334 20H322Z\"\n        fill={\n          style.containerBackgroundColor ??\n          defaults.containerBackgroundColor\n        }\n      />\n    </Svg>\n  );\n};\n\n/* -------------------------------------------------------------------------- */\n/*                           Main Skeleton component                          */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated shimmer skeleton for the Call Logs list.\n *\n * @param props.style – Partial style overrides.\n *\n * @example\n * ```tsx\n * <Skeleton\n *   style={{\n *     linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n *     shimmerBackgroundColor: \"#FFFFFF\",\n *     shimmerOpacity: 0.12,\n *     speed: 2,\n *   }}\n * />\n * ```\n */\nexport const Skeleton: React.FC<SkeletonProps> = ({ style = {} }) => {\n  const theme = useTheme();\n  const defaults = theme.callLogsStyles.skeletonStyle;\n\n  /*   guaranteed speed */\n  const speed = style.speed ?? defaults.speed ?? 1;\n\n  const shimmerAnim = useRef(new Animated.Value(0)).current;\n\n  /* ------------------------------------------------------------------ */\n  /*                            Lifecycle                               */\n  /* ------------------------------------------------------------------ */\n  useEffect(() => {\n    shimmerAnim.setValue(0);\n    Animated.loop(\n      Animated.timing(shimmerAnim, {\n        toValue: 1,\n        duration: (1 / speed) * 1000, // safe\n        easing: Easing.linear,\n        useNativeDriver: false,\n      })\n    ).start();\n  }, [shimmerAnim, speed]);\n\n  /* Animated translation for the first and second diagonal bars */\n  const translateX = shimmerAnim.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth * 2, screenWidth],\n  });\n\n  /* ------------------------------------------------------------------ */\n  /*                              Render                                */\n  /* ------------------------------------------------------------------ */\n  return (\n    <ScrollView showsVerticalScrollIndicator={false} scrollEnabled={false}>\n      {/* Bottom layer – gradient-filled rows */}\n      {Array.from({ length: 20 }).map((_, i) => (\n        <SkeletonItemBottom key={`bottom-${i}`} style={style} />\n      ))}\n\n      {/* Diagonal shimmer bars (two for seamless wrap-around) */}\n      {[0, screenWidth / 2].map((offset, idx) => (\n        <Animated.View\n          // eslint-disable-next-line react/no-array-index-key\n          key={`shimmer-${idx}`}\n          style={[\n            localStyles.animatedBar,\n            {\n              transform: [\n                { translateX: Animated.add(translateX, offset) },\n                { translateY: -20 },\n                { rotate: \"15deg\" },\n              ],\n              backgroundColor:\n                style.shimmerBackgroundColor ??\n                defaults.shimmerBackgroundColor,\n              opacity: style.shimmerOpacity ?? defaults.shimmerOpacity,\n            },\n          ]}\n        />\n      ))}\n\n      <View style={StyleSheet.absoluteFill}>\n        {Array.from({ length: 20 }).map((_, i) => (\n          <SkeletonItemTop key={`top-${i}`} style={style} />\n        ))}\n      </View>\n    </ScrollView>\n  );\n};\n\n/* ----------------------------- local styles ------------------------------ */\n\nconst localStyles = StyleSheet.create({\n  /** Base style for each diagonal shimmer bar */\n  animatedBar: {\n    position: \"absolute\",\n    top: 0,\n    bottom: 0,\n    width: \"25%\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallLogs/index.ts",
    "content": "import { CometChatCallLogs, CometChatCallLogsConfigurationInterface } from \"./CometChatCallLogs\";\n\nexport { CometChatCallLogs };\nexport { getCallLogsStyleDark, getCallLogsStyleLight } from \"./style\";\n\nexport type { CometChatCallLogsConfigurationInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallLogs/resources/index.ts",
    "content": "import BackIcon from \"./Back.png\";\nimport CheckIcon from \"./check.png\";\nimport IncomingCallIcon from \"./incomingcall.png\";\nimport IncomingVideoIcon from \"./incomingvideo.png\";\nimport DetailIcon from \"./Info.png\";\nimport MissedCallIcon from \"./missedcall.png\";\nimport MissedVideoIcon from \"./missedvideo.png\";\nimport OutgoingCallIcon from \"./outgoingcall.png\";\nimport OutgoingVideoIcon from \"./outgoingvideo.png\";\nimport SelectionIcon from \"./Selection.png\";\nimport LoadingIcon from \"./Spinner.png\";\n\nexport {\n  BackIcon,\n  CheckIcon,\n  DetailIcon,\n  IncomingCallIcon,\n  IncomingVideoIcon,\n  LoadingIcon,\n  MissedCallIcon,\n  MissedVideoIcon,\n  OutgoingCallIcon,\n  OutgoingVideoIcon,\n  SelectionIcon,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatCallLogs/style.ts",
    "content": "import {\n  ColorValue,\n  ImageSourcePropType,\n  ImageStyle,\n  StyleSheet,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { AvatarStyle } from \"../../shared/views/CometChatAvatar\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { JSX } from \"react\";\n\nexport const Style = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  errorEmptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    paddingHorizontal: \"10%\",\n    gap: 4,\n  },\n  headerStyle: {\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n  },\n  row: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 10\n  },\n  imageStyle: {\n    height: 24,\n    width: 24,\n    alignSelf: \"center\",\n  },\n});\n\nexport type CallLogsStyle = {\n  emptyStateStyle: {\n    titleStyle: TextStyle;\n    subTitleStyle: TextStyle;\n    containerStyle: ViewStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n  };\n  errorStateStyle: {\n    titleStyle: TextStyle;\n    subTitleStyle: TextStyle;\n    containerStyle: ViewStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n  };\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  titleSeparatorStyle: ViewStyle;\n  itemStyle: {\n    containerStyle: ViewStyle;\n    titleTextStyle: TextStyle;\n    missedCallTitleTextStyle: TextStyle;\n    subTitleTextStyle: TextStyle;\n    avatarStyle: AvatarStyle;\n    callIconStyle: ImageStyle;\n    outgoingCallStatusIconStyle: ImageStyle;\n    incomingCallStatusIconStyle: ImageStyle;\n    missedCallStatusIconStyle: ImageStyle;\n  };\n  skeletonStyle: {\n    linearGradientColors?: [string, string];\n    shimmerBackgroundColor?: ColorValue;\n    shimmerOpacity?: number;\n    speed?: number;\n    containerBackgroundColor?: ColorValue;\n  };\n};\n\nexport const getCallLogsStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CallLogsStyle> =>\n  deepMerge(\n    {\n      containerStyle: {},\n      titleTextStyle: {\n        color: color.textPrimary,\n        ...typography.heading1.bold,\n      },\n      titleSeparatorStyle: {\n        borderBottomWidth: 1,\n        borderBottomColor: color.borderLight,\n        flexDirection: \"row\",\n        justifyContent: \"space-between\",\n        alignItems: \"center\",\n        width: \"100%\",\n      },\n      errorStateStyle: {\n        containerStyle: {},\n        titleStyle: {\n          color: color.textPrimary,\n          textAlign: \"center\",\n          ...typography.heading3.bold,\n        },\n        subTitleStyle: {\n          color: color.textSecondary,\n          textAlign: \"center\",\n          ...typography.body.regular,\n        },\n      },\n      emptyStateStyle: {\n        containerStyle: {},\n        titleStyle: {\n          color: color.textPrimary,\n          textAlign: \"center\",\n          ...typography.heading3.bold,\n        },\n        subTitleStyle: {\n          color: color.textSecondary,\n          textAlign: \"center\",\n          ...typography.body.regular,\n        },\n      },\n\n      itemStyle: getCallLogsItemStyle(color, spacing, typography),\n      skeletonStyle: {\n        linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n        shimmerBackgroundColor: color.staticBlack,\n        shimmerOpacity: 0.01,\n        speed: 1,\n        containerBackgroundColor: color.background2,\n      },\n    } as const,\n    {}\n  );\n\nexport const getCallLogsStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CallLogsStyle> =>\n  deepMerge(getCallLogsStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 1,\n      containerBackgroundColor: color.background2,\n    },\n  });\n\nexport type CallLogsItemStyle = {\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  missedCallTitleTextStyle: TextStyle;\n  subTitleTextStyle: TextStyle;\n  avatarStyle: AvatarStyle;\n  callIconStyle: ImageStyle;\n\n  outgoingCallStatusIconStyle: ImageStyle;\n  missedCallStatusIconStyle: ImageStyle;\n  incomingCallStatusIconStyle: ImageStyle;\n};\n\nexport const getCallLogsItemStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CallLogsItemStyle => {\n  return {\n    containerStyle: {\n      flex: 1,\n      paddingVertical: 12,\n      paddingHorizontal: 16,\n      flexDirection: \"row\",\n      gap: 12,\n      alignItems: \"center\",\n    },\n    titleTextStyle: {\n      color: color.textPrimary,\n      ...typography.heading4.medium,\n    },\n    missedCallTitleTextStyle: {\n      color: color.error,\n      ...typography.heading4.medium,\n    },\n    subTitleTextStyle: {\n      color: color.textSecondary,\n      ...typography.body.regular,\n    },\n    avatarStyle: {\n      containerStyle: {\n        width: 48,\n        height: 48,\n      },\n      imageStyle: {},\n      textStyle: {},\n    },\n    callIconStyle: {\n      tintColor: color.iconPrimary,\n    },\n    outgoingCallStatusIconStyle: {\n      tintColor: color.success,\n    },\n    incomingCallStatusIconStyle: {\n      tintColor: color.success,\n    },\n    missedCallStatusIconStyle: {\n      tintColor: color.error,\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatIncomingCall/CometChatIncomingCall.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Text, TouchableOpacity, View, StyleSheet } from \"react-native\";\nimport { CometChatAvatar, CometChatSoundManager } from \"../../shared\";\nimport {\n  CallTypeConstants,\n  MessageCategoryConstants,\n  MessageTypeConstants,\n} from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CallUIEvents } from \"../CallEvents\";\nimport { CallingPackage } from \"../CallingPackage\";\nimport { CometChatOngoingCall } from \"../CometChatOngoingCall\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { useTheme } from \"../../theme\";\nimport { IncomingCallStyle } from \"./style\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { JSX } from \"react\";\nimport { useCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\n\nconst listnerID = \"CALL_LISTENER_\" + new Date().getTime();\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\n/**\n * Props for the CometChatIncomingCall component.\n *\n * @interface CometChatIncomingCallInterface\n */\nexport interface CometChatIncomingCallInterface {\n  /** The incoming call object, which can be a Call or a CustomMessage */\n  call: CometChat.Call | CometChat.CustomMessage | any;\n  /** Custom view for the entire call item */\n  ItemView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /** Custom view for the title section of the call item */\n  TitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /** Custom view for the subtitle section of the call item */\n  SubtitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /** Custom view for the leading section of the call item */\n  LeadingView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /** Custom view for the trailing section of the call item */\n  TrailingView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /** Flag to disable sound for incoming calls */\n  disableSoundForCalls?: boolean;\n  /** Path or identifier for a custom sound to play for incoming calls */\n  customSoundForCalls?: string;\n  /** Callback fired when the call is accepted */\n  onAccept?: (message: CometChat.BaseMessage) => void;\n  /** Callback fired when the call is declined */\n  onDecline: (message: CometChat.BaseMessage) => void;\n  /** Callback fired when an error occurs */\n  onError?: (e: CometChat.CometChatException) => void;\n  /** Optional custom call settings builder */\n  callSettingsBuilder?: typeof CometChatCalls.CallSettingsBuilder;\n  /** Custom style overrides for the incoming call component */\n  style?: DeepPartial<IncomingCallStyle>;\n}\n\n/**\n * CometChatIncomingCall component.\n *\n * This component handles incoming calls by playing a sound, offering accept/decline buttons,\n * and showing an ongoing call screen if accepted. Custom views for various parts of the call UI\n * can be provided via props.\n *\n * @param {CometChatIncomingCallInterface} props - Component configuration props.\n * @returns {JSX.Element} The rendered incoming call UI.\n */\nexport const CometChatIncomingCall = (props: CometChatIncomingCallInterface): JSX.Element => {\n  const {\n    onAccept,\n    onDecline,\n    customSoundForCalls,\n    disableSoundForCalls,\n    ItemView,\n    TitleView,\n    SubtitleView,\n    LeadingView,\n    TrailingView,\n    call,\n    onError,\n    callSettingsBuilder,\n    style,\n  } = props;\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const [showCallScreen, setShowCallScreen] = useState(false);\n  const acceptedCall = useRef<CometChat.Call>(undefined);\n\n  /** Reference to the call listener */\n  const callListener = useRef<any>(undefined);\n  /** Reference to the call settings builder instance */\n  const callSettings = useRef<any>(undefined);\n\n  // Merge the default and custom styles for incoming calls.\n  const incomingCallStyle = useMemo(() => {\n    return deepMerge(theme.incomingCallStyle, style ?? {});\n  }, [theme.incomingCallStyle, style]);\n\n  /**\n   * Ends the call by rejecting it and emitting a call rejected event.\n   */\n  const endCall = () => {\n    CometChat.rejectCall(call[\"sessionId\"], CometChat.CALL_STATUS.REJECTED).then(\n      (rejectedCall) => {\n        CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallRejected, {\n          call: rejectedCall,\n        });\n        // Notify parent so it can unmount this component.\n        onDecline && onDecline(rejectedCall);\n        CometChatSoundManager.pause();\n      },\n      (err) => {\n        onError && onError(err);\n      }\n    );\n  };\n\n  /**\n   * Accepts the incoming call.\n   *\n   * If a custom onAccept callback is provided, it is used instead of the default behavior.\n   */\n  const acceptCall = () => {\n    CometChatSoundManager.pause();\n    if (onAccept) {\n      onAccept(call);\n      return;\n    }\n    CometChat.acceptCall(call[\"sessionId\"]).then(\n      (accepted) => {\n        acceptedCall.current = accepted!;\n        setShowCallScreen(true);\n        CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallAccepted, {\n          call: accepted,\n        });\n      },\n      (err) => {\n        onError && onError(err);\n      }\n    );\n  };\n\n  /**\n   * Checks if the provided call is a default CometChat call (not a custom meeting call).\n   *\n   * @param {CometChat.BaseMessage} ccCall - The call to check.\n   * @returns {Boolean} True if it's a default call; otherwise, false.\n   */\n  function isDefaultCall(ccCall: CometChat.BaseMessage): Boolean {\n    return ccCall.getCategory() === MessageCategoryConstants.call;\n  }\n\n  // Set up listeners and call settings on component mount.\n  useEffect(() => {\n    if (call && !disableSoundForCalls && call.getType() !== MessageTypeConstants.meeting) {\n      // Play a custom or default incoming call ringtone.\n      if (customSoundForCalls) {\n        CometChatSoundManager.play(\"incomingCall\", customSoundForCalls);\n      } else {\n        CometChatSoundManager.play(\"incomingCall\");\n      }\n    }\n\n    // Add a call listener for call cancellation.\n    CometChat.addCallListener(\n      listnerID,\n      new CometChat.CallListener({\n        onIncomingCallCancelled: () => {\n          CometChatSoundManager.pause();\n        },\n      })\n    );\n\n    // Create an ongoing call listener for managing call events.\n    callListener.current = new CometChatCalls.OngoingCallListener({\n      onCallEnded: () => {\n        CometChatCalls.endSession();\n        CometChat.clearActiveCall();\n        CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, {});\n        setShowCallScreen(false);\n        acceptedCall.current = undefined;\n      },\n      onCallEndButtonPressed: () => {\n        if (isDefaultCall(call)) {\n          CometChat.endCall(call.getSessionId()).then((endedCall) => {\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, {\n              call: endedCall,\n            });\n          });\n        }\n      },\n      onUserJoined: (user: CometChat.User) => {\n        console.log(\"user joined:\", user);\n      },\n      onUserLeft: (user: CometChat.User) => {\n        if (isDefaultCall(call)) {\n          CometChat.endCall(call.getSessionId())\n            .then((endedCall2) => {\n              CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, {\n                call: endedCall2,\n              });\n            })\n            .catch((err) => {\n              console.log(\"Error on userLeft:\", err);\n            });\n        }\n      },\n      onError: (error: CometChat.CometChatException) => {\n        CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, { error });\n        onError && onError(error);\n      },\n    });\n\n    // Initialize call settings using the provided builder or default values.\n    callSettings.current =\n      callSettingsBuilder?.setCallEventListener(callListener.current) ??\n      new CometChatCalls.CallSettingsBuilder()\n        .enableDefaultLayout(true)\n        .setCallEventListener(callListener.current)\n        .setIsAudioOnlyCall(call[\"type\"] === \"audio\");\n\n    // Cleanup listeners and pause any sounds on unmount.\n    return () => {\n      CometChatUIEventHandler.removeCallListener(listnerID);\n      CometChat.removeCallListener(listnerID);\n      CometChatSoundManager.pause();\n    };\n  }, []);\n\n  /**\n   * If the call is accepted, render the ongoing call screen.\n   */\n  if (showCallScreen) {\n    return (\n      <CometChatOngoingCall\n        sessionID={acceptedCall.current?.getSessionId()!}\n        onError={onError}\n        callSettingsBuilder={callSettings.current}\n      />\n    );\n  }\n\n  /**\n   * Render a custom ItemView if provided.\n   */\n  if (ItemView) {\n    return ItemView(call);\n  }\n\n  /**\n   * Render the default incoming call overlay with header and action buttons.\n   */\n  return (\n    <SafeAreaView style={styles.overlay}>\n      <View style={[incomingCallStyle.containerStyle, { width: \"100%\" }]}>\n        {/* Top row: LeadingView, Title/Subtitle, TrailingView */}\n        <View style={styles.topRow}>\n          {LeadingView && LeadingView(call)}\n          <View>\n            {TitleView ? (\n              TitleView(call)\n            ) : (\n              <Text style={incomingCallStyle.titleTextStyle}>\n                {call[\"sender\"]?.[\"name\"] ?? t(\"INCOMING_CALL\")}\n              </Text>\n            )}\n\n            {SubtitleView ? (\n              SubtitleView(call)\n            ) : (\n              <View style={styles.rowInline}>\n                <Icon\n                  name='call-fill'\n                  size={16}\n                  containerStyle={{ marginTop: 4, marginRight: 4 }}\n                />\n                <Text style={incomingCallStyle.subtitleTextStyle}>\n                  {call?.[\"type\"] === CallTypeConstants.audio\n                    ? t(\"INCOMING_AUDIO_CALL\")\n                    : t(\"INCOMING_VIDEO_CALL\")}\n                </Text>\n              </View>\n            )}\n          </View>\n\n          {TrailingView ? (\n            TrailingView(call)\n          ) : (\n            <CometChatAvatar\n              name={call?.[\"sender\"]?.[\"name\"]}\n              image={{ uri: call?.[\"sender\"]?.[\"avatar\"] }}\n              style={incomingCallStyle.avatarStyle}\n            />\n          )}\n        </View>\n\n        {/* Buttons row */}\n        <View style={[styles.bottomRow, { marginTop: 16 }]}>\n          <TouchableOpacity onPress={endCall} style={incomingCallStyle.declineCallButtonStyle}>\n            <Text style={incomingCallStyle.declineCallTextStyle}>{t(\"DECLINE\")}</Text>\n          </TouchableOpacity>\n\n          <TouchableOpacity onPress={acceptCall} style={incomingCallStyle.acceptCallButtonStyle}>\n            <Text style={incomingCallStyle.acceptCallTextStyle}>{t(\"ACCEPT\")}</Text>\n          </TouchableOpacity>\n        </View>\n      </View>\n    </SafeAreaView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  overlay: {\n    position: \"absolute\",\n    top: 0,\n    left: 10,\n    right: 10,\n    bottom: 0,\n    zIndex: 99999,\n  },\n  topRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n  },\n  bottomRow: {\n    flexDirection: \"row\",\n    gap: 10,\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n  },\n  rowInline: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatIncomingCall/index.ts",
    "content": "import { CometChatIncomingCall, CometChatIncomingCallInterface } from \"./CometChatIncomingCall\";\n\nexport {\n  CometChatIncomingCall,\n};\n\nexport type {\n  CometChatIncomingCallInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatIncomingCall/resources/index.ts",
    "content": "import AcceptCall from \"./Call.png\";\nimport DeclineIcon from \"./close.png\";\nimport AudioCall from \"./incomingaudiocall.png\";\nimport VideoCall from \"./incomingvideocall.png\";\n\nexport { AcceptCall, AudioCall, DeclineIcon, VideoCall };\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatIncomingCall/style.ts",
    "content": "import { TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\n\nexport type IncomingCallStyle = {\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  subtitleTextStyle: TextStyle;\n  avatarStyle: CometChatTheme[\"avatarStyle\"];\n  acceptCallTextStyle: TextStyle;\n  acceptCallButtonStyle: ViewStyle;\n  declineCallTextStyle: TextStyle;\n  declineCallButtonStyle: ViewStyle;\n};\n\nexport const getIncomingCallStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): IncomingCallStyle => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background3,\n      padding: 20,\n      borderRadius: 12,\n      shadowColor: \"#000\",\n      shadowOffset: {\n        width: 0,\n        height: 6,\n      },\n      shadowOpacity: 0.37,\n      shadowRadius: 7.49,\n\n      elevation: 12,\n    },\n    titleTextStyle: {\n      color: color.textPrimary,\n      ...typography.heading2.bold,\n    },\n    subtitleTextStyle: {\n      color: color.textSecondary,\n      marginTop: spacing.spacing.s1,\n      ...typography.heading4.regular,\n    },\n    avatarStyle: {\n      containerStyle: {\n        height: 48,\n        width: 48,\n      },\n      imageStyle: {},\n      textStyle: {},\n    },\n    acceptCallButtonStyle: {\n      flex: 1,\n      backgroundColor: color.success,\n      paddingVertical: 12,\n      borderRadius: 8,\n    },\n    acceptCallTextStyle: {\n      color: color.primaryButtonText,\n      textAlign: \"center\",\n      ...typography.button.medium,\n    },\n    declineCallButtonStyle: {\n      flex: 1,\n      backgroundColor: color.error,\n      paddingVertical: 12,\n      borderRadius: 8,\n    },\n    declineCallTextStyle: {\n      color: color.primaryButtonText,\n      textAlign: \"center\",\n      ...typography.button.medium,\n    },\n  };\n};"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOngoingCall/CometChatOngoingCall.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { ActivityIndicator, View } from \"react-native\";\nimport { CallingPackage } from \"../CallingPackage\";\nimport { useTheme } from \"../../theme\";\nimport { JSX } from \"react\";\nimport { NativeModules } from \"react-native\";\nconst { Proximity } = NativeModules;\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\n/**\n * Props for the CometChatOngoingCall component.\n *\n * @interface CometChatOngoingCallInterface\n */\nexport interface CometChatOngoingCallInterface {\n  /** The session ID for the ongoing call */\n  sessionID: string;\n  /**\n   * An instance of the CallSettingsBuilder used to configure the call.\n   * This is typically built using CometChatCalls.CallSettingsBuilder.\n   */\n  callSettingsBuilder: typeof CometChatCalls.CallSettingsBuilder;\n  /** Callback fired when an error occurs */\n  onError?: (e: CometChat.CometChatException) => void;\n}\n\n/**\n * CometChatOngoingCall component.\n *\n * This component handles the ongoing call session by generating a call token and\n * rendering the CometChatCalls component with the appropriate call settings and token.\n *\n * @param {CometChatOngoingCallInterface} props - Component configuration props.\n * @returns {JSX.Element} The rendered ongoing call component.\n */\nexport const CometChatOngoingCall = (props: CometChatOngoingCallInterface): JSX.Element => {\n  const { callSettingsBuilder, onError, sessionID } = props;\n\n  const [callToken, setToken] = useState<string | undefined>(undefined);\n  // Build call settings once using the provided builder.\n  const callSettings = useRef(callSettingsBuilder?.build());\n  const theme = useTheme();\n\n  useEffect(() => {\n    // Fetch the logged-in user and generate a token for the ongoing call session.\n    CometChat.getLoggedinUser()\n      .then((user) => {\n        if (!user) {\n          throw new Error('No logged in user found');\n        }\n        \n        const authToken = user.getAuthToken();\n        console.log('[CometChatOngoingCall] Got auth token, generating call token...');\n        \n        return CometChatCalls.generateToken(sessionID, authToken);\n      })\n      .then((token: any) => {\n        console.log('[CometChatOngoingCall] Successfully generated call token');\n        setToken(token.token);\n      })\n      .catch((rej: CometChat.CometChatException | Error) => {\n        console.error('[CometChatOngoingCall] Failed to generate token for session:', sessionID, rej);\n        setToken(undefined);\n        onError && onError(rej instanceof Error ? \n          new CometChat.CometChatException({\n            code: 'TOKEN_GENERATION_FAILED',\n            message: rej.message\n          }) : rej\n        );\n      });\n\n    // Cleanup call settings on unmount.\n    return () => {\n      callSettings.current = null;\n    };\n  }, [sessionID, onError]);\n\n\n  useEffect(() => {\n    if (callToken) {\n      Proximity?.setEnabled(true);\n    }\n\n    return () => {\n      Proximity?.setEnabled(false);\n    };\n  }, [callToken]);\n\n  return (\n    <View style={[{ height: \"100%\", width: \"100%\", position: \"relative\" }]}>\n      {(callSettings.current && callToken && (\n        <CometChatCalls.Component callSettings={callSettings.current} callToken={callToken} />\n      )) || (\n        <View\n          style={{\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            height: \"100%\",\n            backgroundColor: \"transparent\",\n          }}\n        >\n          <ActivityIndicator size={\"large\"} color={theme.color.primary} />\n        </View>\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOngoingCall/index.ts",
    "content": "import { CometChatOngoingCall, CometChatOngoingCallInterface } from \"./CometChatOngoingCall\";\n\nexport { CometChatOngoingCall};\nexport type {CometChatOngoingCallInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOutgoingCall/CometChatOutgoingCall.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Modal, Text, TouchableOpacity, View } from \"react-native\";\nimport {\n  MessageCategoryConstants,\n  MessageTypeConstants,\n} from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CometChatSoundManager } from \"../../shared/resources\";\nimport { CometChatAvatar } from \"../../shared/views\";\nimport { CallUIEvents } from \"../CallEvents\";\nimport { CallingPackage } from \"../CallingPackage\";\nimport { CometChatOngoingCall } from \"../CometChatOngoingCall\";\nimport { useTheme } from \"../../theme\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { OutgoingCallStyle } from \"./styles\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { useCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\n\nconst listenerId = \"callListener_\" + new Date().getTime();\nconst CometChatCalls = CallingPackage.CometChatCalls;\n\n/**\n * Props for the CometChatOutgoingCall component.\n *\n * @interface CometChatOutgoingCallInterface\n */\nexport interface CometChatOutgoingCallInterface {\n  /**\n   * The outgoing call object, can be a CometChat.Call or CometChat.CustomMessage.\n   */\n  call?: CometChat.Call | CometChat.CustomMessage | any;\n  /**\n   * Action to be performed on click of the cancel/reject button.\n   * Provides the CometChat.Call object as argument.\n   */\n  onEndCallButtonPressed?: (call: CometChat.Call) => void;\n  /**\n   * Flag to disable sound for the call.\n   */\n  disableSoundForCalls?: boolean;\n  /**\n   * Custom sound for the call.\n   */\n  customSoundForCalls?: string;\n  /**\n   * Custom call settings builder instance.\n   */\n  callSettingsBuilder?: typeof CometChatCalls.CallSettingsBuilder;\n  /**\n   * Custom style overrides for the outgoing call component.\n   */\n  style?: DeepPartial<OutgoingCallStyle>;\n  /**\n   * Custom view for the title section.\n   */\n  TitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /**\n   * Custom view for the subtitle section.\n   */\n  SubtitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /**\n   * Custom view for the avatar section.\n   */\n  AvatarView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /**\n   * Custom view for the end call button.\n   */\n  EndCallView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  /**\n   * Callback fired when an error occurs.\n   * @param {CometChat.CometChatException} error - The error object.\n   */\n  onError?: (error: CometChat.CometChatException) => void;\n}\n\n/**\n * CometChatOutgoingCall component.\n *\n * This component handles the UI for an outgoing call by playing sound,\n * rendering call details and providing an end call action. It listens to call events\n * to update the UI based on the call's state.\n *\n * @param {CometChatOutgoingCallInterface} props - Component configuration props.\n * @returns {JSX.Element} The rendered outgoing call UI.\n */\nexport const CometChatOutgoingCall = (props: CometChatOutgoingCallInterface): JSX.Element => {\n  const {\n    call,\n    customSoundForCalls,\n    disableSoundForCalls,\n    onEndCallButtonPressed,\n    callSettingsBuilder,\n    style,\n    TitleView,\n    SubtitleView,\n    AvatarView,\n    EndCallView,\n    onError,\n  } = props;\n\n  // State to track whether the call is connected.\n  const [isCallConnected, setCallConnected] = useState(false);\n\n  // Controls visibility of this modal.\n  const [isModalVisible, setModalVisible] = useState(true);\n\n  const ongoingCall = useRef<CometChat.Call | CometChat.CustomMessage>(undefined);\n  const callSessionId = useRef<string>(undefined);\n  const callListener = useRef<any>(null);\n  const callSettings = useRef<any>(null);\n  const isCallEnded = useRef<null | boolean>(undefined);\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  // Merge default and custom styles for the outgoing call.\n  const outgoingCallStyle = useMemo(() => {\n    return deepMerge(theme.outgoingCallStyle, style ?? {});\n  }, [theme.outgoingCallStyle, style]);\n\n  function checkIfDefaultCall(call: CometChat.BaseMessage): boolean {\n    return call.getCategory() === MessageCategoryConstants.call;\n  }\n\n  /**\n   * Ends the call if required by checking if it is a default call.\n   */\n  const endCallIfRequired = () => {\n    if (call && checkIfDefaultCall(call)) {\n      CometChat.endCall((call as CometChat.Call).getSessionId())\n        .then(() => {\n          (call as CometChat.Call).setStatus(\"ended\");\n          if (!isCallEnded.current) {\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call });\n          }\n          isCallEnded.current = true;\n        })\n        .catch((err) => {\n          console.log(\"Error\", err);\n          onError && onError(err);\n        });\n    }\n  };\n\n  // Set up listeners, call settings, and sound for the outgoing call.\n  useEffect(() => {\n    if (\n      call &&\n      (call[\"status\"] === \"ongoing\" ||\n        (call.getCategory() === (CometChat.CATEGORY_CUSTOM as CometChat.MessageCategory) &&\n          call.getType() === MessageTypeConstants.meeting))\n    ) {\n      ongoingCall.current = call;\n      if (call.getType() == MessageTypeConstants.meeting) {\n        callSessionId.current = (\n          (call as CometChat.CustomMessage).getCustomData() as any\n        )?.sessionId;\n      }\n      if (call.getCategory() === MessageCategoryConstants.call) {\n        callSessionId.current = call[\"sessionId\"];\n      }\n      setCallConnected(true);\n    }\n\n    if (!disableSoundForCalls && call?.getType() !== MessageTypeConstants.meeting) {\n      if (customSoundForCalls) {\n        CometChatSoundManager.play(\"outgoingCall\", customSoundForCalls);\n      } else {\n        CometChatSoundManager.play(\"outgoingCall\");\n      }\n    }\n\n    // Add call listener to handle outgoing call acceptance and rejection.\n    CometChat.addCallListener(\n      listenerId,\n      new CometChat.CallListener({\n        onOutgoingCallAccepted: (acceptedCall: any) => {\n          CometChatSoundManager.pause();\n          ongoingCall.current = acceptedCall;\n          callSessionId.current = acceptedCall[\"sessionId\"];\n          setCallConnected(true);\n        },\n        onOutgoingCallRejected: () => {\n          CometChatSoundManager.pause();\n          ongoingCall.current = undefined;\n          callSessionId.current = undefined;\n          setCallConnected(false);\n        },\n      })\n    );\n\n    // Listen for call failure events.\n    CometChatUIEventHandler.addCallListener(listenerId, {\n      ccCallFailed: (error: CometChat.CometChatException) => {\n        setCallConnected(false);\n        onError && onError(error);\n      },\n    });\n\n    // Create an ongoing call listener to manage call events.\n    callListener.current = new CometChatCalls.OngoingCallListener({\n      onCallEnded: () => {\n        CometChatCalls.endSession();\n        if (checkIfDefaultCall(call)) {\n          CometChat.clearActiveCall();\n          setCallConnected(false);\n          call.setStatus(\"ended\");\n          if (!isCallEnded.current) {\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call });\n          }\n          isCallEnded.current = true;\n        }\n      },\n      onCallEndButtonPressed: () => {\n        if (!checkIfDefaultCall(call)) {\n          setCallConnected(false);\n          call.setStatus(\"ended\");\n          if (!isCallEnded.current) {\n            CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call });\n          }\n          isCallEnded.current = true;\n        } else {\n          endCallIfRequired();\n        }\n      },\n      onUserJoined: (user: CometChat.User) => {\n        console.log(\"user joined:\", user);\n      },\n      onUserLeft: (user: CometChat.User) => {\n        endCallIfRequired();\n      },\n      onError: (error: CometChat.CometChatException) => {\n        CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, { error });\n      },\n    });\n\n    // Determine call type (audio or meeting) and initialize call settings.\n    const callType =\n      call?.getType() === MessageTypeConstants.meeting\n        ? call[\"customData\"]?.[\"callType\"]\n        : call.getType();\n\n    callSettings.current =\n      callSettingsBuilder?.setCallEventListener(callListener.current) ??\n      new CometChatCalls.CallSettingsBuilder()\n        .enableDefaultLayout(true)\n        .setCallEventListener(callListener.current)\n        .setIsAudioOnlyCall(callType === \"audio\");\n\n    // Cleanup on unmount.\n    return () => {\n      if (!disableSoundForCalls) {\n        CometChatSoundManager.pause();\n      }\n      CometChat.removeCallListener(listenerId);\n    };\n  }, []);\n\n  /**\n   * Handles closing the modal (via hardware back button on Android or a custom button).\n   * This is where you can call onEndCallButtonPressed or do any other cleanup.\n   */\n  const handleModalClose = () => {\n    if (onEndCallButtonPressed) {\n      onEndCallButtonPressed(call as CometChat.Call);\n    }\n    // Hide the modal – onDismiss fires after the animation completes\n    setModalVisible(false);\n  };\n\n  const callReceiverName = call?.getReceiver?.().getName?.() ?? \"Unknown\";\n\n  return (\n    <Modal\n      transparent\n      animationType='fade'\n      visible={isModalVisible}\n      onRequestClose={handleModalClose}\n    >\n      <SafeAreaView style={{ flex: 1 }}>\n        {isCallConnected ? (\n          <CometChatOngoingCall\n            sessionID={callSessionId.current!}\n            callSettingsBuilder={callSettings.current!}\n          />\n        ) : (\n          <View style={outgoingCallStyle.containerStyle}>\n            {TitleView ? (\n              TitleView(call)\n            ) : (\n              <Text style={outgoingCallStyle.titleTextStyle}>{callReceiverName}</Text>\n            )}\n            {SubtitleView ? (\n              SubtitleView(call)\n            ) : (\n              <Text style={outgoingCallStyle.subtitleTextStyle}>{t(\"CALLING\")}</Text>\n            )}\n            {AvatarView ? (\n              AvatarView(call)\n            ) : (\n              <CometChatAvatar\n                name={callReceiverName}\n                image={{\n                  uri:\n                    call?.getReceiverType?.() === \"user\"\n                      ? (call?.getReceiver?.() as CometChat.User)?.getAvatar()\n                      : (call?.getReceiver?.() as CometChat.Group)?.getIcon(),\n                }}\n                style={outgoingCallStyle.avatarStyle}\n              />\n            )}\n            {EndCallView ? (\n              EndCallView(call)\n            ) : (\n              <TouchableOpacity\n                style={outgoingCallStyle.endCallButtonStyle}\n                onPress={handleModalClose}\n              >\n                <Icon\n                  name='call-end-fill'\n                  size={32}\n                  height={outgoingCallStyle.endCallIconStyle.height}\n                  width={outgoingCallStyle.endCallIconStyle.width}\n                  color={outgoingCallStyle.endCallIconStyle.tintColor}\n                  imageStyle={outgoingCallStyle.endCallIconStyle}\n                />\n              </TouchableOpacity>\n            )}\n          </View>\n        )}\n      </SafeAreaView>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOutgoingCall/OutgoingCallConfiguration.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CallingPackage } from \"../CallingPackage\";\nimport { OutgoingCallStyle } from \".\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { JSX } from \"react\";\n\nconst CometChatCalls = CallingPackage.CometChatCalls;\nexport class OutgoingCallConfiguration {\n  disableSoundForCalls?: boolean;\n  customSoundForCalls?: string;\n  callSettingsBuilder?: typeof CometChatCalls.CallSettingsBuilder; // TODO: maybe not required\n  onEndCallButtonPressed?: (call: CometChat.Call) => void;\n  style?: DeepPartial<OutgoingCallStyle>;\n  TitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  SubtitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  AvatarView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  EndCallView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n\n  constructor(params: {\n    disableSoundForCalls?: boolean;\n    customSoundForCalls?: string;\n    callSettingsBuilder?: typeof CometChatCalls.CallSettingsBuilder;\n    onEndCallButtonPressed?: (call: CometChat.Call) => void;\n    style?: DeepPartial<OutgoingCallStyle>;\n    TitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n    SubtitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n    AvatarView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n    EndCallView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element;\n  }) {\n    this.disableSoundForCalls = params.disableSoundForCalls;\n    this.customSoundForCalls = params.customSoundForCalls;\n    this.callSettingsBuilder = params.callSettingsBuilder;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOutgoingCall/index.ts",
    "content": "export { CometChatOutgoingCall } from \"./CometChatOutgoingCall\";\nexport type { CometChatOutgoingCallInterface } from \"./CometChatOutgoingCall\";\nexport { OutgoingCallConfiguration } from \"./OutgoingCallConfiguration\";\nexport { getOutgoingCallStyle } from \"./styles\";\nexport type { OutgoingCallStyle } from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOutgoingCall/resources/index.ts",
    "content": "import DeclineIcon from \"./Close.png\";\n\nexport { DeclineIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/CometChatOutgoingCall/styles.ts",
    "content": "import { ImageStyle, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\n\nexport type OutgoingCallStyle = {\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  subtitleTextStyle: TextStyle;\n  avatarStyle: CometChatTheme[\"avatarStyle\"];\n  endCallButtonStyle: ViewStyle;\n  endCallIconStyle: ImageStyle;\n};\n\nexport const getOutgoingCallStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): OutgoingCallStyle => {\n  return {\n    containerStyle: {\n      flex: 1,\n      backgroundColor: color.background2,\n      alignItems: \"center\",\n      paddingVertical: 100,\n      paddingHorizontal: spacing.spacing.s6,\n    },\n    titleTextStyle: {\n      color: color.textPrimary,\n      textAlign: \"center\",\n      ...typography.heading1.bold,\n    },\n    subtitleTextStyle: {\n      color: color.textSecondary,\n      marginTop: spacing.spacing.s2,\n      ...typography.body.regular,\n    },\n    avatarStyle: {\n      containerStyle: {\n        marginTop: spacing.spacing.s10,\n        height: 120,\n        width: 120,\n        borderRadius: spacing.radius.max,\n      },\n      imageStyle: {},\n      textStyle: {},\n    },\n    endCallButtonStyle: {\n      marginTop: \"auto\",\n      backgroundColor: \"red\",\n      padding: 14,\n      borderRadius: 1000,\n    },\n    endCallIconStyle: {\n      tintColor: color.staticWhite,\n      height: 32,\n      width: 32,\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/index.ts",
    "content": "import { CallingExtension } from \"./CallingExtension\";\nimport { CallingExtensionDecorator } from \"./CallingExtensionDecorator\";\n\nimport { CometChatMeetCallBubble } from \"./CometChatCallBubble\";\nimport {\n  CallButtonStyle,\n  CometChatCallButtonConfiguration,\n  CometChatCallButtonConfigurationInterface,\n  CometChatCallButtons,\n  CometChatCallButtonsInterface,\n} from \"./CometChatCallButtons\";\nimport { CometChatIncomingCall } from \"./CometChatIncomingCall\";\nimport { CometChatOngoingCall } from \"./CometChatOngoingCall\";\nimport { CometChatOutgoingCall } from \"./CometChatOutgoingCall\";\n\nimport { CallUIEvents } from \"./CallEvents\";\nimport { CallingPackage } from \"./CallingPackage\";\n\nimport {\n  CometChatCallLogs,\n  CometChatCallLogsConfigurationInterface,\n} from \"./CometChatCallLogs\";\n\nexport {\n  CallingExtension,\n  CallingExtensionDecorator,\n  CallingPackage,\n  CallUIEvents,\n  CometChatMeetCallBubble,\n  CometChatCallButtonConfiguration,\n  CometChatCallButtons,\n  CometChatCallLogs,\n  CometChatIncomingCall,\n  CometChatOngoingCall,\n  CometChatOutgoingCall,\n};\n\nexport type {\n  CallButtonStyle,\n  CometChatCallButtonConfigurationInterface,\n  CometChatCallButtonsInterface,\n  CometChatCallLogsConfigurationInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/calls/resources/index.ts",
    "content": "import AudioIcon from \"./Call.png\";\nimport VideoIcon from \"./Video.png\";\nimport NextArrowIcon from \"./nextarrow.png\";\n\nexport { AudioIcon, NextArrowIcon, VideoIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeBubble/CometChatCollaborativeBubble.tsx",
    "content": "import React, { JSX, useCallback } from \"react\";\nimport { NativeModules, Pressable, Text, TextStyle, View, ViewStyle } from \"react-native\";\nimport { useTheme } from \"../../theme\";\n\nconst WebView = NativeModules[\"WebViewManager\"];\n\ninterface CollaborativeBubbleProps {\n  /** Text displayed in bubble title */\n  title: string;\n  /** Icon displayed in trailing position */\n  icon?: JSX.Element;\n  /** Subtitle string displayed after title */\n  subtitle?: string;\n  /** Button text */\n  buttonText?: string;\n  /** URL to open collaborative document */\n  url: string;\n  /** Style for title */\n  titleStyle?: TextStyle;\n  /** Style for subtitle */\n  subtitleStyle?: TextStyle;\n  /** Callback function when pressed */\n  onPress?: (url: string) => void;\n  /** Image element */\n  image?: JSX.Element;\n  /** Style for divider */\n  dividerStyle?: ViewStyle;\n  /** Style for button container */\n  buttonViewStyle?: ViewStyle;\n  /** Style for button text */\n  buttonTextStyle?: TextStyle;\n}\n\nexport const CometChatCollaborativeBubble = (props: CollaborativeBubbleProps) => {\n  const theme = useTheme();\n\n  // Opens the URL via onPress callback if provided; otherwise uses WebView.\n  const openLink = () => {\n    if (props.url !== \"\") {\n      if (props.onPress) {\n        props.onPress(props.url);\n      } else {\n        console.log(\"opening URL,\", props.url);\n        WebView.openUrl(props.url);\n      }\n    }\n  };\n\n  // Returns the button text if available.\n  const getButtonText = useCallback(() => {\n    return props.buttonText && props.buttonText.trim().length > 0 ? props.buttonText : \"\";\n  }, [props.buttonText]);\n\n  return (\n    <View>\n      {props.image}\n      <View\n        style={{\n          flexDirection: \"row\",\n          paddingVertical: theme.spacing.padding.p2,\n          gap: theme.spacing.spacing.s1,\n        }}\n      >\n        {props.icon}\n        <View style={{ flex: 1 }}>\n          <Text numberOfLines={2} ellipsizeMode=\"tail\" style={props.titleStyle}>\n            {props.title}\n          </Text>\n          <Text numberOfLines={2} ellipsizeMode=\"tail\" style={props.subtitleStyle}>\n            {props.subtitle}\n          </Text>\n        </View>\n      </View>\n      <View style={props.dividerStyle} />\n      <Pressable style={props.buttonViewStyle}>\n        <Text onPress={openLink} style={props.buttonTextStyle}>\n          {getButtonText()}\n        </Text>\n      </Pressable>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeBubble/resources/index.ts",
    "content": "import WhiteBoard from \"./whiteboard.png\";\n\nexport const WHITEBOARDICON = { WhiteBoard };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeBubble/style.ts",
    "content": "import { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../../theme/type\";\n\nexport const getCollabBubbleStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"collaborativeBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"collaborativeBubbleStyles\"];\n} => {\n  return {\n    incomingBubbleStyle: {\n      containerStyle: {\n        paddingHorizontal: spacing.padding.p1,\n        paddingTop: spacing.padding.p1,\n      },\n      imageStyle: {\n        width: 232,\n        height: 140,\n        borderRadius: spacing.radius.r2,\n      },\n      titleStyle: {\n        ...typography.body.medium,\n        color: color.receiveBubbleText,\n      },\n      subtitleStyle: {\n        ...typography.caption2.regular,\n        color: color.receiveBubbleTimestamp,\n      },\n      iconStyle: {\n        height: 32,\n        width: 32,\n        tintColor: color.receiveBubbleIcon,\n      },\n      iconContainerStyle: {\n        paddingHorizontal: spacing.padding.p1\n      },\n      buttonViewStyle: {\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n      },\n      buttonTextStyle: {\n        ...typography.button.medium,\n        color: color.primaryButtonBackground,\n        textAlign: 'center'\n      },\n      dividerStyle: {\n        height: 1,\n        backgroundColor: color.borderDark,\n        width: 240,\n        marginLeft: -4, // Negative margin equal to parent's padding\n        marginRight: -4, // Negative margin equal to parent's padding\n      }\n    },\n    outgoingBubbleStyle: {\n      containerStyle: {\n        paddingHorizontal: spacing.padding.p1,\n        paddingTop: spacing.padding.p1,\n      },\n      imageStyle: {\n        width: 232,\n        height: 140,\n        borderRadius: spacing.radius.r2,\n      },\n      titleStyle: {\n        ...typography.body.medium,\n        color: color.sendBubbleText,\n      },\n      subtitleStyle: {\n        ...typography.caption2.regular,\n        color: color.sendBubbleText,\n      },\n      iconStyle: {\n        height: 32,\n        width: 32,\n        tintColor: color.sendBubbleIcon,\n      },\n      iconContainerStyle: {\n        paddingHorizontal: spacing.padding.p1\n      },\n      buttonViewStyle: {\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n      },\n      buttonTextStyle: {\n        ...typography.button.medium,\n        color: color.sendBubbleText,\n        textAlign: 'center'\n      },\n      dividerStyle: {\n        height: 1,\n        backgroundColor: color.extendedPrimary800,\n        width: 240,\n        //position: 'absolute',\n        marginLeft: -4, // Negative margin equal to parent's padding\n        marginRight: -4, // Negative margin equal to parent's padding\n      }\n    },\n  };\n};\n\nexport const getCollabBubbleStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"collaborativeBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"collaborativeBubbleStyles\"];\n} => {\n  return deepMerge(getCollabBubbleStyleLight(color, spacing, typography)!, {});\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeDocument/CollaborativeDocumentExtension.tsx",
    "content": "import { ChatConfigurator, DataSource, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { CollaborativeDocumentExtensionDecorator } from \"./CollaborativeDocumentExtensionDecorator\";\n\nexport class CollaborativeDocumentExtension extends ExtensionsDataSource {\n  constructor() {\n    super();\n  }\n\n  /**\n   * enable\n   *  @description enables the Document  extension which includes Data profanity and data masking\n   */\n\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource: DataSource) => {\n      return new CollaborativeDocumentExtensionDecorator(dataSource);\n    });\n  }\n\n  override getExtensionId(): string {\n    return ExtensionConstants.document;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeDocument/CollaborativeDocumentExtensionDecorator.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { Text, View } from \"react-native\";\nimport { CometChatUIKit } from \"../../shared\";\nimport {\n  AdditionalAttachmentOptionsParams,\n  AdditionalParams,\n  MessageBubbleAlignmentType,\n} from \"../../shared/base/Types\";\nimport {\n  MessageCategoryConstants,\n  MetadataConstants,\n  ReceiverTypeConstants,\n} from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEvents, MessageEvents } from \"../../shared/events\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { ChatConfigurator, DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { CometChatMessageComposerAction } from \"../../shared/helper/types\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { CometChatMessageTemplate } from \"../../shared/modals\";\nimport { getMessagePreviewInternal } from \"../../shared/utils/MessageUtils\";\nimport { DEFAULT_ICONS } from \"../../theme/default/resources/icons\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { CometChatCollaborativeBubble } from \"../CollaborativeBubble/CometChatCollaborativeBubble\";\nimport { ExtensionConstants, ExtensionTypeConstants } from \"../ExtensionConstants\";\nimport { getExtensionData } from \"../ExtensionModerator\";\nimport { getCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew/LocalizationManager\";\nimport { CometChatMessageEvents } from \"../../shared/events/CometChatMessageEvents\";\nimport { messageStatus } from \"../../shared/utils/CometChatMessageHelper\";\nconst t = getCometChatTranslation();\n\nexport class CollaborativeDocumentExtensionDecorator extends DataSourceDecorator {\n  documentUrl: string = \"v1/create\";\n\n  constructor(dataSource: DataSource) {\n    super(dataSource);\n  }\n\n  isDeletedMessage(message: CometChat.BaseMessage): boolean {\n    return message.getDeletedBy() != null;\n  }\n\n  getId(): string {\n    return \"CollaborativeDocument\";\n  }\n\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element {\n    if (conversation.getLastMessage() == undefined) {\n      return \"\";\n    }\n\n    if (\n      conversation.getLastMessage().getType() == ExtensionTypeConstants.document &&\n      conversation.getLastMessage().getCategory() == MessageCategoryConstants.custom &&\n      conversation.getLastMessage().getDeletedAt() === undefined\n    ) {\n      return getMessagePreviewInternal(\n        \"collaborative-document-fill\",\n        t(\"CUSTOM_MESSAGE_DOCUMENT\"),\n        { theme }\n      );\n    } else {\n      return super.getLastConversationMessage(conversation, theme);\n    }\n  }\n\n  getAllMessageCategories(): string[] {\n    var categoryList: string[] = super.getAllMessageCategories();\n    if (!categoryList.includes(MessageCategoryConstants.custom)) {\n      categoryList.push(MessageCategoryConstants.custom);\n    }\n    return categoryList;\n  }\n\n  getAllMessageTypes(): string[] {\n    var messagesTypes: string[] = super.getAllMessageTypes();\n    messagesTypes.push(ExtensionTypeConstants.document);\n    return messagesTypes;\n  }\n\n  getAttachmentOptions(\n    theme: CometChatTheme,\n    user?: any,\n    group?: any,\n    composerId?: any,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ): CometChatMessageComposerAction[] {\n    let attachmentOptions: CometChatMessageComposerAction[] = super.getAttachmentOptions(\n      theme,\n      user,\n      group,\n      composerId,\n      additionalAttachmentOptionsParams\n    );\n    if (additionalAttachmentOptionsParams?.hideCollaborativeDocumentOption)\n      return attachmentOptions;\n    if (\n      composerId == undefined ||\n      (composerId as Map<any, any>).get(\"parentMessageId\") == undefined\n    )\n      attachmentOptions.push({\n        id: \"document\",\n        title: t(\"COLLABORATIVE_DOCUMENT\"),\n        icon: (\n          <Icon\n            name='collaborative-document-icon'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n        onPress: (user, group) => {\n          this.shareCollaborativedocument(\n            user, \n            group, \n            additionalAttachmentOptionsParams?.replyToMessage,\n            additionalAttachmentOptionsParams?.closeReplyPreview\n          );\n        },\n      });\n    return attachmentOptions;\n  }\n\n  shareCollaborativedocument(\n    user?: CometChat.User, \n    group?: CometChat.Group,\n    replyToMessage?: CometChat.BaseMessage,\n    closeReplyPreview?: () => void\n  ) {\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.ccToggleBottomSheet, {\n      isBottomSheetVisible: false,\n    });\n\n    let receiverId!: string;\n    let receiverType!: string;\n\n    if (user != undefined) {\n      receiverId = user.getUid();\n      receiverType = ReceiverTypeConstants.user;\n    } else if (group != undefined) {\n      receiverId = group.getGuid();\n      receiverType = ReceiverTypeConstants.group;\n    } else {\n    }\n\n    const payload: any = {\n      receiver: receiverId,\n      receiverType: receiverType,\n    };\n\n    if (replyToMessage) {\n      payload.quotedMessageId = replyToMessage.getId();\n    }\n\n    if (closeReplyPreview) {\n      closeReplyPreview();\n    }\n\n    CometChat.callExtension(\n      ExtensionConstants.document,\n      ExtensionConstants.post,\n      this.documentUrl,\n      payload\n    )\n      .then((response) => {\n        console.log(\"extension sent \", response);\n        if (replyToMessage) {\n          CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n            message: replyToMessage,\n            status: messageStatus.success,\n          });\n        }\n      })\n      .catch((error) => {\n        console.log(\"error\", error);\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageError, error);\n      });\n  }\n\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    let templateList: CometChatMessageTemplate[] = super.getAllMessageTemplates(\n      theme,\n      additionalParams\n    );\n\n    templateList.push(\n      new CometChatMessageTemplate({\n        type: ExtensionTypeConstants.document,\n        category: MessageCategoryConstants.custom,\n        ContentView: (message: CometChat.BaseMessage, _alignment: MessageBubbleAlignmentType) => {\n          if (this.isDeletedMessage(message)) {\n            return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n          } else {\n            return this.getCollaborativeBubble(message, _alignment, theme);\n          }\n        },\n        ReplyView: (\n          message: CometChat.BaseMessage,\n          _alignment?: MessageBubbleAlignmentType,\n          onReplyViewClicked?: (messageToReply: CometChat.BaseMessage) => void\n        ) => {\n          let documentMessage: CometChat.CustomMessage = message as CometChat.CustomMessage;\n          return ChatConfigurator.dataSource.getReplyView?.(documentMessage, theme, additionalParams) || null;\n        },\n        options: (\n          loggedInUser: CometChat.User,\n          messageObject: CometChat.BaseMessage,\n          theme: CometChatTheme,\n          group?: CometChat.Group\n        ) =>\n          ChatConfigurator.dataSource.getMessageOptions(\n            loggedInUser,\n            messageObject,\n            theme,\n            group,\n            additionalParams\n          ),\n        BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          return ChatConfigurator.dataSource.getBottomView(message, alignment);\n        },\n      })\n    );\n\n    return templateList;\n  }\n\n  getCollaborativeBubble(\n    message: CometChat.BaseMessage,\n    _alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    if (message) {\n      const documentData = getExtensionData(message, MetadataConstants.extensions?.document);\n\n      if (documentData && documentData.document_url && documentData.document_url.trim().length) {\n        let url: string = documentData.document_url;\n\n        const _style =\n          message.getSender().getUid() === loggedInUser!.getUid()\n            ? theme.messageListStyles.outgoingMessageBubbleStyles?.collaborativeBubbleStyles\n            : theme.messageListStyles.incomingMessageBubbleStyles?.collaborativeBubbleStyles;\n\n        return (\n          <CometChatCollaborativeBubble\n            title={t(\"COLLABORATIVE_DOCUMENT\")}\n            titleStyle={_style?.titleStyle}\n            subtitle={t(\"OPEN_DOCUMENT_TO_DRAW\")}\n            subtitleStyle={_style?.subtitleStyle}\n            buttonText={t(\"OPEN_DOCUMENT\")}\n            icon={\n              <Icon\n                name='collaborative-whiteboard-fill'\n                height={_style?.iconStyle?.height}\n                width={_style?.iconStyle?.width}\n                color={_style?.iconStyle?.tintColor}\n                icon={_style?.iconCollaborativeDocument}\n                imageStyle={_style?.iconStyle}\n                containerStyle={_style?.iconContainerStyle}\n              ></Icon>\n            }\n            url={url}\n            image={\n              <Icon\n                icon={_style?.imageCollaborativeDocument ?? DEFAULT_ICONS.COLLAB_DOCUMENT_3X}\n                height={_style?.imageStyle?.height}\n                width={_style?.imageStyle?.width}\n                color={_style?.imageStyle?.tintColor}\n                imageStyle={_style?.imageStyle}\n                containerStyle={_style?.imageContainerStyle}\n              ></Icon>\n            }\n            buttonViewStyle={_style?.buttonViewStyle}\n            buttonTextStyle={_style?.buttonTextStyle}\n            dividerStyle={_style?.dividerStyle}\n          />\n        );\n      }\n    }\n\n    return (\n      <View>\n        <Text>{\"no match\"}</Text>\n      </View>\n    );\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeDocument/index.ts",
    "content": "import { CometChatCollaborativeBubble } from \"../CollaborativeBubble/CometChatCollaborativeBubble\";\nimport { CollaborativeDocumentExtension } from \"./CollaborativeDocumentExtension\";\nimport { CollaborativeDocumentExtensionDecorator } from \"./CollaborativeDocumentExtensionDecorator\";\n\nexport {\n  CollaborativeDocumentExtension,\n  CollaborativeDocumentExtensionDecorator,\n  CometChatCollaborativeBubble as CometChatCollaborativeDocumentBubble,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeDocument/resources/index.ts",
    "content": "import CollaborativeDocument from \"./collaborative_document.png\";\nimport CollaborativeDocument2X from \"./collaborative_document_2x.png\";\n\nexport const DOCUMENTICON = CollaborativeDocument;\nexport const DOCUMENTICON2X = CollaborativeDocument2X;\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeWhiteboard/CollaborativeWhiteboardExtension.tsx",
    "content": "import { ChatConfigurator, DataSource, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { CollaborativeWhiteboardExtensionDecorator } from \"./CollaborativeWhiteboardExtensionDecorator\";\n\nexport class CollaborativeWhiteboardExtension extends ExtensionsDataSource {\n  constructor() {\n    super();\n  }\n\n  /**\n   * enable\n   *  @description enables the Text moderation extension which includes Data profanity and data masking\n   */\n\n  //override addExtension method from ExtensionsDataSource interface\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource: DataSource) => {\n      return new CollaborativeWhiteboardExtensionDecorator(\n        dataSource\n      );\n    });\n  }\n\n  //override getExtensionId method from ExtensionsDataSource interface\n  override getExtensionId(): string {\n    return ExtensionConstants.whiteboard;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeWhiteboard/CollaborativeWhiteboardExtensionDecorator.tsx",
    "content": "import { DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport {\n  MessageCategoryConstants,\n  MetadataConstants,\n  ReceiverTypeConstants,\n} from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEvents, MessageEvents } from \"../../shared/events\";\nimport { ChatConfigurator } from \"../../shared/framework\";\nimport { CometChatMessageComposerAction } from \"../../shared/helper/types\";\nimport { CometChatMessageTemplate } from \"../../shared/modals\";\nimport { CometChatCollaborativeBubble } from \"../CollaborativeBubble/CometChatCollaborativeBubble\";\nimport { ExtensionConstants, ExtensionTypeConstants } from \"../ExtensionConstants\";\nimport { getExtensionData } from \"../ExtensionModerator\";\nimport React, { JSX } from \"react\";\nimport { Text, View } from \"react-native\";\nimport { CometChatUIKit } from \"../../shared\";\nimport {\n  AdditionalAttachmentOptionsParams,\n  AdditionalParams,\n  MessageBubbleAlignmentType,\n} from \"../../shared/base/Types\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { getMessagePreviewInternal } from \"../../shared/utils/MessageUtils\";\nimport { DEFAULT_ICONS } from \"../../theme/default/resources/icons\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { getCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew/LocalizationManager\";\nimport { CometChatMessageEvents } from \"../../shared/events/CometChatMessageEvents\";\nimport { messageStatus } from \"../../shared/utils/CometChatMessageHelper\";\nconst t = getCometChatTranslation();\n\n/**\n * Extension decorator for Collaborative Whiteboard messages.\n */\nexport class CollaborativeWhiteboardExtensionDecorator extends DataSourceDecorator {\n  whiteboardUrl: string = \"v1/create\";\n\n  loggedInUser!: CometChat.User;\n\n  /**\n   * Creates an instance of CollaborativeWhiteboardExtensionDecorator.\n   *\n   * @param dataSource - The data source instance to decorate.\n   */\n  constructor(dataSource: DataSource) {\n    super(dataSource);\n\n    CometChat.getLoggedinUser()\n      .then((u) => {\n        this.loggedInUser = u!;\n      })\n      .catch((err) => console.log(err));\n  }\n\n  /**\n   * Checks if the given message is deleted.\n   *\n   * @param message - The message to check.\n   * @returns True if the message is deleted.\n   */\n  isDeletedMessage(message: CometChat.BaseMessage): boolean {\n    return message.getDeletedBy() != null;\n  }\n\n  /**\n   * Returns the unique ID for this extension.\n   *\n   * @returns The extension ID.\n   */\n  getId(): string {\n    return \"CollaborativeWhiteBoard\";\n  }\n\n  /**\n   * Gets a preview for the last conversation message.\n   *\n   * @param conversation - The conversation object.\n   * @param theme - (Optional) The theme.\n   * @returns A string or JSX.Element to display as the conversation preview.\n   */\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element {\n    if (conversation.getLastMessage() == undefined) {\n      return \"\";\n    }\n    if (\n      (conversation.getLastMessage() as CometChat.BaseMessage).getType() ==\n        ExtensionTypeConstants.whiteboard &&\n      (conversation.getLastMessage() as CometChat.BaseMessage).getCategory() ==\n        MessageCategoryConstants.custom &&\n      (conversation.getLastMessage() as CometChat.BaseMessage).getDeletedAt() === undefined\n    ) {\n      return getMessagePreviewInternal(\n        \"collaborative-whiteboard-fill\",\n        t(\"COLLABORATIVE_WHITEBOARD\"),\n        { theme }\n      );\n    } else {\n      return super.getLastConversationMessage(conversation, theme);\n    }\n  }\n\n  /**\n   * Returns an array of all message categories, ensuring custom category is included.\n   *\n   * @returns Array of message categories.\n   */\n  getAllMessageCategories(): string[] {\n    const categoryList: string[] = super.getAllMessageCategories();\n    if (!categoryList.includes(MessageCategoryConstants.custom)) {\n      categoryList.push(MessageCategoryConstants.custom);\n    }\n    return categoryList;\n  }\n\n  /**\n   * Returns an array of all message types, including whiteboard type.\n   *\n   * @returns Array of message types.\n   */\n  getAllMessageTypes(): string[] {\n    const messagesTypes: string[] = super.getAllMessageTypes();\n    messagesTypes.push(ExtensionTypeConstants.whiteboard);\n    return messagesTypes;\n  }\n\n  /**\n   * Returns the attachment options for the composer, including the whiteboard option.\n   *\n   * @param theme - The current theme.\n   * @param user - (Optional) The user object.\n   * @param group - (Optional) The group object.\n   * @param composerId - (Optional) The composer ID.\n   * @returns Array of attachment option actions.\n   */\n  getAttachmentOptions(\n    theme: CometChatTheme,\n    user?: any,\n    group?: any,\n    composerId?: any,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ): CometChatMessageComposerAction[] {\n    const attachmentOptions: CometChatMessageComposerAction[] = super.getAttachmentOptions(\n      theme,\n      user,\n      group,\n      composerId,\n      additionalAttachmentOptionsParams\n    );\n    if (additionalAttachmentOptionsParams?.hideCollaborativeWhiteboardOption)\n      return attachmentOptions;\n    if (\n      composerId === undefined ||\n      (composerId as Map<any, any>).get(\"parentMessageId\") === undefined\n    ) {\n      attachmentOptions.push({\n        id: \"whiteboard\",\n        title: t(\"COLLABORATIVE_WHITEBOARD\"),\n        icon: (\n          <Icon\n            name='collaborative-whiteboard-icon'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n        onPress: (user, group) => {\n          this.shareCollaborativeWhiteboard(\n            user, \n            group, \n            additionalAttachmentOptionsParams?.replyToMessage,\n            additionalAttachmentOptionsParams?.closeReplyPreview\n          );\n        },\n      });\n    }\n    return attachmentOptions;\n  }\n\n  /**\n   * Shares a collaborative whiteboard by calling the extension.\n   *\n   * @param user - (Optional) The user object.\n   * @param group - (Optional) The group object.\n   * @param replyToMessage - (Optional) The message to reply to.\n   * @param closeReplyPreview - (Optional) Function to close reply preview.\n   */\n  shareCollaborativeWhiteboard(\n    user?: CometChat.User, \n    group?: CometChat.Group,\n    replyToMessage?: CometChat.BaseMessage,\n    closeReplyPreview?: () => void\n  ) {\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.ccToggleBottomSheet, {\n      isBottomSheetVisible: false,\n    });\n    let receiverId!: string;\n    let receiverType!: string;\n\n    if (user != undefined) {\n      receiverId = user.getUid();\n      receiverType = ReceiverTypeConstants.user;\n    } else if (group != undefined) {\n      receiverId = group.getGuid();\n      receiverType = ReceiverTypeConstants.group;\n    }\n\n    const payload: any = {\n      receiver: receiverId,\n      receiverType: receiverType,\n    };\n\n    if (replyToMessage) {\n      payload.quotedMessageId = replyToMessage.getId();\n    }\n\n    if (closeReplyPreview) {\n      closeReplyPreview();\n    }\n\n    CometChat.callExtension(\n      ExtensionConstants.whiteboard,\n      ExtensionConstants.post,\n      this.whiteboardUrl,\n      payload\n    )\n      .then((response) => {\n        console.log(\"extension sent \", response);\n        if (replyToMessage) {\n          CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n            message: replyToMessage,\n            status: messageStatus.success,\n          });\n        }\n      })\n      .catch((error) => {\n        console.log(\"error\", error);\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageError, error);\n      });\n  }\n\n  /**\n   * Returns all message templates including the whiteboard template.\n   *\n   * @param theme - The current theme.\n   * @param additionalParams - (Optional) Additional parameters.\n   * @returns Array of message templates.\n   */\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    const templateList: CometChatMessageTemplate[] = super.getAllMessageTemplates(\n      theme,\n      additionalParams\n    );\n\n    templateList.push(\n      new CometChatMessageTemplate({\n        type: ExtensionTypeConstants.whiteboard,\n        category: MessageCategoryConstants.custom,\n        ContentView: (message: CometChat.BaseMessage, _alignment: MessageBubbleAlignmentType) => {\n          if (this.isDeletedMessage(message)) {\n            return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n          } else {\n            return this.getCollaborativeBubble(message, _alignment, theme);\n          }\n        },\n        ReplyView: (\n          message: CometChat.BaseMessage,\n          _alignment?: MessageBubbleAlignmentType,\n          onReplyViewClicked?: (messageToReply: CometChat.BaseMessage) => void\n        ) => {\n          let whiteboardMessage: CometChat.CustomMessage = message as CometChat.CustomMessage;\n          return ChatConfigurator.dataSource.getReplyView?.(whiteboardMessage, theme, additionalParams) || null;\n        },\n        options: (\n          loggedInUser: CometChat.User,\n          messageObject: CometChat.BaseMessage,\n          theme: CometChatTheme,\n          group?: CometChat.Group\n        ) =>\n          ChatConfigurator.dataSource.getMessageOptions(\n            loggedInUser,\n            messageObject,\n            theme,\n            group,\n            additionalParams\n          ),\n        BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          return ChatConfigurator.dataSource.getBottomView(message, alignment);\n        },\n      })\n    );\n\n    return templateList;\n  }\n\n  /**\n   * Returns the collaborative bubble component for a whiteboard message.\n   *\n   * @param message - The message object.\n   * @param _alignment - The bubble alignment.\n   * @param theme - The current theme.\n   * @returns A JSX.Element to render.\n   */\n  getCollaborativeBubble(\n    message: CometChat.BaseMessage,\n    _alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    const loggedInUser = CometChatUIKit.loggedInUser;\n    if (message && this.loggedInUser) {\n      const whiteboardData = getExtensionData(message, MetadataConstants.extensions?.whiteboard);\n\n      if (whiteboardData && whiteboardData.board_url && whiteboardData.board_url.trim().length) {\n        const username = this.loggedInUser?.getName()?.replace(\" \", \"_\");\n        const url: string = whiteboardData.board_url + \"&username=\" + username;\n        const _style =\n          message.getSender().getUid() === loggedInUser!.getUid()\n            ? theme.messageListStyles.outgoingMessageBubbleStyles?.collaborativeBubbleStyles\n            : theme.messageListStyles.incomingMessageBubbleStyles?.collaborativeBubbleStyles;\n\n        return (\n          <CometChatCollaborativeBubble\n            title={t(\"COLLABORATIVE_WHITEBOARD\")}\n            titleStyle={_style?.titleStyle}\n            subtitle={t(\"OPEN_WHITEBOARD_TO_DRAW\")}\n            subtitleStyle={_style?.subtitleStyle}\n            buttonText={t(\"OPEN_WHITEBOARD\")}\n            icon={\n              <Icon\n                name='collaborative-whiteboard-fill'\n                height={_style?.iconStyle?.height}\n                width={_style?.iconStyle?.width}\n                color={_style?.iconStyle?.tintColor}\n                icon={_style?.iconCollaborativeWhiteboard}\n                imageStyle={_style?.iconStyle}\n                containerStyle={_style?.iconContainerStyle}\n              />\n            }\n            url={url}\n            image={\n              <Icon\n                icon={_style?.imageCollaborativeWhiteboard ?? DEFAULT_ICONS.COLLAB_WHITEBOARD_3X}\n                height={_style?.imageStyle?.height}\n                width={_style?.imageStyle?.width}\n                color={_style?.imageStyle?.tintColor}\n                imageStyle={_style?.imageStyle}\n                containerStyle={_style?.imageContainerStyle}\n              />\n            }\n            buttonViewStyle={_style?.buttonViewStyle}\n            buttonTextStyle={_style?.buttonTextStyle}\n            dividerStyle={_style?.dividerStyle}\n          />\n        );\n      }\n    }\n    return (\n      <View>\n        <Text>{\"no match\"}</Text>\n      </View>\n    );\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeWhiteboard/index.ts",
    "content": "import { CometChatCollaborativeBubble } from \"../CollaborativeBubble/CometChatCollaborativeBubble\";\nimport { CollaborativeWhiteboardExtension } from \"./CollaborativeWhiteboardExtension\";\nimport { CollaborativeWhiteboardExtensionDecorator } from \"./CollaborativeWhiteboardExtensionDecorator\";\n\nexport {\n  CollaborativeWhiteboardExtension,\n  CollaborativeWhiteboardExtensionDecorator,\n  CometChatCollaborativeBubble as CometChatCollaborativeWhiteBoardBubble,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/CollaborativeWhiteboard/resources/index.ts",
    "content": "import CollaborativeWhiteboard from \"./collaborative_whiteboard.png\";\nimport WhiteBoard from \"./whiteboard.png\";\n\nexport const WHITEBOARDICON = WhiteBoard;\nexport const COLLABORATIVEWHITEBOARDICON = CollaborativeWhiteboard;\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/ExtensionConstants.tsx",
    "content": "const ExtensionConstants = {\n  extensions: \"extensions\",\n  linkPreview: \"link-preview\",\n  smartReply: \"smart-reply\",\n  messageTranslation: \"message-translation\",\n  profanityFilter: \"profanity-filter\",\n  //imageModeration: \"image-moderation\",\n  thumbnailGeneration: \"thumbnail-generation\",\n  sentimentalAnalysis: \"sentiment-analysis\",\n  polls: \"polls\",\n  reactions: \"reactions\",\n  whiteboard: \"whiteboard\",\n  document: \"document\",\n  dataMasking: \"data-masking\",\n  stickers: \"stickers\",\n  xssFilter: \"xss-filter\",\n  saveMessage: \"save-message\",\n  pinMessage: \"pin-message\",\n  voiceTranscription: \"voice-transcription\",\n  richMedia: \"rich-media\",\n  malwareScanner: \"virus-malware-scanner\",\n  mentions: \"mentions\",\n  customStickers: \"customStickers\",\n  defaultStickers: \"defaultStickers\",\n  stickerUrl: \"stickerUrl\",\n  sanitizedText: \"sanitized_text\",\n  hasXSS: \"hasXSS\",\n  yes: \"yes\", //not sure\n  post: \"POST\",\n  data: \"data\",\n  sensitiveData: \"sensitive_data\",\n  messageMasked: \"message_masked\",\n  profanity: \"profanity\",\n  messageClean: \"message_clean\",\n};\n\nconst ExtensionURLs = {\n  reaction: \"v1/react\",\n  stickers: \"v1/fetch\",\n  document: \"v1/create\",\n  whiteboard: \"v1/create\",\n  translate: \"v2/translate\",\n  poll: \"v2/create\",\n};\n\nconst ExtensionTypeConstants = {\n  extensionPoll: \"extension_poll\",\n  sticker: \"extension_sticker\",\n  document: \"extension_document\",\n  whiteboard: \"extension_whiteboard\",\n};\n\nexport { ExtensionConstants, ExtensionTypeConstants, ExtensionURLs };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/ExtensionModerator.tsx",
    "content": "export const CheckPropertyExists = (obj: any, propkey: PropertyKey) => {\n  return Object.prototype.hasOwnProperty.call(obj, propkey);\n}\n\n/**\n* Returns extention data object or null if extention data not found.\n* @param {object} message - message Object from SDK\n* @param {string} extentionKey extention tobe searched\n* @returns object or null.\n*/\nexport const getExtensionData = (message: any, extentionKey: string) => {\n  if (message?.metadata) {\n      var injectedObject = message.metadata[\"@injected\"];\n      if (injectedObject != null && injectedObject.hasOwnProperty(\"extensions\")) {\n          var extensionsObject = injectedObject[\"extensions\"];\n          if (\n              extensionsObject != null &&\n              extensionsObject.hasOwnProperty(extentionKey)\n          ) {\n              return extensionsObject[extentionKey];\n          }\n      }\n  }\n  return null;\n}\n\n\n/**\n* Returns extention data object or null if extention data not found.\n* @param {object} message - message Object from SDK\n* @param {string} extentionKey extention tobe searched\n* @returns object or null.\n*/\nexport const getExtensionDataByMetaData = (metadata: any, extentionKey: string | number) => {\n  \n  if (metadata) {\n      var injectedObject = metadata[\"@injected\"];\n      if (injectedObject != null && injectedObject.hasOwnProperty(\"extensions\")) {\n          var extensionsObject = injectedObject[\"extensions\"];\n          if (\n              extensionsObject != null &&\n              extensionsObject.hasOwnProperty(extentionKey)\n          ) {\n              return extensionsObject[extentionKey];\n          }\n      }\n  }\n  return null;\n}\n\nexport const getMetadataByKey = (message: any, metadataKey: string | number) => {\n  if (message.hasOwnProperty(\"metadata\")) {\n      const metadata = message[\"metadata\"];\n      if (metadata.hasOwnProperty(metadataKey)) {\n          return metadata[metadataKey];\n      }\n  }\n\n  return null;\n};"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/LinkPreview/LinkPreviewBubble.tsx",
    "content": "import React, { JSX, useCallback, useRef, useState } from \"react\";\nimport {\n  Alert,\n  DimensionValue,\n  Image,\n  LayoutChangeEvent,\n  Linking,\n  Platform,\n  Text,\n  View,\n} from \"react-native\";\nimport { DefaultLinkPreview } from \"./resources\";\nimport { useTheme } from \"../../theme\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { useCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew\";\n\n/**\n * Props for the LinkPreviewBubble component.\n */\nexport interface LinkPreviewBubbleInterface {\n  /** Custom child view to render below the preview */\n  ChildView?: () => JSX.Element;\n  /** Description text to display */\n  description?: string;\n  /** Custom style object for the bubble */\n  style?: {\n    /** Custom body style */\n    bodyStyle?: (CometChatTheme[\"linkPreviewBubbleStyles\"] & {})[\"bodyStyle\"];\n    /** Custom header image style */\n    headerImageStyle?: (CometChatTheme[\"linkPreviewBubbleStyles\"] & {})[\"headerImageStyle\"];\n    /** Custom header image container style */\n    headerImageContainerStyle?: (CometChatTheme[\"linkPreviewBubbleStyles\"] & {})[\"headerImageContainerStyle\"];\n  };\n  /** URL of the link to preview */\n  link: string;\n  /** Callback when the preview is pressed */\n  onPress?: () => void;\n  /** Title text to display */\n  title: string;\n  /** Image URL for the preview header */\n  image: string;\n  /** Favicon URL to display */\n  favicon: string;\n}\n\n/**\n * LinkPreviewBubble component displays a preview of a link with image, title, description, and favicon.\n *\n * @param props - LinkPreviewBubbleInterface properties.\n * @returns A JSX.Element rendering the link preview bubble.\n */\nexport const LinkPreviewBubble = (props: LinkPreviewBubbleInterface) => {\n  const { style, link, title, ChildView, image, onPress, description, favicon } = props;\n  const theme = useTheme();\n  const {t}=useCometChatTranslation()\n\n  const _style = style;\n\n  const [imageSource, setImageSource] = useState({\n    uri: image.startsWith(\"https:\") ? image : `https:${image.split(\"http:\")[1]}`,\n  });\n\n  const [imageHeight, setImageHeight] = useState<DimensionValue>();\n  const [imageWidth, setImageWidth] = useState<DimensionValue>();\n\n  const [faviconSource, setFaviconSource] = useState({\n    uri: favicon.startsWith(\"https:\") ? favicon : `https:${favicon.split(\"http:\")[1]}`,\n  });\n\n  const [faviconError, setFaviconError] = useState(false);\n\n  const pressTime = useRef<number | null>(0);\n\n  /**\n   * Records the time when the touch starts.\n   */\n  const handleTouchStart = () => {\n    pressTime.current = Date.now();\n  };\n\n  /**\n   * Handles the touch end event. If the press duration is less than 500ms, it triggers the onPress callback or opens the link.\n   */\n  const handleTouchEnd = async () => {\n    const endTime = Date.now();\n    const pressDuration = endTime - (pressTime.current ?? 0);\n    if (pressDuration < 500) {\n      if (onPress) {\n        onPress();\n      } else {\n        const opened = await Linking.openURL(link);\n        if (!opened) {\n          Alert.alert(t(\"SOMETHING_WRONG\"));\n        }\n      }\n    }\n  };\n\n  /**\n   * Handles the touch move event. For iOS, it cancels the press action.\n   */\n  const onTouchMove = () => {\n    if (Platform.OS === \"ios\") {\n      pressTime.current = null;\n    }\n  };\n\n  /**\n   * Callback for layout changes of the container.\n   * Sets the image width and height based on the container size.\n   *\n   * @param event - The layout change event.\n   */\n  const onLayoutHandler = useCallback(\n    (event: LayoutChangeEvent) => {\n      if (imageHeight && imageWidth) {\n        return;\n      }\n      const containerWidth = event.nativeEvent.layout.width;\n      Image.getSize(\n        image,\n        (width: number, height: number) => {\n          setImageHeight(height);\n          if (typeof style?.headerImageContainerStyle?.maxHeight === \"number\") {\n            setImageHeight(\n              height < style?.headerImageContainerStyle?.maxHeight\n                ? height\n                : style?.headerImageContainerStyle.maxHeight\n            );\n          }\n          setImageWidth(width < containerWidth ? width : containerWidth);\n        },\n        (error: any) => {\n          console.log(error);\n        }\n      );\n    },\n    [image, imageHeight, imageWidth, style]\n  );\n\n  return (\n    <View \n      onLayout={onLayoutHandler}\n      onTouchStart={handleTouchStart}\n      onTouchEnd={handleTouchEnd}\n      onTouchMove={onTouchMove}\n    >\n      <View\n        style={style?.headerImageContainerStyle}\n        onTouchStart={handleTouchStart}\n        onTouchEnd={handleTouchEnd}\n        onTouchMove={onTouchMove}\n      >\n        <Image\n          source={imageSource}\n          style={[{ height: imageHeight, width: imageWidth }, style?.headerImageStyle]}\n          onError={(err) => {\n            setImageSource(DefaultLinkPreview);\n          }}\n        />\n      </View>\n\n      <View style={style?.bodyStyle?.containerStyle}>\n        <View style={{ flexDirection: \"row\" }}>\n          <View style={style?.bodyStyle?.titleContainerStyle}>\n            <Text style={style?.bodyStyle?.titleStyle} ellipsizeMode='tail'>\n              {title}\n            </Text>\n          </View>\n\n          {!faviconError && (\n            <View style={style?.bodyStyle?.faviconContainerStyle}>\n              <Image\n                source={faviconSource}\n                style={style?.bodyStyle?.faviconStyle}\n                onError={(err) => {\n                  setFaviconError(true);\n                }}\n              />\n            </View>\n          )}\n        </View>\n        <View style={style?.bodyStyle?.subtitleContainerStyle}>\n          <Text style={style?.bodyStyle?.subtitleTitle} numberOfLines={2} ellipsizeMode='tail'>\n            {description}\n          </Text>\n          <Text style={style?.bodyStyle?.subtitleTitle} numberOfLines={2} ellipsizeMode='tail'>\n            {link}\n          </Text>\n        </View>\n      </View>\n      <View\n        style={{\n          paddingVertical: theme.spacing.padding.p3,\n          paddingHorizontal: style?.bodyStyle?.containerStyle.padding,\n          flexShrink: 0,\n        }}\n      >\n        {ChildView && <ChildView />}\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/LinkPreview/LinkPreviewExtension.tsx",
    "content": "import { ChatConfigurator, DataSource, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { LinkPreviewExtensionDecorator } from \"./LinkPreviewExtensionDecorator\";\n\nexport class LinkPreviewExtension extends ExtensionsDataSource {\n\n  constructor() {\n    super();\n  }\n\n  //override addExtension method from ExtensionsDataSource interface\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource: DataSource) => {\n      return new LinkPreviewExtensionDecorator(dataSource);\n    });\n  }\n\n  //override getExtensionId method from ExtensionsDataSource interface\n  override getExtensionId(): string {\n    return ExtensionConstants.linkPreview;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/LinkPreview/LinkPreviewExtensionDecorator.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { AdditionalParams, MessageBubbleAlignmentType } from \"../../shared/base/Types\";\nimport { DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { BubbleStyles, CometChatTheme } from \"../../theme/type\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { getExtensionData } from \"../ExtensionModerator\";\nimport { LinkPreviewBubble } from \"./LinkPreviewBubble\";\nimport { CometChatUIKit } from \"../../shared\";\nimport { DeepPartial } from \"../../shared/helper/types\";\n\n/**\n * Extension decorator for rendering link preview messages.\n */\nexport class LinkPreviewExtensionDecorator extends DataSourceDecorator {\n  /**\n   * Creates an instance of LinkPreviewExtensionDecorator.\n   *\n   * @param dataSource - The data source instance to decorate.\n   */\n  constructor(dataSource: DataSource) {\n    super(dataSource);\n  }\n\n  /**\n   * Checks if the given message has been deleted.\n   *\n   * @param message - The message to check.\n   * @returns True if the message is deleted, otherwise false.\n   */\n  isDeletedMessage(message: CometChat.BaseMessage): boolean {\n    return message.getDeletedBy() != null;\n  }\n\n  /**\n   * Returns the unique ID for this extension.\n   *\n   * @returns The extension ID as a string.\n   */\n  getId(): string {\n    return \"LinkPreviewExtension\";\n  }\n\n  /**\n   * Returns the content view for text messages.\n   * If the message contains link preview data, it returns a LinkPreviewBubble,\n   * otherwise it falls back to the super implementation.\n   *\n   * @param message - The text message.\n   * @param alignment - The alignment for the message bubble.\n   * @param theme - The current theme.\n   * @param additionalParams - (Optional) Additional parameters.\n   * @returns A JSX.Element representing the message content view.\n   */\n  getTextMessageContentView(\n    message: CometChat.TextMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element {\n    const loggedInUser = CometChatUIKit.loggedInUser;\n    const linkData = getExtensionData(message, ExtensionConstants.linkPreview);\n\n    if (!linkData || linkData.links.length === 0) {\n      return super.getTextMessageContentView(message, alignment, theme, additionalParams);\n    } else {\n      const _style: DeepPartial<BubbleStyles[\"linkPreviewBubbleStyles\"]> =\n        message.getSender().getUid() === loggedInUser!.getUid()\n          ? theme.messageListStyles?.outgoingMessageBubbleStyles?.linkPreviewBubbleStyles\n          : theme.messageListStyles?.incomingMessageBubbleStyles?.linkPreviewBubbleStyles;\n      const { image, favicon, title, url, description } = linkData.links[0];\n      const img = image.length === 0 ? favicon : image;\n      return (\n        <LinkPreviewBubble\n          link={url}\n          description={description}\n          image={img}\n          favicon={favicon}\n          ChildView={() =>\n            super.getTextMessageBubble(\n              (message as CometChat.TextMessage).getText(),\n              message as CometChat.TextMessage,\n              alignment,\n              theme,\n              additionalParams\n            )\n          }\n          title={title}\n          style={_style as CometChatTheme[\"linkPreviewBubbleStyles\"]}\n        />\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/LinkPreview/index.ts",
    "content": "import { LinkPreviewBubble, LinkPreviewBubbleInterface } from \"./LinkPreviewBubble\";\nimport { LinkPreviewExtension } from \"./LinkPreviewExtension\";\nimport { LinkPreviewExtensionDecorator } from \"./LinkPreviewExtensionDecorator\";\n\nexport {\n  LinkPreviewBubble,\n  LinkPreviewExtension,\n  LinkPreviewExtensionDecorator,\n};\n\nexport type {\n  LinkPreviewBubbleInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/LinkPreview/resources/index.ts",
    "content": "import DefaultLinkPreview from \"./defaultImage.png\";\n\nexport { DefaultLinkPreview };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/LinkPreview/style.ts",
    "content": "import { CometChatTheme } from \"../../theme/type\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\n\nconst getLinkPreviewBubbleStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"linkPreviewBubbleStyles\"] => {\n  return {\n    containerStyle: {\n      paddingHorizontal: spacing.padding.p1,\n      paddingTop: spacing.padding.p1,\n      flex: 1,\n    },\n    bodyStyle: {\n      containerStyle: {\n        //borderRadius: spacing.radius.r2,\n        borderBottomLeftRadius: spacing.radius.r2,\n        borderBottomRightRadius: spacing.radius.r2,\n        padding: spacing.padding.p2,\n        backgroundColor: color.extendedPrimary900,\n        flex: 1,\n      },\n      titleStyle: {\n        ...typography.body.bold,\n        color: color.sendBubbleTimestamp,\n      },\n      titleContainerStyle: { flex: 1 },\n      subtitleTitle: {\n        ...typography.caption1.regular,\n        color: color.sendBubbleTimestamp,\n      },\n      subtitleContainerStyle: {\n        flex: 1,\n        gap: spacing.spacing.s0_5,\n      },\n      faviconStyle: { height: 32.7, width: 32.7 },\n      faviconContainerStyle: {},\n    },\n    headerImageStyle: {\n      alignSelf: \"center\",\n      borderTopLeftRadius: spacing.radius.r2,\n      borderTopRightRadius: spacing.radius.r2,\n    },\n    headerImageContainerStyle: {\n      flex: 1,\n      maxHeight: 250,\n      flexDirection: 'row',\n      justifyContent: \"center\",\n      backgroundColor: color.extendedPrimary900,\n      borderTopLeftRadius: spacing.radius.r2,\n      borderTopRightRadius: spacing.radius.r2,\n    },\n  };\n};\n\nexport const getLinkPreviewBubbleStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"linkPreviewBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"linkPreviewBubbleStyles\"];\n} => {\n  return {\n    incomingBubbleStyle: deepMerge(getLinkPreviewBubbleStyle(color, spacing, typography)!, {\n      bodyStyle: {\n        containerStyle: {\n          backgroundColor: color.neutral400,\n        },\n        titleStyle: {\n          color: color.receiveBubbleText,\n        },\n        subtitleTitle: {\n          color: color.receiveBubbleText,\n        },\n      },\n      headerImageContainerStyle: {\n        backgroundColor: color.neutral400,\n      },\n    }) as CometChatTheme['linkPreviewBubbleStyles'],\n    outgoingBubbleStyle: getLinkPreviewBubbleStyle(color, spacing, typography),\n  };\n};\n\nexport const getLinkPreviewBubbleStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"linkPreviewBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"linkPreviewBubbleStyles\"];\n} => {\n  return {\n    incomingBubbleStyle: getLinkPreviewBubbleStyleLight(color, spacing, typography)\n      .incomingBubbleStyle,\n    outgoingBubbleStyle: getLinkPreviewBubbleStyleLight(color, spacing, typography)\n      .outgoingBubbleStyle,\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/MessageTranslation/MessageTranslationBubble.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { Text, TextStyle, View, ViewStyle } from \"react-native\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUrlsFormatter,\n} from \"../../shared\";\nimport { CometChatTextBubble } from \"../../shared/views\";\nimport { useTheme } from \"../../theme\";\nimport { useCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew\";\n\n/**\n * Props for MessageTranslationBubble component.\n */\nexport interface MessageTranslationBubbleProps {\n  /** The translated version of the original text. */\n  translatedText?: string;\n  /** The original text message. */\n  text?: string;\n  /** Styling for the original text. */\n  textStyle?: TextStyle;\n  /** Container style for the original text. */\n  textContainerStyle?: ViewStyle;\n  /** Styling for the translated text. */\n  translatedTextStyle?: TextStyle;\n  /** Container style for the translated text. */\n  translatedTextContainerStyle?: ViewStyle;\n  /** Style for the divider between original and translated text. */\n  translatedTextDividerStyle?: ViewStyle;\n  /** Alignment of the text bubble. */\n  alignment?: string;\n  /** An array of text formatters to process the text. */\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n}\n\n/**\n * MessageTranslationBubble component renders a text bubble that displays both\n * the original text and its translated version.\n *\n * If a translated text is provided, it displays:\n * - The formatted original text.\n * - A divider.\n * - The formatted translated text.\n * - A label indicating the text has been translated.\n *\n * If no translated text is provided, it falls back to rendering the standard\n * CometChatTextBubble.\n *\n * @param {MessageTranslationBubbleProps} props - The props for configuring the text bubble.\n *\n * @returns {JSX.Element} A React element representing the message translation bubble.\n */\nexport const MessageTranslationBubble: React.FC<MessageTranslationBubbleProps> = (props) => {\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n\n  // Destructure the props.\n  const {\n    translatedText,\n    text,\n    textStyle,\n    textContainerStyle,\n    translatedTextStyle,\n    translatedTextContainerStyle,\n    translatedTextDividerStyle,\n    alignment,\n    textFormatters,\n  } = props;\n\n  // State to hold the formatted original text.\n  const [formattedText, setFormattedText] = useState<string>(\"\");\n  // State to hold the formatted translated text.\n  const [formattedTranslatedText, setFormattedTranslatedText] = useState<string>(\"\");\n\n  // Log the divider style for debugging purposes.\n  console.log(\"translatedTextDividerStyle: \", translatedTextDividerStyle);\n\n  /**\n   * Effect hook to apply text formatters to both the original and translated texts.\n   *\n   * It iterates over all provided text formatters and updates the state with the formatted texts.\n   */\n  useEffect(() => {\n    let _formattedText: string = text || \"\";\n    let _formattedTranslatedText: string = translatedText || \"\";\n\n    // Apply formatters to the original text if any are provided.\n    if (textFormatters && textFormatters.length) {\n      for (let i = 0; i < textFormatters.length; i++) {\n        _formattedText = textFormatters[i].getFormattedText(_formattedText);\n      }\n    }\n\n    // Apply formatters to the translated text if any are provided.\n    if (textFormatters && textFormatters.length) {\n      for (let i = 0; i < textFormatters.length; i++) {\n        _formattedTranslatedText = textFormatters[i].getFormattedText(_formattedTranslatedText);\n      }\n    }\n\n    // Update state with the formatted texts.\n    setFormattedText(_formattedText);\n    setFormattedTranslatedText(_formattedTranslatedText);\n  }, [text, translatedText, textFormatters]);\n\n  // Render the bubble with both texts and a translation label if translatedText exists.\n  if (translatedText && translatedText !== \"\") {\n    return (\n      <View>\n        {/* Render the original text */}\n        <Text style={textStyle}>{formattedText}</Text>\n\n        {/* Render the divider */}\n        <View style={translatedTextDividerStyle} />\n\n        {/* Container for the translated text */}\n        <View style={translatedTextContainerStyle}>\n          <Text style={translatedTextStyle}>{formattedTranslatedText}</Text>\n        </View>\n\n        {/* Label indicating the text has been translated */}\n        <Text\n          style={[\n            {\n              ...theme.typography.caption2.regular,\n              color: translatedTextStyle?.color ?? theme.color.iconSecondary,\n            },\n          ]}\n        >\n          {t(\"TEXT_TRANSLATED\")}\n        </Text>\n      </View>\n    );\n  }\n\n  // If no translated text is provided, render the default text bubble.\n  return (\n    <CometChatTextBubble\n      text={text!}\n      textContainerStyle={textContainerStyle}\n      textStyle={textStyle}\n      textFormatters={textFormatters}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/MessageTranslation/MessageTranslationDecorator.tsx",
    "content": "import { ChatConfigurator, DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatMessageOption } from \"../../shared/modals\";\nimport { ExtensionConstants, ExtensionURLs } from \"../ExtensionConstants\";\nimport React, { JSX } from \"react\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUIKit,\n  CometChatUrlsFormatter,\n  getCurrentLanguage,\n  MentionTextStyle,\n} from \"../../shared\";\nimport { AdditionalParams, MessageBubbleAlignmentType } from \"../../shared/base/Types\";\nimport {\n  MentionsTargetElement,\n  MessageOptionConstants,\n} from \"../../shared/constants/UIKitConstants\";\nimport { CometChatUIEvents, MessageEvents } from \"../../shared/events\";\nimport { CometChatUIEventHandler } from \"../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { messageStatus } from \"../../shared/utils/CometChatMessageHelper\";\nimport { CommonUtils } from \"../../shared/utils/CommonUtils\";\nimport { MessageTranslationBubble } from \"./MessageTranslationBubble\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { MentionContext } from \"../../shared/framework/MessageDataSource\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { getCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew/LocalizationManager\"\n\nconst t = getCometChatTranslation();\n\n/**\n * Decorator class for handling message translation in the chat application.\n */\nexport class MessageTranslationExtensionDecorator extends DataSourceDecorator {\n  translatedMessage: any = {};\n\n  /**\n   * Creates an instance of MessageTranslationExtensionDecorator.\n   *\n   * @param {DataSource} dataSource - The data source to be decorated.\n   */\n  constructor(dataSource: DataSource) {\n    super(dataSource);\n  }\n\n  /**\n   * Returns the unique ID for this extension.\n   *\n   * @returns {string} The ID string \"MessageTranslation\".\n   */\n  getId(): string {\n    return \"MessageTranslation\";\n  }\n\n  /**\n   * Gets the text message options for a given message.\n   *\n   * @param {CometChat.User} loggedInUser - The currently logged in user.\n   * @param {CometChat.BaseMessage} messageObject - The message object.\n   * @param {CometChatTheme} theme - The current theme settings.\n   * @param {CometChat.Group} group - The group to which the message belongs.\n   *\n   * @returns {CometChatMessageOption[]} An array of message options including the translate option.\n   */\n  getTextMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    // Retrieve default text message options from the decorated data source.\n    let optionsList: CometChatMessageOption[] = super.getTextMessageOptions(\n      loggedInUser,\n      messageObject,\n      theme,\n      group,\n      additionalParams\n    );\n    // Add the translate option to the options list.\n    !additionalParams?.hideTranslateMessageOption &&\n      optionsList.push(this.getTranslateOption(messageObject, theme));\n    return optionsList;\n  }\n\n  /**\n   * Constructs the translation option for a message.\n   *\n   * @param {CometChat.BaseMessage} messageObject - The message object to be translated.\n   * @param {CometChatTheme} theme - The current theme settings.\n   *\n   * @returns {CometChatMessageOption} A message option object for translation.\n   */\n  getTranslateOption(\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme\n  ): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.translateMessage,\n      title: t(\"TRANSLATE\"),\n      icon: (\n        <Icon\n          name='translate'\n          color={\n            theme.messageListStyles?.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={\n            theme.messageListStyles?.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height\n          }\n          width={theme.messageListStyles?.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles?.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n      onPress: () => {\n        // Invoke translation when the option is pressed.\n        this.translateMessage(messageObject as CometChat.TextMessage);\n      },\n    };\n  }\n\n  /**\n   * Updates the metadata of a message with translation information.\n   *\n   * @param {CometChat.TextMessage} messageObj - The text message object.\n   * @param {any} messageTranslation - The translated text for the message.\n   *\n   * @returns {object} An object containing the updated message and translation metadata.\n   */\n  getSetMetaData = (messageObj: CometChat.TextMessage, messageTranslation: any) => {\n    let metaData: any = messageObj.getMetadata();\n    if (!metaData) {\n      metaData = {};\n    }\n    // Inject the translate extension if not already present.\n    if (metaData && !metaData[\"@injected\"]) {\n      metaData = {\n        ...metaData,\n        \"@injected\": { extensions: { translate: {} } },\n      };\n    }\n    if (metaData && metaData[\"@injected\"] && metaData[\"@injected\"][\"extensions\"]) {\n      let tempData = metaData[\"@injected\"][\"extensions\"];\n      tempData = {\n        ...metaData,\n        \"@injected\": {\n          ...metaData[\"@injected\"],\n          extensions: {\n            ...metaData[\"@injected\"][\"extensions\"],\n            translate: { [messageObj.getId()]: messageTranslation },\n          },\n        },\n      };\n      metaData = tempData;\n    }\n\n    if (metaData && metaData[\"@injected\"] && metaData[\"@injected\"][\"extensions\"][\"translate\"]) {\n      let tempMetaData = {};\n      let translateData = metaData[\"@injected\"][\"extensions\"][\"translate\"];\n\n      if (translateData) {\n        translateData = {\n          ...translateData,\n          [messageObj.getId()]: messageTranslation,\n        };\n      } else {\n        translateData[messageObj.getId()] = {\n          [messageObj.getId()]: messageTranslation,\n        };\n      }\n      tempMetaData = {\n        ...metaData[\"@injected\"][\"extensions\"][\"translate\"],\n        ...translateData,\n      };\n\n      metaData[\"@injected\"][\"extensions\"][\"translate\"] = tempMetaData;\n    }\n\n    // Update the message metadata.\n    messageObj.setMetadata(metaData);\n    return {\n      msg: messageObj,\n      metaData: metaData[\"@injected\"][\"extensions\"][\"translate\"],\n    };\n  };\n\n  /**\n   * Translates a given text message by calling the translation extension.\n   *\n   * @param {CometChat.TextMessage} message - The text message to be translated.\n   */\n  translateMessage = (message: CometChat.TextMessage) => {\n    const language = getCurrentLanguage()\n    const messageId = message.getId();\n    const messageText = message.getText();\n    // Get the target language for translation.\n    let translateToLanguage = language\n\n    // Hide the bottom sheet (if visible).\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.ccToggleBottomSheet, {\n      isBottomSheetVisible: false,\n    });\n\n    // Modify the text to avoid translating specific tags.\n    let tempText = messageText.replace(\n      /<@uid:[a-zA-Z0-9]+>/g,\n      (match) => `<span translate='no'>${match}</span>`\n    );\n\n    // Call the translation extension endpoint.\n    CometChat.callExtension(\n      ExtensionConstants.messageTranslation,\n      \"POST\",\n      ExtensionURLs.translate,\n      {\n        msgId: messageId,\n        text: tempText,\n        languages: [translateToLanguage],\n      }\n    )\n      .then((result: any) => {\n        if (result?.hasOwnProperty(\"translations\") && result[\"translations\"][\"length\"]) {\n          let messageTranslation = result[\"translations\"][0];\n          // Replace span tags to restore original formatting.\n          messageTranslation[\"message_translated\"] = messageTranslation[\n            \"message_translated\"\n          ].replace(\n            /<span translate='no'>(<@uid:[a-zA-Z0-9]+>)<\\/span>/g,\n            (_: any, match: any) => match\n          );\n          // Update message metadata with translation.\n          const translatedMsg = this.getSetMetaData(\n            message,\n            messageTranslation[\"message_translated\"]\n          );\n          if (translatedMsg) {\n            if (translatedMsg.metaData?.translate)\n              this.translatedMessage = translatedMsg?.metaData?.translate;\n          } else {\n            this.translatedMessage = {\n              [message.getId()]: `${messageTranslation[\"message_translated\"]}`,\n            };\n          }\n          // Emit an event indicating the message has been edited.\n          CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n            message: translatedMsg.msg,\n            status: messageStatus.success,\n          });\n        }\n      })\n      .catch((error) => {\n        console.log(error);\n      });\n  };\n\n  /**\n   * Returns the text message bubble component.\n   *\n   * This method renders a MessageTranslationBubble if the message has been translated;\n   * otherwise, it falls back to the default text message bubble.\n   *\n   * @param {string} messageText - The original text of the message.\n   * @param {CometChat.TextMessage} message - The message object.\n   * @param {MessageBubbleAlignmentType} alignment - The alignment of the message bubble.\n   * @param {CometChatTheme} theme - The current theme settings.\n   * @param {AdditionalParams} [additionalParams] - Additional parameters for rendering.\n   *\n   * @returns {JSX.Element} The text message bubble component.\n   */\n  getTextMessageBubble(\n    messageText: string,\n    message: CometChat.TextMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element {\n    let tempTranslatedMsg: any = {};\n    let translatedMetaData: any = message.getMetadata();\n\n    // Check if translation metadata exists in the message.\n    if (\n      translatedMetaData &&\n      translatedMetaData[\"@injected\"] &&\n      translatedMetaData[\"@injected\"][\"extensions\"] &&\n      translatedMetaData[\"@injected\"][\"extensions\"][\"translate\"]\n    ) {\n      tempTranslatedMsg = translatedMetaData[\"@injected\"][\"extensions\"][\"translate\"];\n    }\n\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    let mentionedUsers = message.getMentionedUsers();\n    let textFormatters = [...(additionalParams?.textFormatters || [])];\n\n    // Determine if the message was sent by the logged-in user.\n    const isMessageSentByLoggedInUser = message.getSender().getUid() === loggedInUser!.getUid();\n\n    // Select the appropriate style based on the sender.\n    const _style: Partial<CometChatTheme[\"textBubbleStyles\"]> = isMessageSentByLoggedInUser\n      ? (theme.messageListStyles.outgoingMessageBubbleStyles\n          .textBubbleStyles as CometChatTheme[\"textBubbleStyles\"])\n      : (theme.messageListStyles.incomingMessageBubbleStyles\n          .textBubbleStyles as CometChatTheme[\"textBubbleStyles\"]);\n\n    // Create URL formatter and set properties.\n    let linksTextFormatter = ChatConfigurator.getDataSource().getUrlsFormatter(loggedInUser!);\n    let mentionsTextFormatter = ChatConfigurator.getDataSource().getMentionsFormatter(\n      loggedInUser!,\n      theme\n    );\n    linksTextFormatter.setMessage(message);\n    linksTextFormatter.setId(\"ccDefaultUrlsFormatterId\");\n    linksTextFormatter.setStyle({ linkTextColor: theme.color.receiveBubbleLink });\n    if (isMessageSentByLoggedInUser) {\n      linksTextFormatter.setStyle({ linkTextColor: theme.color.sendBubbleText });\n    }\n    mentionsTextFormatter.setContext(isMessageSentByLoggedInUser ? MentionContext.Outgoing : MentionContext.Incoming);\n\n    // Configure mentions formatter if mentioned users are present.\n    if (!additionalParams?.disableMentions && mentionedUsers && mentionedUsers.length) {\n      mentionsTextFormatter.setLoggedInUser(loggedInUser!);\n      mentionsTextFormatter.setMessage(message);\n      mentionsTextFormatter.setId(\"ccDefaultMentionFormatterId\");\n    }\n\n    let finalFormatters: CometChatTextFormatter[] = [];\n    let urlFormatterExists = false;\n    let mentionsFormatterExists = false;\n\n    // Process the provided text formatters.\n    for (const formatter of textFormatters) {\n      if (formatter instanceof CometChatUrlsFormatter) {\n        urlFormatterExists = true;\n      }\n      if (formatter instanceof CometChatMentionsFormatter) {\n        mentionsFormatterExists = true;\n        formatter.setMessage(message);\n        formatter.setTargetElement(MentionsTargetElement.textbubble);\n        formatter.setLoggedInUser(CometChatUIKit.loggedInUser!);\n        formatter.setContext(isMessageSentByLoggedInUser ? \"outgoing\" : \"incoming\");\n      }\n      formatter.setMessage(message);\n      finalFormatters.push(CommonUtils.clone(formatter));\n      if (urlFormatterExists && mentionsFormatterExists) {\n        break;\n      }\n    }\n\n    // Ensure URL formatter is present.\n    if (!urlFormatterExists) {\n      finalFormatters.push(linksTextFormatter);\n    }\n    // Ensure mentions formatter is present.\n    if (!mentionsFormatterExists) {\n      finalFormatters.push(mentionsTextFormatter);\n    }\n\n    // If a translation exists for the message, render the MessageTranslationBubble.\n    if (\n      (tempTranslatedMsg && tempTranslatedMsg[message.getId()]) ||\n      (this.translatedMessage && this.translatedMessage[message.getId()])\n    ) {\n      return (\n        <MessageTranslationBubble\n          translatedText={\n            tempTranslatedMsg\n              ? tempTranslatedMsg[message.getId()]\n              : this.translatedMessage[message.getId()]\n                ? this.translatedMessage[message.getId()]\n                : \"\"\n          }\n          textStyle={_style?.textStyle}\n          textContainerStyle={_style?.textContainerStyle}\n          translatedTextStyle={_style?.translatedTextStyle}\n          translatedTextContainerStyle={_style?.translatedTextContainerStyle}\n          text={messageText}\n          alignment={alignment}\n          textFormatters={finalFormatters}\n          translatedTextDividerStyle={_style?.translatedTextDividerStyle}\n        />\n      );\n    }\n    // Otherwise, return the default text message bubble from the decorated data source.\n    return super.getTextMessageBubble(messageText, message, alignment, theme, additionalParams);\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/MessageTranslation/MessageTranslationExtension.tsx",
    "content": "import { ChatConfigurator, DataSource, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { MessageTranslationBubble } from \"./MessageTranslationBubble\";\nimport { MessageTranslationExtensionDecorator } from \"./MessageTranslationDecorator\";\n\nexport class MessageTranslationExtension extends ExtensionsDataSource {\n  constructor() {\n    super();\n  }\n\n  /**\n   * enable\n   *  @description enables the Text moderation extension which includes Data profanity and data masking\n   */\n\n  //override addExtension method from ExtensionsDataSource interface\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource: DataSource) => {\n      return new MessageTranslationExtensionDecorator(\n        dataSource\n      );\n    });\n  }\n\n  //override getExtensionId method from ExtensionsDataSource interface\n  override getExtensionId(): string {\n    return ExtensionConstants.messageTranslation;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/MessageTranslation/index.ts",
    "content": "import { MessageTranslationBubble } from \"./MessageTranslationBubble\";\nimport { MessageTranslationExtensionDecorator } from \"./MessageTranslationDecorator\";\nimport {\n  MessageTranslationExtension,\n} from \"./MessageTranslationExtension\";\n\nexport {\n  MessageTranslationBubble,\n  MessageTranslationExtension,\n  MessageTranslationExtensionDecorator,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/MessageTranslation/resources/index.ts",
    "content": "import TRANSLATE from \"./translate.png\";\n\nexport const ICONS = {\n  TRANSLATE,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/Header.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { ICONS } from \"./resources\";\n\n/**\n * HeaderInterface defines the props for the Header component.\n */\ninterface HeaderInterface {\n  /** The header title text. */\n  title: string;\n  /** Custom style for the title text. */\n  titleStyle: any;\n  /** Tint color for the close icon. */\n  closeIconTint: string;\n  /** Tint color for the join/create icon. */\n  createIconTint: string;\n  /** Image source for the join icon. */\n  joinIcon: ImageSourcePropType;\n  /** Image source for the close icon. */\n  closeIcon: ImageSourcePropType;\n  /** Callback function executed when the cancel action is triggered. */\n  onCancel: () => void;\n  /** Callback function executed when the submit action is triggered. */\n  onSubmit: () => void;\n}\n\n/**\n * Header component displays a header with a left-aligned close icon,\n * a centered title, and a right-aligned submit icon.\n *\n * @param {HeaderInterface} props - The props for the Header component.\n *\n * @returns {JSX.Element} The rendered Header component.\n */\nconst Header = (props: HeaderInterface) => {\n  const {\n    title,\n    titleStyle,\n    closeIconTint,\n    createIconTint,\n    joinIcon,\n    closeIcon,\n    onCancel,\n    onSubmit,\n  } = props;\n\n  return (\n    <View style={styles.container}>\n      {/* Left section with close icon and title */}\n      <View style={styles.leftContainer}>\n        <TouchableOpacity style={styles.iconContainer} onPress={onCancel}>\n          <Image\n            source={closeIcon ?? ICONS.CLOSE}\n            style={{\n              tintColor: closeIconTint ?? \"rgb(51, 153, 255)\",\n              height: 24,\n              width: 24,\n            }}\n          />\n        </TouchableOpacity>\n        <Text style={[styles.headingText, titleStyle]}>{title}</Text>\n      </View>\n      {/* Right section with join/submit icon */}\n      <TouchableOpacity style={styles.iconContainer} onPress={onSubmit}>\n        <Image\n          source={joinIcon ?? ICONS.TICK}\n          style={{\n            tintColor: createIconTint ?? \"rgb(51, 153, 255)\",\n            height: 24,\n            width: 24,\n          }}\n        />\n      </TouchableOpacity>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    height: 56,\n  },\n  leftContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n  iconContainer: {\n    paddingRight: 15,\n  },\n  headingText: {\n    fontSize: 20,\n    fontWeight: \"600\",\n    color: \"#000\",\n  },\n});\n\nexport default Header;\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/Polls.tsx",
    "content": "import React, { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport {\n  ActivityIndicator,\n  FlatList,\n  InteractionManager,\n  Keyboard,\n  KeyboardAvoidingView,\n  NativeModules,\n  Platform,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  TouchableWithoutFeedback,\n  View,\n} from \"react-native\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { commonVars } from \"../../shared/base/vars\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { useTheme } from \"../../theme\";\nimport { useCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew\";\nimport { SafeAreaView, useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { CometChatMessageEvents } from \"../../shared/events/CometChatMessageEvents\";\nimport { messageStatus } from \"../../shared/utils/CometChatMessageHelper\";\n\nconst { CommonUtil } = NativeModules;\n\n/**\n * Props for the CometChatCreatePoll component.\n */\nexport interface CometChatCreatePollInterface {\n  /**\n   *\n   *\n   * @type {string}\n   * @description Title of the component\n   */\n  title?: string;\n  /**\n   *\n   *\n   * @type {string}\n   * @description Placeholder text for the poll question input\n   */\n  questionPlaceholderText?: string;\n  /**\n   *\n   *\n   * @type {(error: CometChat.CometChatException) => void}\n   * @description Callback invoked when an error occurs\n   */\n  onError?: (error: CometChat.CometChatException) => void;\n  /**\n   *\n   *\n   * @type {CometChat.User}\n   * @description The user for which the poll is being created\n   */\n  user?: CometChat.User;\n  /**\n   *\n   *\n   * @type {CometChat.Group}\n   * @description The group for which the poll is being created\n   */\n  group?: CometChat.Group;\n  /**\n   *\n   *\n   * @type {() => void}\n   * @description Callback triggered when the close icon is pressed\n   */\n  onClose?: () => void;\n  /**\n   *\n   *\n   * @type {string}\n   * @description Placeholder text for the answer inputs\n   */\n  answerPlaceholderText?: string;\n  /**\n   *\n   *\n   * @type {string}\n   * @description Error message when answer fields are empty\n   */\n  answerHelpText?: string;\n  /**\n   *\n   *\n   * @type {string}\n   * @description Text for the \"Add Answers\" button\n   */\n  addAnswerText?: string;\n  /**\n   *\n   *\n   * @type {number}\n   * @description Default number of answer options\n   */\n  defaultAnswers?: number;\n  /**\n   *\n   *\n   * @type {CometChat.BaseMessage}\n   * @description The message to reply to\n   */\n  replyToMessage?: CometChat.BaseMessage;\n  /**\n   *\n   *\n   * @type {() => void}\n   * @description Function to close the reply preview\n   */\n  closeReplyPreview?: () => void;\n}\n\n/**\n * CometChatCreatePoll component allows the user to create a poll with a question and multiple answers.\n *\n * It validates the poll inputs, handles keyboard behavior, and makes an API call to create the poll.\n *\n * @param {CometChatCreatePollInterface} props - The props for the component.\n *\n * @returns {JSX.Element} The rendered poll creation UI.\n */\nexport const CometChatCreatePoll = (props: CometChatCreatePollInterface) => {\n  const {\n    title,\n    questionPlaceholderText = \"Ask question\",\n    onError,\n    user,\n    group,\n    onClose,\n    answerPlaceholderText = \"Answers\",\n    answerHelpText,\n    addAnswerText,\n    defaultAnswers = 2,\n    replyToMessage,\n    closeReplyPreview,\n  } = props;\n\n  const [question, setQuestion] = useState(\"\");\n  const [error, setError] = useState(\"\");\n  const [answers, setAnswers] = useState<string[]>([]);\n  const [kbOffset, setKbOffset] = useState(59);\n  const [loader, setLoader] = useState(false);\n  const loggedInUser = useRef<CometChat.User | null>(null);\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const answerRefs = useRef<(TextInput | null)[]>([]);\n  const [lastRemovedIndex, setLastRemovedIndex] = useState<number | null>(null);\n  const [newlyAddedIndex, setNewlyAddedIndex] = useState<number | null>(null);\n  const flatListRef = useRef<FlatList>(null);\n  const insets = useSafeAreaInsets();\n  const LIMIT_ERROR = t(\"REACHED_MAX_LIMIT\");\n\n  /**\n   * Validates the poll question and answer inputs.\n   *\n   * @returns True if validation passes; otherwise, false.\n   */\n  function validate() {\n    if (!question.trim()) {\n      setError(t(\"INVALID_POLL_QUESTION\"));\n      return false;\n    }\n    const filledAnswers = answers.filter((item) => item.trim() !== \"\");\n    const hasEmptyAnswers = answers.some((item) => item.trim() === \"\");\n    if (filledAnswers.length < 2) {\n      setError(answerHelpText || t(\"INVALID_POLL_OPTION\"));\n      return false;\n    }\n    if (hasEmptyAnswers) {\n      setError(answerHelpText || t(\"INVALID_POLL_OPTION\"));\n      return false;\n    }\n    setError(\"\");\n    return true;\n  }\n\n  /**\n   * Submits the poll by calling the 'polls' extension.\n   */\n  function polls() {\n    if (!validate()) return;\n    setLoader(true);\n\n    const payload: any = {\n      question: question,\n      options: answers.filter((item) => item),\n      receiver: user ? user?.getUid() : group ? group?.getGuid() : \"\",\n      receiverType: user ? \"user\" : group ? \"group\" : \"\",\n    };\n\n    if (replyToMessage) {\n      payload.quotedMessageId = replyToMessage.getId();\n    }\n\n    if (closeReplyPreview) {\n      closeReplyPreview();\n    }\n\n    CometChat.callExtension(\"polls\", \"POST\", \"v2/create\", payload)\n      .then((response) => {\n        console.log(\"poll created\", response);\n        onClose && onClose();\n        setLoader(false);\n        if (replyToMessage) {\n          CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n            message: replyToMessage,\n            status: messageStatus.success,\n          });\n        }\n      })\n      .catch((error) => {\n        console.log(\"poll error\", error);\n        setLoader(false);\n        setError(t(\"SOMETHING_WRONG\"));\n        onError && onError(error);\n      });\n  }\n\n  /**\n   * Renders an error view if any validation error exists.\n   *\n   * @returns The error view or null if no error.\n   */\n  function ErrorView() {\n    if (!error) return null;\n    return (\n      <View\n        style={{\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          borderRadius: 8,\n          backgroundColor: \"rgba(255, 59, 48, 0.1)\",\n          justifyContent: \"center\",\n          marginBottom: 10,\n          paddingVertical: 10,\n          paddingHorizontal: 15,\n        }}\n      >\n        <Icon name='info' color={theme.color.error} size={20} />\n        <Text\n          style={[theme.typography.caption1.regular, { color: theme.color.error, marginLeft: 10 }]}\n        >\n          {error}\n        </Text>\n      </View>\n    );\n  }\n\n  /**\n   * Handles changes to the question input.\n   *\n   * @param {string} text - The updated question text.\n   */\n  function handleQuestionChange(text: string) {\n    setQuestion(text);\n    if (error) {\n      setError(\"\");\n    }\n  }\n\n  /**\n   * Handles changes to an answer input.\n   *\n   * @param {string} text - The updated answer text.\n   * @param {number} index - The index of the answer being updated.\n   */\n  function handleAnswerTextChange(text: string, index: number) {\n    let existingAnswers = [...answers];\n    existingAnswers[index] = text;\n    setAnswers(existingAnswers);\n    if (error && error !== LIMIT_ERROR) {\n      setError(\"\");\n    }\n    if (index >= 2 && text.trim() === \"\") {\n      const previousIndex = index - 1;\n      if (previousIndex >= 0 && answerRefs.current[previousIndex]) {\n        answerRefs.current[previousIndex]?.focus();\n      }\n      const currentIndex = index;\n      setTimeout(() => {\n        setAnswers((prevAnswers) => {\n          const updatedAnswers = [...prevAnswers];\n          if (\n            currentIndex >= 0 &&\n            currentIndex < updatedAnswers.length &&\n            updatedAnswers[currentIndex].trim() === \"\"\n          ) {\n            updatedAnswers.splice(currentIndex, 1);\n          }\n          return updatedAnswers;\n        });\n      }, 100);\n    }\n  }\n\n  /**\n   * Adds a new answer row if the limit is not reached.\n   */\n  function handleAddAnswerRow() {\n    if (answers.length < 12) {\n      let existingAnswers = [...answers];\n      existingAnswers.push(\"\");\n      setAnswers(existingAnswers);\n      setNewlyAddedIndex(existingAnswers.length - 1);\n    } else {\n      setError(LIMIT_ERROR);\n    }\n  }\n\n  /**\n   * Renders each answer row.\n   *\n   * @param {{item: string, index: number}} param0 - The answer text and its index.\n   * @returns {JSX.Element} The rendered answer input.\n   */\n  const renderAnswerItem = ({ item, index }: { item: string; index: number }) => (\n    <View\n      onStartShouldSetResponder={() => true}\n      style={{\n        flexDirection: \"row\",\n        width: \"100%\",\n        alignSelf: \"center\",\n        justifyContent: \"space-between\",\n        alignItems: \"center\",\n        marginTop: 10,\n      }}\n    >\n      <TextInput\n        ref={(el) => {\n          answerRefs.current[index] = el;\n        }}\n        value={item}\n        autoFocus={index === newlyAddedIndex}\n        onChangeText={(text) => handleAnswerTextChange(text, index)}\n        placeholder={answerPlaceholderText}\n        placeholderTextColor={theme.color.textTertiary}\n        style={{\n          flex: 1,\n          padding: Platform.select({ android: 5, ios: 10 }),\n          borderWidth: 1,\n          borderColor: theme.color.borderLight,\n          borderRadius: 8,\n          paddingLeft: 10,\n          color: theme.color.textPrimary,\n        }}\n      />\n    </View>\n  );\n\n  /**\n   * Renders the \"Add Answer\" button.\n   *\n   * @returns The add answer button or null if limit reached.\n   */\n  function AddAnswer() {\n    if (answers.length >= 12) {\n      return <View style={{ height: 10 }} />;\n    }\n    return (\n      <TouchableOpacity\n        onPress={handleAddAnswerRow}\n        style={{\n          height: 56,\n          width: \"100%\",\n          justifyContent: \"center\",\n          alignItems: \"flex-start\",\n          paddingLeft: 10,\n        }}\n      >\n        <Text\n          style={[\n            theme.typography.caption1.medium,\n            { color: theme.color.primary, textAlign: \"center\" },\n          ]}\n        >\n          {\"+ \" + (addAnswerText || t(\"ADD_OPTIONS\"))}\n        </Text>\n      </TouchableOpacity>\n    );\n  }\n\n  useLayoutEffect(() => {\n    if (lastRemovedIndex !== null) {\n      const previousIndex = lastRemovedIndex - 1;\n      if (previousIndex >= 0 && answerRefs.current[previousIndex]) {\n        answerRefs.current[previousIndex]?.focus();\n      }\n      setLastRemovedIndex(null);\n    }\n  }, [lastRemovedIndex]);\n\n  useEffect(() => {\n    if (newlyAddedIndex !== null) {\n      const timer = setTimeout(() => {\n        if (answerRefs.current[newlyAddedIndex]) {\n          answerRefs.current[newlyAddedIndex]?.focus();\n        }\n        setNewlyAddedIndex(null);\n      }, 50);\n\n      return () => clearTimeout(timer);\n    }\n    return;\n  }, [newlyAddedIndex]);\n\n  useEffect(() => {\n    answerRefs.current = answers.map((_, i) => answerRefs.current[i] || null);\n  }, [answers]);\n\n  useEffect(() => {\n    let timer: ReturnType<typeof setTimeout>;\n    if (answers.length > 0 && flatListRef.current) {\n      timer = setTimeout(() => {\n        flatListRef.current?.scrollToEnd({ animated: true });\n      }, 50);\n    }\n    return () => {\n      if (timer) clearTimeout(timer);\n    };\n  }, [answers.length]);\n\n  useEffect(() => {\n    if (answers.length) {\n      InteractionManager.runAfterInteractions(() => {\n        flatListRef.current?.scrollToEnd({ animated: true });\n      });\n    }\n  }, [answers.length]);\n\n  useEffect(() => {\n    if (error === LIMIT_ERROR && answers.length < 12) {\n      setError(\"\");\n    }\n  }, [answers, error]);\n\n  useEffect(() => {\n    const isMaxAndAllFilled = answers.length >= 12 && answers.every((a) => a.trim() !== \"\");\n    if (isMaxAndAllFilled) {\n      setError(LIMIT_ERROR);\n    }\n    // do nothing when condition not met — the other effect you already added clears LIMIT_ERROR when answers < 12\n  }, [answers]);\n\n  useEffect(() => {\n    let answerslist = new Array(defaultAnswers).fill(\"\");\n    setAnswers(answerslist);\n    CometChat.getLoggedinUser()\n      .then((u) => (loggedInUser.current = u))\n      .catch((e) => {});\n    if (Platform.OS === \"ios\") {\n      if (Number.isInteger(commonVars.safeAreaInsets.top)) {\n        setKbOffset(commonVars.safeAreaInsets.top!);\n        return;\n      }\n      CommonUtil.getSafeAreaInsets().then((res: any) => {\n        if (Number.isInteger(res.top)) {\n          commonVars.safeAreaInsets.top = res.top;\n          commonVars.safeAreaInsets.bottom = res.bottom;\n          setKbOffset(res.top);\n        }\n      });\n    }\n  }, []);\n\n  return (\n    <SafeAreaView\n      style={{ flex: 1, backgroundColor: theme.color.background1 }}\n      edges={[\"top\", \"left\", \"right\"]}\n    >\n      <KeyboardAvoidingView\n        style={{ flex: 1 }}\n        enabled={Platform.OS === \"ios\"}\n        behavior={Platform.select({ ios: \"padding\", android: \"height\" })}\n      >\n        <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>\n          <View style={{ flex: 1 }}>\n            {/* Header */}\n            <View\n              style={{\n                flexDirection: \"row\",\n                alignItems: \"center\",\n                paddingTop: Platform.OS === \"ios\" ? insets.top : 0,\n              }}\n            >\n              <TouchableOpacity\n                onPress={onClose}\n                style={{ flexDirection: \"row\", paddingVertical: 20, paddingLeft: 10 }}\n              >\n                <Icon name='arrow-back' />\n              </TouchableOpacity>\n              <Text\n                style={[\n                  theme.typography.heading2.bold,\n                  { color: theme.color.iconPrimary, paddingLeft: 10 },\n                ]}\n              >\n                {title ? title : t(\"CREATE_POLL\")}\n              </Text>\n            </View>\n            {/* Question Input */}\n            <View style={{ borderTopWidth: 1, borderColor: theme.color.borderLight }}>\n              <View style={{ paddingHorizontal: 20 }}>\n                <Text\n                  style={[\n                    theme.typography.heading4.medium,\n                    { marginTop: 15, color: theme.color.textPrimary },\n                  ]}\n                >\n                  {t(\"QUESTION\")}\n                </Text>\n                <TextInput\n                  value={question}\n                  onChangeText={handleQuestionChange}\n                  placeholder={questionPlaceholderText}\n                  placeholderTextColor={theme.color.textTertiary}\n                  style={[\n                    {\n                      padding: Platform.select({ android: 5, ios: 10 }),\n                      borderWidth: 1,\n                      borderColor: theme.color.borderLight,\n                      borderRadius: 8,\n                      marginTop: 10,\n                      paddingLeft: 10,\n                      color: theme.color.textPrimary,\n                    },\n                  ]}\n                />\n                <Text\n                  style={[\n                    theme.typography.heading4.medium,\n                    { marginTop: 25, color: theme.color.textPrimary, marginBottom: 2 },\n                  ]}\n                >\n                  {t(\"OPTIONS\")}\n                </Text>\n              </View>\n            </View>\n            {/* Main Content: Answers list */}\n            <View style={{ flex: 1 }}>\n              <FlatList\n                ref={flatListRef}\n                data={answers}\n                keyExtractor={(item, index) => index.toString()}\n                renderItem={renderAnswerItem}\n                // render a footer only when needed, avoiding a permanently present wrapper that causes extra space\n                ListFooterComponent={() => (\n                  <View style={{ paddingBottom: answers.length >= 12 ? 10 : 20 }}>\n                    <AddAnswer />\n                  </View>\n                )}\n                removeClippedSubviews={false}\n                keyboardShouldPersistTaps='always'\n                keyboardDismissMode='interactive'\n                contentContainerStyle={{\n                  paddingBottom: 20,\n                  paddingTop: 10,\n                  paddingHorizontal: 20,\n                }}\n                automaticallyAdjustContentInsets={false}\n                onScrollToIndexFailed={() => {}}\n                showsVerticalScrollIndicator={false}\n              />\n            </View>\n          </View>\n        </TouchableWithoutFeedback>\n        {/* Loader Overlay */}\n        {loader && (\n          <View\n            style={{\n              position: \"absolute\",\n              height: \"100%\",\n              width: \"100%\",\n              zIndex: 1,\n              justifyContent: \"center\",\n              alignItems: \"center\",\n              backgroundColor: \"rgba(0,0,0,0.3)\",\n            }}\n          >\n            <ActivityIndicator size='large' color={theme.color.primary} />\n          </View>\n        )}\n        {/* Fixed Create Button */}\n        <View\n          style={{\n            paddingHorizontal: 20,\n            backgroundColor: theme.color.background1,\n            paddingBottom: Platform.select({\n              ios: Math.max(insets.bottom, 10),\n              android: 10,\n            }),\n          }}\n        >\n          {error && <ErrorView />}\n          <TouchableOpacity\n            onPress={polls}\n            style={{\n              width: \"100%\",\n              backgroundColor: theme.color.primaryButtonBackground,\n              alignItems: \"center\",\n              borderRadius: 8,\n              paddingVertical: 12,\n              marginBottom: Platform.select({ ios: 0, android: 10 }),\n            }}\n          >\n            <Text\n              style={[theme.typography.button.medium, { color: theme.color.primaryButtonText }]}\n            >\n              {t(\"CREATE\")}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      </KeyboardAvoidingView>\n    </SafeAreaView>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/PollsBubble.tsx",
    "content": "import React, { useCallback, useEffect, useState } from \"react\";\nimport {\n  ActivityIndicator,\n  ImageStyle,\n  StyleSheet,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n  DimensionValue,\n  ColorValue,\n  Pressable,\n} from \"react-native\";\nimport { makeExtentionCall } from \"../../shared/utils/CometChatMessageHelper\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { CometChatAvatar } from \"../../shared/views/CometChatAvatar\";\nimport { useTheme } from \"../../theme\";\nimport { Icon } from \"../../shared/icons/Icon\";\n\n/**\n *\n *\n * @type {string}\n * @description poll question\n */\nexport interface PollsBubbleInterface {\n  /**\n   *\n   *\n   * @type {string}\n   * @description poll question\n   */\n  pollQuestion?: string;\n  /**\n   *\n   *\n   * @type {({ id: string | number; value: string }[])}\n   * @description options\n   */\n  options?: { id: string | number; value: string }[];\n  /**\n   *\n   *\n   * @type {string}\n   * @description poll id\n   */\n  pollId?: string;\n  /**\n   *\n   *\n   * @type {CometChat.User}\n   * @description logged in user\n   */\n  loggedInUser?: CometChat.User;\n  /**\n   *\n   *\n   * @description callback function which returns the id when user votes\n   */\n  choosePoll?: (id: string) => void;\n  /**\n   *\n   *\n   * @type {string}\n   * @description uid of poll creator\n   */\n  senderUid?: string;\n  /**\n   *\n   *\n   * @type {object}\n   * @description metadata attached to the poll message\n   */\n  metadata?: any;\n\n  /***************************/\n  /**\n   *\n   *\n   * @type {TextStyle}\n   * @description Style for the poll question text\n   */\n  titleStyle?: TextStyle;\n  /**\n   *\n   *\n   * @type {TextStyle}\n   * @description Style for the option text\n   */\n  optionTextStyle?: TextStyle;\n  /**\n   *\n   *\n   * @type {TextStyle}\n   * @description Style for the vote count text\n   */\n  voteCountTextStyle?: TextStyle;\n  /**\n   *\n   *\n   * @type {ImageStyle}\n   * @description Style for the selected icon\n   */\n  selectedIconStyle?: ImageStyle;\n  /**\n   *\n   *\n   * @type {ViewStyle}\n   * @description Style for the radio button container\n   */\n  radioButtonStyle?: ViewStyle;\n  /**\n   *\n   *\n   * @type {CometChatTheme[\"avatarStyle\"]}\n   * @description Style for the voter avatar\n   */\n  voteravatarStyle?: CometChatTheme[\"avatarStyle\"];\n  /**\n   *\n   *\n   * @type {ViewStyle}\n   * @description Style for the progress bar container\n   */\n  progressBarStyle?: ViewStyle;\n  /**\n   *\n   *\n   * @type {ColorValue}\n   * @description Active tint color for the progress bar\n   */\n  activeProgressBarTint?: ColorValue;\n}\n\nexport const PollsBubble = (props: PollsBubbleInterface) => {\n  const {\n    pollQuestion,\n    options,\n    pollId,\n    loggedInUser,\n    choosePoll,\n    senderUid,\n    metadata,\n    titleStyle,\n    optionTextStyle,\n    voteCountTextStyle,\n    selectedIconStyle,\n    radioButtonStyle,\n    voteravatarStyle,\n    progressBarStyle,\n    activeProgressBarTint,\n  } = props;\n  const theme = useTheme();\n  const [optionsList, setOptionsList] = useState<Array<{ id: any; value: any }>>([]);\n  const [optionsMetaData, setOptionsMetaData] = useState<any>({});\n  const [isLoading, setIsLoading] = useState(false);\n  // Track which option is being voted on for showing the loader\n  const [loadingOption, setLoadingOption] = useState<string | number | null>(null);\n  // State to track the selected vote so that the tick icon appears\n  const [selectedVote, setSelectedVote] = useState<string | number | null>(null);\n\n  useEffect(() => {\n    let allOptions: Array<{ id: any; value: any }> = [];\n    if (options) {\n      for (const [key, value] of Object.entries(options)) {\n        allOptions.push({ id: key, value });\n      }\n    }\n    setOptionsList(allOptions);\n    if (metadata) {\n      setOptionsMetaData(metadata);\n    }\n    const optionsFromResult = metadata?.results?.options ?? {};\n\n    for (const key in optionsFromResult) {\n      if (\n        optionsFromResult[key] &&\n        optionsFromResult[key].voters &&\n        Object.keys(optionsFromResult[key].voters).length\n      ) {\n        if (loggedInUser && optionsFromResult[key].voters[loggedInUser.getUid()]) {\n          setSelectedVote(key);\n        }\n      }\n    }\n  }, [metadata, options, loggedInUser]);\n\n  /**\n   * Handles the vote action for a poll option.\n   *\n   * @param {string} id - The id of the option being voted for.\n   */\n  const handleResult = (id: string) => {\n    let newOptionsMetaData = { ...optionsMetaData };\n    if (newOptionsMetaData.results?.options[id]?.[\"voters\"]?.[loggedInUser!.getUid()]) return;\n    // Set the loading option id here\n    setLoadingOption(id);\n    setIsLoading(true);\n    choosePoll && choosePoll(id);\n    makeExtentionCall(\"polls\", \"POST\", \"v2/vote\", {\n      vote: id,\n      id: pollId ?? optionsMetaData.id,\n    })\n      .then((s) => {\n        console.log(\"success\", s);\n        // Update the selected vote so that the check icon appears\n        setSelectedVote(id);\n        setIsLoading(false);\n        setLoadingOption(null);\n      })\n      .catch((error) => {\n        console.log(error);\n        setIsLoading(false);\n        setLoadingOption(null);\n      });\n  };\n\n  /**\n   * Renders an option item.\n   *\n   * @param {any} param0 - Object containing id and value of the option.\n   * @returns {JSX.Element} The rendered option item.\n   */\n  const OptionItem = ({ id, value }: any) => {\n    // Adjust avatar styles if needed\n    const secondavatarStyle = voteravatarStyle;\n    const thirdavatarStyle = voteravatarStyle;\n    if (secondavatarStyle && secondavatarStyle.containerStyle) {\n      secondavatarStyle.containerStyle.marginLeft = -10;\n    }\n    if (thirdavatarStyle && thirdavatarStyle.containerStyle) {\n      thirdavatarStyle.containerStyle.marginLeft = -10;\n    }\n    // Ensure value is a string for rendering\n    let optionText = typeof value === 'string' ? value : (value && value.value ? value.value : String(value));\n    return (\n      <Pressable\n        style={{ flexDirection: \"row\", marginBottom: 15, gap: 10 }}\n        onPress={() => handleResult(id)}\n      >\n        {isLoading && loadingOption === id ? (\n          <ActivityIndicator\n            size=\"small\"\n            color={activeProgressBarTint}\n            // Remove circle border/background during loading\n            style={[radioButtonStyle, { borderWidth: 0, backgroundColor: \"transparent\" }]}\n          />\n        ) : selectedVote !== id ? (\n          <View style={radioButtonStyle}></View>\n        ) : (\n          <Icon\n            name=\"check\"\n            color={selectedIconStyle?.tintColor}\n            height={selectedIconStyle?.height}\n            width={selectedIconStyle?.width}\n            containerStyle={{\n              ...radioButtonStyle,\n              borderWidth: 0,\n              backgroundColor: activeProgressBarTint,\n              alignItems: \"center\",\n              justifyContent: \"center\",\n            }}\n          />\n        )}\n\n        <View style={{ gap: 5, width: \"100%\", flex: 1 }}>\n          <View style={{ flex: 1, flexDirection: \"row\", justifyContent: \"space-between\" }}>\n            <Text style={optionTextStyle}>{optionText}</Text>\n            <View style={{ flexDirection: \"row\", gap: theme.spacing.spacing.s1 }}>\n              <View style={{ left: 0, flexDirection: \"row\" }}>\n                {optionsMetaData?.results?.options?.[id]?.voters &&\n                  Object.keys(optionsMetaData.results.options[id].voters)\n                    .slice(0, 3)\n                    .map((key, index) => {\n                      const voter = optionsMetaData.results.options[id].voters[key];\n                      return (\n                        <CometChatAvatar\n                          key={key} // Unique key for each voter\n                          image={{ uri: voter.avatar }}\n                          style={\n                            index === 0\n                              ? secondavatarStyle\n                              : index === 1\n                              ? thirdavatarStyle\n                              : voteravatarStyle\n                          } // Apply different styles based on index\n                          name={voter.name}\n                        />\n                      );\n                    })}\n              </View>\n\n              <Text style={voteCountTextStyle}>\n                {optionsMetaData?.results?.options?.[id]?.count ?? 0}\n              </Text>\n            </View>\n          </View>\n          <View style={progressBarStyle}>\n            <View\n              style={[\n                progressBarStyle,\n                {\n                  backgroundColor: activeProgressBarTint,\n                  width: getSliderWidth(\n                    optionsMetaData?.results?.options?.[id]?.voters\n                      ? Object.keys(optionsMetaData.results.options[id].voters).length\n                      : 0\n                  ),\n                },\n              ]}\n            ></View>\n          </View>\n        </View>\n      </Pressable>\n    );\n  };\n\n  /**\n   * Calculates the width of the progress bar based on the number of voters.\n   *\n   * @param {number} voters - The number of voters for an option.\n   * @returns {DimensionValue} The width percentage for the progress bar.\n   */\n  const getSliderWidth = useCallback(\n    (voters: number) => {\n      const totalVotes = metadata?.results?.total ?? 0;\n      return totalVotes ? (((voters / totalVotes) * 100 + \"%\") as DimensionValue) : \"0%\";\n    },\n    [metadata]\n  );\n\n  return (\n    <View style={[style.container]}>\n      <Text style={titleStyle}>{pollQuestion}</Text>\n      {optionsList.map((item) => (\n        <OptionItem key={item[\"id\"]} {...item} />\n      ))}\n    </View>\n  );\n};\n\nconst style = StyleSheet.create({\n  container: {\n    borderRadius: 10,\n    marginBottom: 20,\n  },\n  questionText: {\n    marginHorizontal: 10,\n    marginVertical: 5,\n  },\n  voteText: {\n    marginHorizontal: 10,\n    marginVertical: 5,\n  },\n  optionItemContainer: {\n    marginHorizontal: 5,\n    marginTop: 5,\n    height: 42,\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    borderRadius: 6,\n    flexDirection: \"row\",\n  },\n  optionsOption: {\n    height: 20,\n    width: 20,\n    borderRadius: 20,\n    marginLeft: 10,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  valueText: {\n    marginHorizontal: 10,\n  },\n  resultMask: {\n    alignItems: \"center\",\n    flexDirection: \"row\",\n    position: \"absolute\",\n    height: \"100%\",\n    borderTopLeftRadius: 6,\n    borderBottomLeftRadius: 6,\n  },\n  centerPosition: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    zIndex: 1,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/PollsConfigurations.ts",
    "content": "export interface PollsConfigurationInterface {\n  title?: string;\n  questionPlaceholderText?: string;\n  answerPlaceholderText?: string;\n  answerHelpText?: string;\n  addAnswerText?: string;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/PollsDecorator.tsx",
    "content": "import { DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { MessageCategoryConstants, MetadataConstants } from \"../../shared/constants/UIKitConstants\";\nimport { ChatConfigurator } from \"../../shared/framework\";\nimport { CometChatMessageComposerAction } from \"../../shared/helper/types\";\nimport { CometChatMessageTemplate } from \"../../shared/modals\";\nimport { ExtensionTypeConstants } from \"../ExtensionConstants\";\nimport { getExtensionData } from \"../ExtensionModerator\";\nimport { PollsConfigurationInterface } from \"./PollsConfigurations\";\nimport React, { JSX } from \"react\";\nimport { View } from \"react-native\";\nimport { CometChatUIKit } from \"../../shared\";\nimport {\n  AdditionalAttachmentOptionsParams,\n  AdditionalParams,\n  MessageBubbleAlignmentType,\n} from \"../../shared/base/Types\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { getMessagePreviewInternal } from \"../../shared/utils/MessageUtils\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { CometChatCreatePoll } from \"./Polls\";\nimport { PollsBubble } from \"./PollsBubble\";\nimport { getCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew/LocalizationManager\";\nimport { CometChatMessageEvents } from \"../../shared/events/CometChatMessageEvents\";\nimport { messageStatus } from \"../../shared/utils/CometChatMessageHelper\";\n\nconst t = getCometChatTranslation();\n\n/**\n *\n *\n * @type {PollsConfigurationInterface}\n * @description Optional configuration for polls.\n */\nexport class PollsExtensionDecorator extends DataSourceDecorator {\n  pollsConfiguration?: PollsConfigurationInterface;\n\n  /**\n   *\n   *\n   * @param {DataSource} dataSource - The data source to decorate.\n   * @param {PollsConfigurationInterface} [pollsConfiguration] - Optional polls configuration.\n   */\n  constructor(dataSource: DataSource, pollsConfiguration?: PollsConfigurationInterface) {\n    super(dataSource);\n    if (pollsConfiguration != undefined) {\n      this.pollsConfiguration = pollsConfiguration;\n    }\n  }\n\n  /**\n   *\n   *\n   * @returns {boolean}\n   * @description Returns true if the message is deleted.\n   */\n  isDeletedMessage(message: CometChat.BaseMessage): boolean {\n    return message.getDeletedBy() != null;\n  }\n\n  /**\n   *\n   *\n   * @returns {string}\n   * @description Returns the unique ID for this extension.\n   */\n  getId(): string {\n    return \"Polls\";\n  }\n\n  /**\n   *\n   *\n   * @param {CometChat.Conversation} conversation - The conversation object.\n   * @param {CometChatTheme} [theme] - The current theme.\n   * @returns {string | JSX.Element}\n   * @description Returns the preview text or element for the last conversation message.\n   */\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element {\n    if (conversation.getLastMessage() == undefined) {\n      return \"\";\n    }\n\n    if (\n      (conversation.getLastMessage() as CometChat.BaseMessage).getType() ==\n        ExtensionTypeConstants.extensionPoll &&\n      (conversation.getLastMessage() as CometChat.BaseMessage).getCategory() ==\n        MessageCategoryConstants.custom &&\n      (conversation.getLastMessage() as CometChat.BaseMessage).getDeletedAt() === undefined\n    ) {\n      return getMessagePreviewInternal(\"bar-chart-fill\", t(\"CUSTOM_MESSAGE_POLL\"), {\n        theme,\n      });\n    } else {\n      return super.getLastConversationMessage(conversation, theme);\n    }\n  }\n\n  /**\n   *\n   *\n   * @returns {string[]}\n   * @description Returns all message categories including custom types.\n   */\n  getAllMessageCategories(): string[] {\n    var categoryList: string[] = super.getAllMessageCategories();\n    if (!categoryList.includes(MessageCategoryConstants.custom)) {\n      categoryList.push(MessageCategoryConstants.custom);\n    }\n    return categoryList;\n  }\n\n  /**\n   *\n   *\n   * @returns {string[]}\n   * @description Returns all message types including the poll extension type.\n   */\n  getAllMessageTypes(): string[] {\n    var messagesTypes: string[] = super.getAllMessageTypes();\n    messagesTypes.push(ExtensionTypeConstants.extensionPoll);\n    return messagesTypes;\n  }\n\n  /**\n   *\n   *\n   * @param {CometChatTheme} theme - The current theme.\n   * @param {any} [user] - The current user.\n   * @param {any} [group] - The current group.\n   * @param {any} [composerId] - The composer identifier.\n   * @returns {CometChatMessageComposerAction[]}\n   * @description Returns the attachment options for the message composer including polls.\n   */\n  getAttachmentOptions(\n    theme: CometChatTheme,\n    user?: any,\n    group?: any,\n    composerId?: any,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ): CometChatMessageComposerAction[] {\n    let attachmentOptions: CometChatMessageComposerAction[] = super.getAttachmentOptions(\n      theme,\n      user,\n      group,\n      composerId,\n      additionalAttachmentOptionsParams\n    );\n    if (additionalAttachmentOptionsParams?.hidePollsAttachmentOption) return attachmentOptions;\n    if (\n      composerId == undefined ||\n      (composerId as Map<any, any>).get(\"parentMessageId\") == undefined\n    )\n      attachmentOptions.push({\n        id: \"polls\",\n        title: t(\"CUSTOM_MESSAGE_POLL\"),\n        icon: <Icon name='poll_icon' color={theme.color.primary} />,\n        CustomView: (user, group, _id, pollsProps) => {\n          return (\n            <CometChatCreatePoll\n              user={user}\n              group={group}\n              {...pollsProps}\n              {...this.pollsConfiguration}\n              replyToMessage={additionalAttachmentOptionsParams?.replyToMessage}\n              closeReplyPreview={additionalAttachmentOptionsParams?.closeReplyPreview}\n            />\n          );\n        },\n      });\n    return attachmentOptions;\n  }\n\n  /**\n   *\n   *\n   * @param {CometChatTheme} theme - The current theme.\n   * @param {AdditionalParams} [additionalParams] - Additional parameters.\n   * @returns {CometChatMessageTemplate[]}\n   * @description Returns all message templates including the poll template.\n   */\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    let templateList: CometChatMessageTemplate[] = super.getAllMessageTemplates(\n      theme,\n      additionalParams\n    );\n\n    templateList.push(\n      new CometChatMessageTemplate({\n        type: ExtensionTypeConstants.extensionPoll,\n        category: MessageCategoryConstants.custom,\n        ContentView: (message: CometChat.BaseMessage, _alignment: MessageBubbleAlignmentType) => {\n          if (this.isDeletedMessage(message)) {\n            return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n          } else {\n            return this.getPollBubble(message, _alignment, theme);\n          }\n        },\n        ReplyView: (\n          message: CometChat.BaseMessage,\n          _alignment?: MessageBubbleAlignmentType,\n          onReplyViewClicked?: (messageToReply: CometChat.BaseMessage) => void\n        ) => {\n          let pollMessage: CometChat.CustomMessage = message as CometChat.CustomMessage;\n          return ChatConfigurator.dataSource.getReplyView?.(pollMessage, theme, additionalParams) || null;\n        },\n        options: (loggedInUser, messageObject, theme, group) =>\n          ChatConfigurator.dataSource.getMessageOptions(\n            loggedInUser,\n            messageObject,\n            theme,\n            group,\n            additionalParams\n          ),\n        BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          return ChatConfigurator.dataSource.getBottomView(message, alignment);\n        },\n      })\n    );\n\n    return templateList;\n  }\n\n  /**\n   *\n   *\n   * @param {CometChat.BaseMessage} message - The poll message.\n   * @param {MessageBubbleAlignmentType} _alignment - The alignment for the message bubble.\n   * @param {CometChatTheme} theme - The current theme.\n   * @returns {JSX.Element}\n   * @description Returns the poll bubble element for a given poll message.\n   */\n  getPollBubble(\n    message: CometChat.BaseMessage,\n    _alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    let _loggedInUser = CometChatUIKit.loggedInUser;\n    if (message && _loggedInUser) {\n      const metaData = getExtensionData(message, MetadataConstants.extensions?.polls);\n      const _style =\n        message.getSender().getUid() === _loggedInUser.getUid()\n          ? theme.messageListStyles.outgoingMessageBubbleStyles?.pollBubbleStyles\n          : theme.messageListStyles.incomingMessageBubbleStyles?.pollBubbleStyles;\n\n      return (\n        <PollsBubble\n          pollQuestion={(message as any)[\"customData\"]?.[\"question\"]}\n          options={(message as any)[\"customData\"]?.[\"options\"]}\n          pollId={(message as any)[\"customData\"]?.[\"id\"]}\n          loggedInUser={_loggedInUser}\n          // choosePoll\n          senderUid={(message as any)[\"sender\"]?.[\"uid\"]}\n          metadata={metaData}\n          titleStyle={_style?.titleStyle}\n          optionTextStyle={_style?.optionTextStyle}\n          voteCountTextStyle={_style?.voteCountTextStyle}\n          selectedIconStyle={_style?.selectedIconStyle}\n          radioButtonStyle={_style?.radioButtonStyle}\n          voteravatarStyle={_style?.voteravatarStyle}\n          progressBarStyle={_style?.progressBarStyle}\n          activeProgressBarTint={_style?.activeProgressBarTint}\n        />\n      );\n    }\n\n    return <View></View>;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/PollsExtension.tsx",
    "content": "import { ChatConfigurator, DataSource, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { PollsConfigurationInterface } from \"./PollsConfigurations\";\nimport { PollsExtensionDecorator } from \"./PollsDecorator\";\n\nexport class PollsExtension extends ExtensionsDataSource {\n  PollsConfigurationInterface?: PollsConfigurationInterface;\n\n  constructor(PollsConfigurationConfiguration?: PollsConfigurationInterface) {\n    super();\n    if (PollsConfigurationConfiguration != null) {\n      this.PollsConfigurationInterface = PollsConfigurationConfiguration;\n    }\n  }\n\n  /**\n   * enable\n   *  @description enables the Text moderation extension which includes Data profanity and data masking\n   */\n\n  //override addExtension method from ExtensionsDataSource interface\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource: DataSource) => {\n      return new PollsExtensionDecorator(dataSource, this.PollsConfigurationInterface);\n    });\n  }\n\n  //override getExtensionId method from ExtensionsDataSource interface\n  override getExtensionId(): string {\n    return ExtensionConstants.polls;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/index.ts",
    "content": "import { CometChatCreatePoll, CometChatCreatePollInterface } from \"./Polls\";\nimport { PollsConfigurationInterface } from \"./PollsConfigurations\";\nimport { PollsExtension } from \"./PollsExtension\";\nexport {\n  CometChatCreatePoll,\n  PollsExtension,\n};\nexport type {\n  CometChatCreatePollInterface,\n  PollsConfigurationInterface,\n};"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/resources/index.ts",
    "content": "import CLOSE from \"./close.png\";\nimport DOCUMENT from \"./document.png\";\nimport KICK from \"./kickIcon.png\";\nimport TICK from \"./rightTick.png\";\nimport WARNING from \"./warning.png\";\n\nexport const ICONS = {\n  CLOSE,\n  TICK,\n  WARNING,\n  KICK,\n  DOCUMENT,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Polls/style.ts",
    "content": "import { CometChatTheme } from \"../../theme/type\";\nimport { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { StyleSheet } from 'react-native';\n\n//remove after create poll\nexport const styles = StyleSheet.create({\n  container: { flex: 1, paddingHorizontal: 15 },\n  textInput: {\n    marginTop: 32,\n    padding: 8,\n    borderBottomWidth: 1,\n  },\n  textInputAnswers: {\n    marginTop: 32,\n    padding: 8,\n    borderBottomWidth: 1,\n  },\n  errorContainer: {\n    flexDirection: 'row',\n    padding: 16,\n    alignItems: 'center',\n    marginTop: 25,\n    borderRadius: 5,\n  },\n  errorImageContainer: {\n    padding: 8,\n    borderRadius: 100,\n  },\n  errorImage: {\n    height: 24,\n    width: 24,\n    resizeMode: 'contain',\n  },\n  errorTextTitle: {\n    marginStart: 8,\n  },\n  errorTextContainer: { flex: 1 },\n  errorText: {\n    marginStart: 8,\n  },\n  addAnswerButtonContainer: { marginTop: 30, marginBottom: 10 },\n});\n\n\nexport const getPollBubbleStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"pollBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"pollBubbleStyles\"];\n} => {\n  return {\n    incomingBubbleStyle: {\n      containerStyle: {\n        width: 240,\n        paddingHorizontal: spacing.padding.p3,\n        paddingTop: spacing.padding.p2,\n      },\n      titleStyle: {\n        ...typography.heading4.bold,\n        color: color.receiveBubbleText,\n        marginBottom: spacing.margin.m4,\n      },\n      optionTextStyle: {\n        ...typography.body.regular,\n        color: color.receiveBubbleText,\n        flex: 1,\n        maxWidth: \"80%\",\n        marginRight: 10,\n        //alignSelf: 'flex-end'\n      },\n      voteCountTextStyle: {\n        ...typography.body.regular,\n        color: color.receiveBubbleText,\n      },\n      selectedIconStyle: {\n        tintColor: color.staticWhite,\n        height: 18,\n        width: 18,\n      },\n      radioButtonStyle: {\n        borderRadius: 10,\n        borderWidth: 1.5,\n        borderColor: color.iconSecondary,\n        height: 20,\n        width: 20,\n        margin: spacing.margin.m0_5,\n        marginBottom: spacing.margin.m0,\n      },\n      progressBarStyle: {\n        height: 8,\n        width: \"100%\",\n        borderRadius: 10,\n        backgroundColor: color.iconTertiary,\n      },\n      activeProgressBarTint: color.iconHighlight,\n      dateReceiptContainerStyle: {\n        paddingRight: spacing.padding.p0,\n      },\n      voteravatarStyle: {\n        containerStyle: {\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          backgroundColor: color.extendedPrimary500,\n          height: spacing.spacing.s5,\n          width: spacing.spacing.s5,\n          borderRadius: spacing.radius.max,\n          borderWidth: 1,\n          borderColor: color.receiveBubbleBackground,\n        },\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n          borderRadius: spacing.radius.max,\n        },\n        textStyle: {\n          textAlign: \"center\",\n          textAlignVertical: \"center\",\n          color: color.primaryButtonIcon,\n          fontFamily: typography.fontFamily,\n          ...typography.caption1.bold,\n        },\n      },\n    },\n    outgoingBubbleStyle: {\n      containerStyle: {\n        width: 240,\n        paddingHorizontal: spacing.padding.p3,\n        paddingTop: spacing.padding.p2,\n      },\n      titleStyle: {\n        ...typography.heading4.bold,\n        color: color.sendBubbleText,\n        marginBottom: spacing.margin.m4,\n      },\n      optionTextStyle: {\n        ...typography.body.regular,\n        color: color.sendBubbleText,\n        flex: 1,\n        maxWidth: \"80%\",\n        marginRight: 10\n      },\n      voteCountTextStyle: {\n        ...typography.body.regular,\n        color: color.sendBubbleText,\n      },\n      selectedIconStyle: {\n        tintColor: color.iconHighlight,\n        height: 18,\n        width: 18,\n      },\n      progressBarStyle: {\n        height: 8,\n        width: \"100%\",\n        borderRadius: 10,\n        backgroundColor: color.extendedPrimary700,\n      },\n      activeProgressBarTint: color.sendBubbleIcon,\n      radioButtonStyle: {\n        borderRadius: 10,\n        borderWidth: 1.5,\n        borderColor: color.sendBubbleIcon,\n        height: 20,\n        width: 20,\n        margin: spacing.margin.m0_5,\n        marginBottom: spacing.margin.m0,\n      },\n      dateReceiptContainerStyle: {\n        paddingRight: spacing.padding.p0,\n      },\n      voteravatarStyle: {\n        containerStyle: {\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          backgroundColor: color.extendedPrimary500,\n          height: spacing.spacing.s5,\n          width: spacing.spacing.s5,\n          borderRadius: spacing.radius.max,\n          borderWidth: 1,\n          borderColor: color.primary,\n        },\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n          borderRadius: spacing.radius.max,\n        },\n        textStyle: {\n          textAlign: \"center\",\n          textAlignVertical: \"center\",\n          color: color.primaryButtonIcon,\n          fontFamily: typography.fontFamily,\n          ...typography.caption1.bold,\n        },\n      },\n    },\n  };\n};\n\nexport const getPollBubbleStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"pollBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"pollBubbleStyles\"];\n} => {\n  return deepMerge(getPollBubbleStyleLight(color, spacing, typography)!, {\n    incomingBubbleStyle: {\n      titleStyle: {\n        color: color.sendBubbleText,\n      },\n      voteCountTextStyle: {\n        color: color.sendBubbleText\n      },\n      radioButtonStyle: {\n        borderColor: color.iconSecondary\n      },\n      selectedIconStyle: {\n        tintColor: color.staticWhite\n      }\n    }\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/CometChatStickerKeyboard/CometChatStickerKeyboard.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useRef } from \"react\";\nimport { ActivityIndicator, FlatList, Image, Text, TouchableOpacity, View } from \"react-native\";\nimport { Icon } from \"../../../shared/icons/Icon\";\nimport { ErrorEmptyView } from \"../../../shared/views/ErrorEmptyView/ErrorEmptyView\";\nimport { useTheme } from \"../../../theme\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { ExtensionConstants } from \"../../ExtensionConstants\";\nimport { Hooks } from \"./hooks\";\nimport { Skeleton } from \"./Skeleton\";\nimport { Styles } from \"./style\";\nimport { useCometChatTranslation } from \"../../../shared/resources/CometChatLocalizeNew\";\n\n/**\n * Interface defining the props for CometChatStickerKeyboard component\n */\nexport interface CometChatStickerKeyboardInterface {\n  loadingText?: string;\n  theme?: CometChatTheme;\n  onPress?: (item: any) => void; // Callback when a sticker is pressed\n  emptyText?: string; // Optional text when no stickers are available\n  errorText?: string; // Optional error text\n}\n\n/**\n * Props for the StickerItem component\n */\ninterface StickerItemProps {\n  stickerItem: any; // Sticker item data or null for placeholders\n  onPress: (item: any) => void; // Callback when the sticker is pressed\n  theme: CometChatTheme; // Theme object\n}\n\n/**\n * StickerItem Component\n * Renders individual sticker items within the sticker keyboard.\n * Handles loading state, error state, and placeholder rendering.\n */\nconst StickerItem = ({ stickerItem, onPress, theme }: StickerItemProps) => {\n  // State to manage loading status of the sticker image\n  const [isLoading, setIsLoading] = React.useState(true);\n  // State to manage error status if the image fails to load\n  const [hasError, setHasError] = React.useState(false);\n\n  // If stickerItem is null, render an empty placeholder to maintain grid structure\n  if (!stickerItem) {\n    return <View style={Styles.stickerItemStyle} />;\n  }\n\n  return (\n    <TouchableOpacity\n      style={Styles.stickerItemStyle}\n      onPress={() => onPress(stickerItem)}\n      accessibilityRole='button'\n    >\n      {hasError ? (\n        // If there was an error loading the sticker, display a default icon\n        <Image\n          source={require(\"../../../../src/theme/default/resources/icons/Base_Icon.png\")}\n          style={Styles.stickerImageStyle}\n          resizeMode='contain'\n        />\n      ) : (\n        // Otherwise, display the sticker image\n        <Image\n          source={{ uri: stickerItem?.stickerUrl }}\n          style={Styles.stickerImageStyle}\n          onLoad={() => setIsLoading(false)} // Update loading state when image loads\n          onError={() => {\n            setIsLoading(false);\n            setHasError(true); // Update error state if image fails to load\n          }}\n          resizeMode='contain'\n        />\n      )}\n      {isLoading && !hasError && (\n        // Show a loading indicator while the image is loading\n        <View style={Styles.activityIndicatorWrapper}>\n          <ActivityIndicator size='small' color={theme.color.primary} />\n        </View>\n      )}\n    </TouchableOpacity>\n  );\n};\n\n/**\n * CometChatStickerKeyboard Component\n * Fetches stickers from the Stickers extension and displays them in a keyboard layout.\n */\nexport const CometChatStickerKeyboard = (props: CometChatStickerKeyboardInterface) => {\n  const [stickerList, setStickerList] = React.useState<any[]>([]);\n  const [stickerSet, setStickerSet] = React.useState<Record<string, any[]>>({});\n  const [activeStickerList, setActiveStickerList] = React.useState<any[]>([]);\n  const [activeStickerSetName, setActiveStickerSetName] = React.useState<string | undefined>(\n    undefined\n  );\n\n  const [loading, setLoading] = React.useState<boolean>(true);\n  const [error, setError] = React.useState<string | null>(null);\n  const theme = useTheme();\n  const {t}= useCometChatTranslation()\n  const flatListRef = useRef<FlatList>(null);\n\n  /**\n   * Function to handle sending a sticker message\n   * @param stickerItem - The selected sticker item\n   */\n  const sendStickerMessage = (stickerItem: any) => {\n    if (props?.onPress) {\n      // Invoke the onPress callback with the sticker message\n      props.onPress({\n        ...stickerItem,\n        sticker_url: stickerItem?.stickerUrl,\n        sticker_name: stickerItem?.stickerSetName,\n      });\n    }\n  };\n\n  /**\n   * Function to handle the selection of a sticker set\n   * @param sectionItem - The name of the selected sticker set\n   */\n  const onStickerSetClicked = (sectionItem: string) => {\n    setActiveStickerList(stickerSet[sectionItem]);\n    setActiveStickerSetName(sectionItem);\n    // Scroll the sticker list to the top when a new set is selected\n    if (flatListRef.current) {\n      flatListRef.current.scrollToOffset({ offset: 0, animated: false });\n    }\n  };\n\n  /**\n   * Function to render the list of stickers based on the current state\n   */\n  const getStickerList = () => {\n    if (error) {\n      return (\n        <View style={[Styles.stickerContainer]}>\n          <View style={Styles.stickerMsgStyle}>\n            <Text\n              style={[\n                theme.typography.body.regular,\n                { color: theme.color.textTertiary, textAlign: \"center\" },\n              ]}\n            >\n              {t(\"SOMETHING_WENT_WRONG\")}\n            </Text>\n          </View>\n        </View>\n      );\n    }\n\n    // If data is still loading, display the skeleton loader\n    if (loading) {\n      return (\n        <View style={Styles.skeletonContainer}>\n          <Skeleton />\n        </View>\n      );\n    }\n\n    // If there are no stickers available, display an empty state view\n    if (stickerList.length === 0) {\n      return (\n        <ErrorEmptyView\n          title={t(\"NO_STICKERS_AVAILABLE\")}\n          subTitle={t(\"YOU_DONT_HAVE_STICKERS_YET\")}\n          Icon={\n            <Icon\n              containerStyle={{ marginBottom: 10 }}\n              name='base-icon'\n              width={theme.spacing.spacing.s15}\n              height={theme.spacing.spacing.s15}\n            />\n          }\n          containerStyle={[Styles.stickerContainer, Styles.emptyContainer]}\n          titleStyle={[theme.typography.heading4.bold, { color: theme.color.textPrimary }]}\n          subTitleStyle={[theme.typography.body.regular, { color: theme.color.textSecondary }]}\n        />\n      );\n    }\n\n    // If sticker sets are available, render them in a grid\n    if (stickerSet && Object.keys(stickerSet).length) {\n      const numberOfStickers = activeStickerList.length;\n      const numberOfPlaceholders = numberOfStickers % 3 === 0 ? 0 : 3 - (numberOfStickers % 3);\n      const paddedStickerList = [...activeStickerList];\n\n      for (let i = 0; i < numberOfPlaceholders; i++) {\n        paddedStickerList.push(null);\n      }\n\n      return (\n        <FlatList\n          ref={flatListRef}\n          data={paddedStickerList}\n          renderItem={({ item }) => (\n            <StickerItem stickerItem={item} onPress={sendStickerMessage} theme={theme} />\n          )}\n          keyExtractor={(item, index) => (item ? `${item.id}-${index}` : `placeholder-${index}`)}\n          numColumns={3}\n          contentContainerStyle={{\n            paddingHorizontal: theme.spacing.spacing.s5,\n          }} // Adds horizontal padding\n          showsVerticalScrollIndicator={false}\n        />\n      );\n    }\n\n    return null;\n  };\n\n  // Invoke custom hooks to fetch stickers and manage state\n  Hooks(\n    props,\n    stickerList,\n    stickerSet,\n    activeStickerSetName,\n    setStickerList,\n    setStickerSet,\n    setActiveStickerList,\n    setActiveStickerSetName,\n    setLoading,\n    setError\n  );\n\n  return (\n    <View style={[Styles.stickerWrapperStyle, { backgroundColor: theme.color.background1 }]}>\n      {/* Header displaying the active sticker set name or a default label */}\n      <View style={{ padding: 10 }}>\n        <Text\n          style={[\n            theme?.typography?.body.regular,\n            { color: theme.color.textTertiary, paddingLeft: 10 },\n          ]}\n        >\n          {activeStickerSetName}\n        </Text>\n      </View>\n      {/* Main content area for stickers and category selector */}\n      <View style={Styles.stickerContentWrapper}>\n        {/* Render the list of stickers based on current state */}\n        {getStickerList()}\n        {/* If there's no error, display the sticker set category selector */}\n        {!error && (\n          <View\n            style={[Styles.categorySelectorWrapper, { borderTopColor: theme.color.borderLight }]}\n          >\n            <FlatList\n              data={Object.keys(stickerSet)} // List of sticker set names\n              renderItem={({ item }) => {\n                // Retrieve the thumbnail for the sticker set (first sticker's URL)\n                const stickerSetThumbnail = stickerSet[item][0][ExtensionConstants.stickerUrl];\n                // Determine if this sticker set is currently active\n                const isActive = item === activeStickerSetName;\n\n                return (\n                  <TouchableOpacity\n                    style={Styles.sectionListItemStyle}\n                    onPress={() => onStickerSetClicked(item)}\n                    accessibilityLabel={`Sticker category ${item}`}\n                    accessibilityRole='button'\n                  >\n                    {isActive && (\n                      // Highlight the active sticker set\n                      <View\n                        style={[\n                          Styles.activeCategoryBackground,\n                          { backgroundColor: theme.color.primary },\n                        ]}\n                      />\n                    )}\n                    {/* Display the sticker set thumbnail */}\n                    <Image\n                      source={{ uri: stickerSetThumbnail }}\n                      style={Styles.stickerCategoryImageStyle}\n                      resizeMode='contain'\n                    />\n                  </TouchableOpacity>\n                );\n              }}\n              keyExtractor={(item, index) => `${item}-${index}`}\n              horizontal\n              showsHorizontalScrollIndicator={false}\n              contentContainerStyle={{\n                paddingHorizontal: theme.spacing.spacing.s3,\n              }}\n            />\n          </View>\n        )}\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/CometChatStickerKeyboard/Skeleton.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { Animated, Dimensions, Easing, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Rect, Stop } from \"react-native-svg\";\nimport { useThemeInternal } from \"../../../theme/hook\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonBox = ({ index, boxSize, gradientColors }: any) => {\n  // Determine if the box is the last in its row\n  const isLastInRow = (index + 1) % 3 === 0;\n\n  return (\n    <Svg\n      height={boxSize}\n      width={boxSize}\n      viewBox='0 0 100 100'\n      fill='none'\n      style={[\n        styles.skeletonBox,\n        {\n          width: boxSize,\n          height: boxSize,\n          marginRight: isLastInRow ? 0 : 8,\n        },\n      ]}\n    >\n      <Defs>\n        <LinearGradient\n          id={`paint0_linear_${index}`}\n          x1={10}\n          y1={50}\n          x2={90}\n          y2={50}\n          gradientUnits='userSpaceOnUse'\n        >\n          <Stop stopColor={gradientColors[0]} />\n          <Stop offset={1} stopColor={gradientColors[1]} />\n        </LinearGradient>\n      </Defs>\n      <Rect\n        x={10}\n        y={10}\n        width={80}\n        height={80}\n        rx={15}\n        ry={15}\n        fill={`url(#paint0_linear_${index})`}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const animatedValue = useRef(new Animated.Value(0)).current;\n  const [isLoading, setIsLoading] = useState(true);\n  const { mode } = useThemeInternal();\n\n  // Define static colors\n  const color = {\n    staticBlack: \"#000000\",\n    staticWhite: \"#FFFFFF\",\n  };\n\n  // Define skeletonStyle based on the theme mode\n  const skeletonStyle =\n    mode === \"light\"\n      ? {\n          linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"],\n          shimmerBackgroundColor: color.staticBlack,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        }\n      : {\n          linearGradientColors: [\"#383838\", \"#272727\"],\n          shimmerBackgroundColor: color.staticWhite,\n          shimmerOpacity: 0.01,\n          speed: 1,\n        };\n\n  const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } = skeletonStyle;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n\n    const loadData = setTimeout(() => {\n      setIsLoading(false);\n    }, 3000);\n\n    return () => clearTimeout(loadData);\n  }, [animatedValue, speed]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth, screenWidth],\n  });\n\n  if (!isLoading) {\n    return null;\n  }\n\n  const boxSize = (screenWidth - 22) / 3;\n  const shimmerWidth = screenWidth;\n  const shimmerHeight = boxSize + 16;\n\n  return (\n    <View style={styles.container}>\n      <View style={styles.grid}>\n        {new Array(6).fill(0).map((_, index) => (\n          <SkeletonBox\n            key={index}\n            index={index}\n            boxSize={boxSize}\n            gradientColors={linearGradientColors}\n          />\n        ))}\n      </View>\n      <Animated.View\n        style={[\n          styles.animatedShimmer,\n          {\n            width: shimmerWidth,\n            height: shimmerHeight,\n            transform: [{ translateX: shimmerTranslateX }],\n            backgroundColor: shimmerBackgroundColor,\n            opacity: shimmerOpacity,\n          },\n        ]}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    position: \"relative\",\n    borderRadius: 16,\n    overflow: \"hidden\",\n  },\n  grid: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n  },\n  skeletonBox: {\n    marginBottom: 10,\n    marginHorizontal: -10,\n  },\n  animatedShimmer: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n  },\n});\n\nexport default Skeleton;\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/CometChatStickerKeyboard/StickerKeyboardConfiguration.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\n\n/**\n * @class StickerKeyboardConfiguration\n * @description StickerKeyboardConfiguration class is used for defining the StickerKeyboard templates.\n * @param {Function} onPress\n * @param {Object} style\n */\n\nclass StickerKeyboardConfiguration {\n  onPress: (item: CometChat.CustomMessage) => void;\n  constructor({ onPress = (item: CometChat.CustomMessage) => {} }) {\n    this.onPress = onPress;\n  }\n}\n\nexport { StickerKeyboardConfiguration };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/CometChatStickerKeyboard/hooks.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React from \"react\";\nimport { MessageEvents } from \"../../../shared/events\";\nimport { CometChatUIEventHandler } from \"../../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { ExtensionConstants, ExtensionURLs } from \"../../ExtensionConstants\";\nimport { CometChatStickerKeyboardInterface } from \"./CometChatStickerKeyboard\";\nimport { useCometChatTranslation } from \"../../../shared/resources/CometChatLocalizeNew\";\n\nexport const Hooks = (\n  props: CometChatStickerKeyboardInterface, // Adjust type based on actual usage\n  stickerList: CometChat.CustomMessage[],\n  stickerSet: Record<string, CometChat.CustomMessage[]>,\n  activeStickerSetName: string | undefined,\n  setStickerList: React.Dispatch<React.SetStateAction<CometChat.CustomMessage[]>>,\n  setStickerSet: React.Dispatch<React.SetStateAction<Record<string, CometChat.CustomMessage[]>>>,\n  setActiveStickerList: React.Dispatch<React.SetStateAction<CometChat.CustomMessage[]>>,\n  setActiveStickerSetName: React.Dispatch<React.SetStateAction<string | undefined>>,\n  setLoading: React.Dispatch<React.SetStateAction<boolean>>,\n  setError: React.Dispatch<React.SetStateAction<string | null>>,\n) => {\n  const { t } = useCometChatTranslation();\n  React.useEffect(() => {\n    setLoading(true); // Start loading\n    CometChat.callExtension(ExtensionConstants.stickers, \"GET\", ExtensionURLs.stickers, {})\n      .then((stickers : any) => {\n        // Stickers received\n        const customStickers = stickers.hasOwnProperty(ExtensionConstants.customStickers)\n          ? stickers[ExtensionConstants.customStickers]\n          : [];\n        const defaultStickers = stickers.hasOwnProperty(ExtensionConstants.defaultStickers)\n          ? stickers[ExtensionConstants.defaultStickers]\n          : [];\n\n        defaultStickers.sort((a: any, b: any) => a.stickerSetOrder - b.stickerSetOrder);\n        customStickers.sort((a: any, b: any) => a.stickerSetOrder - b.stickerSetOrder);\n\n        const combinedStickers = [...defaultStickers, ...customStickers];\n        setStickerList(combinedStickers);\n\n        if (combinedStickers.length === 0) {\n          // Handle empty sticker list if necessary\n        }\n\n        setLoading(false); // End loading\n      })\n      .catch((error) => {\n        console.log(error);\n        setError(props?.errorText || t(\"SOMETHING_WENT_WRONG\"));\n        setLoading(false); // End loading even if there's an error\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageError, error);\n      });\n  }, []);\n\n  React.useEffect(() => {\n    if (stickerList.length > 0) {\n      const groupedStickers = stickerList.reduce((acc: any, sticker: any) => {\n        const { stickerSetName } = sticker;\n\n        if (!acc[stickerSetName]) {\n          acc[stickerSetName] = [];\n        }\n        acc[stickerSetName].push(sticker);\n\n        return acc;\n      }, {});\n\n      setStickerSet(groupedStickers);\n\n      // Set the first sticker set as active if not already set\n      if (!activeStickerSetName) {\n        const firstSetName = Object.keys(groupedStickers)[0];\n        setActiveStickerSetName(firstSetName);\n        setActiveStickerList(groupedStickers[firstSetName]);\n      }\n    } else {\n      setStickerSet({});\n      setActiveStickerList([]);\n      setActiveStickerSetName(undefined);\n    }\n  }, [stickerList]);\n\n  React.useEffect(() => {\n    if (stickerSet && Object.keys(stickerSet).length && activeStickerSetName) {\n      const sortedStickerSet = stickerSet[activeStickerSetName].sort(\n        (a: any, b: any) => a.stickerOrder - b.stickerOrder\n      );\n      setActiveStickerList(sortedStickerSet);\n    }\n  }, [stickerSet, activeStickerSetName]);\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/CometChatStickerKeyboard/index.ts",
    "content": "import { CometChatStickerKeyboard } from \"./CometChatStickerKeyboard\";\nimport { StickerKeyboardConfiguration } from \"./StickerKeyboardConfiguration\";\n\nexport { CometChatStickerKeyboard, StickerKeyboardConfiguration };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/CometChatStickerKeyboard/style.ts",
    "content": "import { Dimensions, StyleSheet } from \"react-native\";\nconst { height } = Dimensions.get(\"window\");\n\nexport const Styles = StyleSheet.create({\n  stickerWrapperStyle: {\n    marginTop: 5,\n    padding: 0,\n    marginHorizontal:-10,\n    height: height * 0.4,\n  },\n  stickerContainer: {\n    justifyContent: \"center\",\n    position: \"relative\",\n  },\n  stickerContentWrapper: {\n    flex: 1,\n    justifyContent: \"space-between\",\n  },\n  stickerListStyle: {\n    flexGrow: 1,\n  },\n  stickerItemStyle: {\n    flex: 1,\n    aspectRatio: 1,\n    margin: 10,\n    position: \"relative\",\n  },\n  stickerImageStyle: {\n    width: \"100%\",\n    height: \"100%\",\n    borderRadius: 8,\n  },\n  activityIndicatorWrapper: {\n    position: \"absolute\",\n    top: \"50%\",\n    left: \"50%\",\n    marginLeft: -10,\n    marginTop: -10,\n    zIndex: 2,\n    borderRadius: 10,\n    padding: 2,\n  },\n  sectionListItemStyle: {\n    paddingHorizontal: 10,\n    flexShrink: 0,\n    paddingVertical: 8,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  activeCategoryBackground: {\n    position: \"absolute\",\n    width: 40,\n    height: 40,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  stickerMsgStyle: {\n    top: 90,\n  },\n  stickerCategoryImageStyle: {\n    width: 28,\n    height: 28,\n    borderRadius: 14,\n  },\n  categorySelectorWrapper: {\n    borderTopWidth: 1,\n    paddingVertical: 10,\n  },\n  emptyContainer: {\n    top: 176,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  skeletonContainer: {\n    paddingHorizontal: 20,\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/StickerConfiguration.ts",
    "content": "import { ImageStyle } from \"react-native\";\n\nexport interface StickerConfigurationInterface {\n  style?: ImageStyle;\n}\n\nexport class StickerConfiguration implements StickerConfigurationInterface {\n  style?: ImageStyle;\n\n  constructor({ style = {} }: StickerConfigurationInterface) {\n    this.style = style;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/StickersBubble.tsx",
    "content": "import React from \"react\";\nimport { Image, ImageStyle } from \"react-native\";\n\nexport interface CometChatStickerBubbleProps {\n  /**\n   * image url pass as {uri: \"dummyUrl\"}\n   */\n  url: string;\n  /**\n   * place holder image\n   */\n  name?: string;\n  /**\n   * style object of type ImageBubbleStyleInterface\n   */\n  style?: ImageStyle;\n}\n\nexport const CometChatStickerBubble = (props: CometChatStickerBubbleProps) => {\n  const { url, style} = props;\n\n\n  return (\n    <Image\n      resizeMode={\"cover\"}\n      source={{ uri: url }}\n      style={style}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/StickersExtension.ts",
    "content": "import { ChatConfigurator, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { StickerConfiguration, StickerConfigurationInterface } from \"./StickerConfiguration\";\nimport { StickersExtensionDecorator } from \"./StickersExtensionDecorator\";\n\nexport class StickersExtension extends ExtensionsDataSource {\n  //Configuration prop taken as optional field in constructor\n  configuration?: StickerConfigurationInterface;\n\n  constructor(stickerConfiguration?: StickerConfigurationInterface) {\n    super();\n    this.configuration = new StickerConfiguration({\n      ...stickerConfiguration,\n    });\n  }\n\n  //override addExtension method from ExtensionsDataSource interface\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource) => {\n      return new StickersExtensionDecorator({\n        dataSource,\n        configration: this.configuration,\n      });\n    });\n  }\n\n  //override getExtensionId method from ExtensionsDataSource interface\n  override getExtensionId(): string {\n    return ExtensionConstants.stickers;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/StickersExtensionDecorator.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX, useCallback, useEffect, useRef, useState } from \"react\";\nimport { Keyboard, KeyboardEventName, Platform, TouchableOpacity, View } from \"react-native\";\nimport {\n  AdditionalParams,\n  CometChatMessageTemplate,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  MessageBubbleAlignmentType,\n} from \"../../shared\";\nimport { CometChatMessageEvents } from \"../../shared/events/CometChatMessageEvents\";\nimport { messageStatus } from \"../../shared/utils/CometChatMessageHelper\";\nimport { ChatConfigurator, DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { CometChatUIKit } from \"../../shared/CometChatUiKit/CometChatUIKit\";\nimport { MessageCategoryConstants, ViewAlignment } from \"../../shared/constants/UIKitConstants\";\nimport { Icon } from \"../../shared/icons/Icon\";\nimport { getUnixTimestampInMilliseconds } from \"../../shared/utils/CometChatMessageHelper\";\nimport { getMessagePreviewInternal } from \"../../shared/utils/MessageUtils\";\nimport { useTheme } from \"../../theme\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { ExtensionTypeConstants } from \"../ExtensionConstants\";\nimport { CometChatStickerKeyboard } from \"./CometChatStickerKeyboard\";\nimport { CometChatStickerBubble } from \"./StickersBubble\";\nimport { StickerConfigurationInterface } from \"./StickerConfiguration\";\nimport { AdditionalAuxiliaryOptionsParams } from \"../../shared/base/Types\";\nimport { getCometChatTranslation } from \"../../shared/resources/CometChatLocalizeNew/LocalizationManager\"\n\nconst t = getCometChatTranslation();\n\n/**\n * StickerButton Component\n * A button that toggles a sticker panel, allowing users to send stickers as custom messages.\n * Handles keyboard interactions and panel visibility for smooth user experience.\n *\n * @param {Object} props - Component props.\n * @param {CometChat.User} props.user - User object representing the chat receiver.\n * @param {CometChat.Group} props.group - Group object representing the chat group.\n * @param {Map<string, any>} props.id - Additional metadata, like parent message ID.\n */\nconst StickerButton = ({ user, group, id, stickerIconStyle, stickerIcon, replyToMessage, closeReplyPreview }: any) => {\n  const [isPanelOpen, setIsPanelOpen] = useState(false); // Tracks sticker panel visibility state.\n  const [keyboardOpen, setKeyboardOpen] = useState(false); // Tracks if the system keyboard is open.\n  const loggedInUser = useRef<CometChat.User | null>(null); // Stores the currently logged-in user.\n  const theme = useTheme(); // Retrieves theme configurations for styling.\n  const uiListenerIdRef = useRef<string>(`sticker_button_${Date.now()}`);\n  \n  // Use refs to store reply message info to avoid stale closures\n  const replyToMessageRef = useRef(replyToMessage);\n  const closeReplyPreviewRef = useRef(closeReplyPreview);\n\n  // Update refs when props change\n  useEffect(() => {\n    replyToMessageRef.current = replyToMessage;\n    closeReplyPreviewRef.current = closeReplyPreview;\n  }, [replyToMessage, closeReplyPreview]);\n\n\n  /**\n   * Fetches the logged-in user and sets it to `loggedInUser` ref.\n   */\n  useEffect(() => {\n    CometChat.getLoggedinUser().then((u) => (loggedInUser.current = u));\n  }, []);\n\n  /**\n   * Platform-specific keyboard event names for show and hide events.\n   */\n  const keyboardShowEvent = Platform.select({\n    ios: \"keyboardWillShow\",\n    android: \"keyboardDidShow\",\n  }) as KeyboardEventName;\n\n  const keyboardHideEvent = Platform.select({\n    ios: \"keyboardWillHide\",\n    android: \"keyboardDidHide\",\n  }) as KeyboardEventName;\n\n  /**\n   * Keyboard event listeners to update keyboard state.\n   * Automatically closes the sticker panel when the keyboard appears.\n   */\n  useEffect(() => {\n    const keyboardDidShowListener = Keyboard.addListener(keyboardShowEvent, () => {\n      setKeyboardOpen(true);\n      if (isPanelOpen) {\n        closePanel();\n      }\n    });\n\n    const keyboardDidHideListener = Keyboard.addListener(keyboardHideEvent, () => {\n      setKeyboardOpen(false);\n    });\n\n    return () => {\n      keyboardDidShowListener.remove();\n      keyboardDidHideListener.remove();\n    };\n  }, [isPanelOpen, keyboardShowEvent, keyboardHideEvent]);\n\n  /**\n   * Sends a custom sticker message to the receiver (user or group).\n   *\n   * @param {Object} sticker - Sticker object containing sticker details.\n   */\n  const sendCustomMessage = (sticker: any) => {\n    // Determine receiver details.\n    let receiverId = user?.getUid() || group?.getGuid();\n    let receiverType = user\n      ? CometChat.RECEIVER_TYPE.USER\n      : group\n        ? CometChat.RECEIVER_TYPE.GROUP\n        : undefined;\n\n    if (!receiverType) {\n      console.error(\"Receiver type is undefined.\");\n      return;\n    }\n\n    // Prepare the custom sticker message.\n    let customType = ExtensionTypeConstants.sticker;\n    let customData = sticker;\n    let parentId = id?.get(\"parentMessageId\") || undefined;\n    let customMessage = new CometChat.CustomMessage(\n      receiverId,\n      receiverType,\n      customType,\n      customData\n    );\n\n    // Configure message metadata and properties.\n    customMessage.setCategory(CometChat.CATEGORY_CUSTOM as CometChat.MessageCategory);\n    customMessage.setParentMessageId(parentId);\n    customMessage.setMuid(String(getUnixTimestampInMilliseconds()));\n    customMessage.setSender(loggedInUser.current!);\n    customMessage.setReceiver(user || group);\n    customMessage.shouldUpdateConversation(true);\n    customMessage.setMetadata({ incrementUnreadCount: true });\n\n    // Set quoted message if replying - use ref to get current value\n    const currentReplyMessage = replyToMessageRef.current;\n    if (currentReplyMessage) {\n      customMessage.setQuotedMessage(currentReplyMessage);\n      customMessage.setQuotedMessageId(currentReplyMessage.getId());\n    }\n\n    // Close reply preview immediately after triggering send\n    const currentClosePreview = closeReplyPreviewRef.current;\n    if (currentClosePreview) {\n      currentClosePreview();\n    }\n\n    // Send the custom message using CometChatUIKit.\n    CometChatUIKit.sendCustomMessage(customMessage)\n      .then((res) => {\n        // Emit reply event if this was a reply\n        if (currentReplyMessage) {\n          CometChatMessageEvents.emit(CometChatMessageEvents.ccReplyToMessage, {\n            message: res,\n            status: messageStatus.success,\n          });\n        }\n      })\n      .catch((err) => {\n        console.error(\"Failed to send sticker:\", err);\n      });\n  };\n\n  /**\n   * Opens the sticker panel.\n   */\n  const OpenPanel = useCallback(() => {\n    setIsPanelOpen(true);\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.showPanel, {\n      alignment: ViewAlignment.composerBottom,\n      child: () => <CometChatStickerKeyboard onPress={sendCustomMessage} />, // Render the sticker keyboard.\n      panelId: \"sticker\", // tag panel so we can identify related events\n    });\n  }, []);\n\n  /**\n   * Closes the sticker panel.\n   */\n  const closePanel = useCallback(() => {\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.hidePanel, {\n      alignment: ViewAlignment.composerBottom,\n      child: () => null, // Hide the panel content.\n      panelId: \"sticker\",\n    });\n    setIsPanelOpen(false);\n  }, []);\n\n  /**\n   * Toggles the sticker panel visibility.\n   * Handles interactions with the keyboard for a smooth user experience.\n   */\n  const togglePanel = useCallback(() => {\n    if (isPanelOpen) {\n      closePanel();\n    } else {\n      if (keyboardOpen) {\n        Keyboard.dismiss(); // Close the keyboard first.\n        setTimeout(() => {\n          OpenPanel(); // Open the sticker panel after a small delay.\n        }, 200);\n      } else {\n        OpenPanel(); // Open the panel directly if the keyboard isn't open.\n      }\n    }\n  }, [isPanelOpen, keyboardOpen, OpenPanel, closePanel]);\n\n  /**\n   * Listen to global UI events so external navigation / programmatic panel hides\n   * correctly update the local button highlight state.\n   */\n  useEffect(() => {\n    const id = uiListenerIdRef.current;\n    CometChatUIEventHandler.addUIListener(id, {\n      hidePanel: (payload: any) => {\n        if (isPanelOpen) {\n          if (!payload || payload?.panelId === \"sticker\" || payload?.alignment === ViewAlignment.composerBottom) {\n            setIsPanelOpen(false);\n          }\n        }\n      },\n      showPanel: (payload: any) => {\n        if (payload?.panelId === \"sticker\") {\n          setIsPanelOpen(true);\n        }\n      },\n    });\n    return () => {\n      CometChatUIEventHandler.removeUIListener(id);\n    };\n  }, [isPanelOpen]);\n\n  return (\n    <TouchableOpacity\n      key={\"sticker\"}\n      onPress={togglePanel}\n      style={{ justifyContent: \"center\", alignItems: \"center\", padding: 10 }}\n      accessibilityLabel='Sticker Button'\n      accessibilityHint='Opens the sticker panel'\n    >\n      <Icon\n        name='sticker-fill'\n        width={24}\n        height={24}\n        imageStyle={stickerIconStyle}\n        icon={!isPanelOpen ? stickerIcon?.inactive : stickerIcon?.active}\n        //color={!isPanelOpen ? theme.color.iconSecondary : theme.color.primary}\n        color={\n          !isPanelOpen\n            ? (stickerIconStyle.inactive.tintColor ?? theme.color.iconSecondary)\n            : theme.color.primary\n        }\n      />\n    </TouchableOpacity>\n  );\n};\n\n/**\n * StickersExtensionDecorator Class\n * Extends the DataSourceDecorator to add support for sticker messages.\n * Defines sticker message templates, auxiliary options, and category/type handling.\n */\nexport class StickersExtensionDecorator extends DataSourceDecorator {\n  configuration: StickerConfigurationInterface;\n\n  constructor(props: { dataSource: DataSource; configration?: StickerConfigurationInterface }) {\n    super(props.dataSource);\n    this.configuration = props.configration ?? {}; // Load configuration if provided.\n  }\n\n  /**\n   * Checks if the message is deleted.\n   * @param {CometChat.BaseMessage} message - The message to check.\n   * @returns {boolean} - True if the message is deleted, otherwise false.\n   */\n  isDeletedMessage(message: CometChat.BaseMessage): boolean {\n    return message.getDeletedBy() != null;\n  }\n\n  /**\n   * Adds sticker templates to the list of message templates.\n   */\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    let templates = super.getAllMessageTemplates(theme, additionalParams);\n    templates.push(\n      new CometChatMessageTemplate({\n        type: ExtensionTypeConstants.sticker,\n        category: MessageCategoryConstants.custom,\n        ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          if (this.isDeletedMessage(message)) {\n            return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n          } else {\n            return this.getStickerBubble(message as CometChat.CustomMessage, alignment, theme);\n          }\n        },\n        ReplyView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          const replyView = ChatConfigurator.dataSource.getReplyView?.(message, theme, additionalParams);\n          return replyView || null;\n        },\n        options: (loggedInuser, message, theme, group) => {\n          return ChatConfigurator.dataSource.getMessageOptions(\n            loggedInuser,\n            message,\n            theme,\n            group,\n            additionalParams\n          );\n        },\n        BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n          return ChatConfigurator.dataSource.getBottomView(message, alignment);\n        },\n      })\n    );\n    return templates;\n  }\n\n  /**\n   * Renders a sticker bubble containing the sticker image.\n   */\n  getStickerBubble(\n    message: CometChat.CustomMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    let url =\n      message?.[\"data\"]?.[\"customData\"]?.[\"stickerUrl\"] ||\n      message?.[\"data\"]?.[\"customData\"]?.[\"sticker_url\"];\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    const _style =\n      message.getSender().getUid() === loggedInUser!.getUid()\n        ? theme.messageListStyles.outgoingMessageBubbleStyles\n        : theme.messageListStyles.incomingMessageBubbleStyles;\n    return (\n      <View style={_style.stickerBubbleStyles?.containerStyle}>\n        <CometChatStickerBubble\n          url={url}\n          name=''\n          style={{\n            ...this.configuration?.style,\n            ..._style.stickerBubbleStyles?.imageStyle,\n          }}\n        />\n      </View>\n    );\n  }\n\n  /**\n   * Adds the sticker button to auxiliary options for message input.\n   */\n  getAuxiliaryOptions(\n    user: CometChat.User,\n    group: CometChat.Group,\n    id?: Map<string, any>,\n    additionalAuxiliaryParams?: AdditionalAuxiliaryOptionsParams\n  ) {\n    const auxiliaryOptions = super.getAuxiliaryOptions(\n      user,\n      group,\n      id ?? new Map<string, any>(),\n      additionalAuxiliaryParams\n    );\n    if (additionalAuxiliaryParams?.hideStickersButton) return auxiliaryOptions;\n    auxiliaryOptions.push(\n      <StickerButton\n        key='sticker-button'\n        user={user}\n        group={group}\n        id={id}\n        stickerIcon={additionalAuxiliaryParams?.stickerIcon}\n        stickerIconStyle={additionalAuxiliaryParams?.stickerIconStyle}\n        replyToMessage={additionalAuxiliaryParams?.replyToMessage}\n        closeReplyPreview={additionalAuxiliaryParams?.closeReplyPreview}\n      />\n    );\n    return auxiliaryOptions;\n  }\n\n  /**\n   * Ensures that \"custom\" is included in the list of message categories.\n   */\n  getAllMessageCategories(): string[] {\n    var categoryList: string[] = super.getAllMessageCategories();\n    if (!categoryList.includes(MessageCategoryConstants.custom)) {\n      categoryList.push(MessageCategoryConstants.custom);\n    }\n    return categoryList;\n  }\n\n  /**\n   * Adds the sticker type to the list of supported message types.\n   */\n  getAllMessageTypes(): string[] {\n    var messagesTypes: string[] = super.getAllMessageTypes();\n    messagesTypes.push(ExtensionTypeConstants.sticker);\n    return messagesTypes;\n  }\n\n  /**\n   * Returns a unique identifier for the sticker extension.\n   */\n  getId(): string {\n    return \"stickerExtension\";\n  }\n\n  /**\n   * Customizes the last conversation message preview for sticker messages.\n   */\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element {\n    const message = conversation.getLastMessage() as CometChat.BaseMessage;\n    if (\n      message != null &&\n      message.getType() === ExtensionTypeConstants.sticker &&\n      message.getCategory() === MessageCategoryConstants.custom &&\n      message.getDeletedAt() === undefined\n    ) {\n      return getMessagePreviewInternal(\"sticker-fill\", t(\"CUSTOM_MESSAGE_STICKER\"), {\n        theme,\n      });\n    } else {\n      return super.getLastConversationMessage(conversation, theme);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/index.ts",
    "content": "import { StickerConfigurationInterface } from \"./StickerConfiguration\";\nimport { CometChatStickerBubble, CometChatStickerBubbleProps } from \"./StickersBubble\";\nimport { StickersExtension } from \"./StickersExtension\";\nimport { StickersExtensionDecorator } from \"./StickersExtensionDecorator\";\n\nexport {\n  CometChatStickerBubble,\n  StickersExtension,\n  StickersExtensionDecorator,\n};\n\nexport type {\n  StickerConfigurationInterface,\n  CometChatStickerBubbleProps\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/resources/index.ts",
    "content": "import StickerIcon from \"./Stickers.png\";\n\nexport { StickerIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/Stickers/style.ts",
    "content": "import { deepMerge } from \"../../shared/helper/helperFunctions\";\nimport { CometChatTheme } from \"../../theme/type\";\n\nexport const getStickerStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"stickerBubbleStyles\"] => {\n  return {\n    containerStyle: {\n      width: 180,\n      backgroundColor: color.background2,\n      padding: spacing.padding.p0,\n      paddingBottom: spacing.padding.p0,\n      borderRadius: spacing.radius.r3,\n    },\n    dateReceiptContainerStyle: {\n      backgroundColor: color.receiveBubbleTimestamp,\n      paddingHorizontal: spacing.padding.p1,\n      borderRadius: spacing.radius.max,\n      position: \"absolute\",\n      bottom: 0,\n    },\n    dateStyles: {\n      containerStyle: {\n        paddingTop: spacing.padding.p0,\n        paddingBottom: spacing.padding.p0,\n        paddingHorizontal: spacing.padding.p1,\n      },\n      textStyle: {\n        color: color.sendBubbleTimestamp,\n      },\n    },\n    imageStyle: {\n      minHeight: 150,\n      minWidth: 150,\n      alignSelf: \"center\",\n    },\n  };\n};\n\nexport const getStickerStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"stickerBubbleStyles\"] => {\n  return deepMerge(getStickerStyleLight(color, spacing, typography)!, {\n    dateReceiptContainerStyle: {\n      backgroundColor: color.background1,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/ThumbnailGeneration/ThumbnailGenerationDecorator.tsx",
    "content": "import { DataSource, DataSourceDecorator } from \"../../shared/framework\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\nimport { getExtensionData } from \"../ExtensionModerator\";\nimport {\n  CometChatImageBubble,\n  CometChatVideoBubble,\n} from \"../../shared/views\";\nimport React, { JSX } from \"react\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { CometChatUIKit } from \"../../shared\";\n\n/**\n *\n *\n * @description Decorator to generate thumbnails for media messages.\n */\nexport class ThumbnailGenerationExtensionDecorator extends DataSourceDecorator {\n  /**\n   *\n   *\n   * @param {DataSource} dataSource\n   * @description Creates an instance of ThumbnailGenerationExtensionDecorator.\n   */\n  constructor(dataSource: DataSource) {\n    super(dataSource);\n  }\n\n  /**\n   *\n   *\n   * @returns {string}\n   * @description Returns the unique ID for the thumbnail generation extension.\n   */\n  getId(): string {\n    return \"ThumbnailGeneration\";\n  }\n\n  /**\n   *\n   *\n   * @param {CometChat.MediaMessage} message\n   * @returns {{ uri: string }}\n   * @description Checks for a generated thumbnail for the message. If available, returns the thumbnail URI;\n   * otherwise, returns the default image URI (for images) or an empty URI.\n   */\n  checkThumbnail(message: CometChat.MediaMessage) {\n    let image: { uri: string } = { uri: \"\" };\n    const thumbnailData = getExtensionData(message, ExtensionConstants.thumbnailGeneration);\n    if (thumbnailData == undefined) {\n      image = message.getType() === \"image\" ? { uri: (message?.getData() as any)?.url } : image;\n    } else {\n      const attachmentData = thumbnailData[\"attachments\"];\n      if (attachmentData.length) {\n        const dataObj = attachmentData[0];\n        if (!dataObj[\"error\"]) {\n          const imageLink = dataObj?.[\"data\"]?.[\"thumbnails\"]?.[\"url_small\"];\n          image = imageLink ? { uri: dataObj[\"data\"][\"thumbnails\"][\"url_small\"] } : image;\n        }\n      }\n    }\n    return image;\n  }\n\n  /**\n   *\n   *\n   * @param {string} videoUrl\n   * @param {string} thumbnailUrl\n   * @param {CometChat.MediaMessage} message\n   * @param {CometChatTheme} theme\n   * @returns {JSX.Element}\n   * @description Returns the video message bubble element with thumbnail support.\n   */\n  getVideoMessageBubble(\n    videoUrl: string,\n    thumbnailUrl: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ) {\n    const image = this.checkThumbnail(message);\n    const loggedInUser = CometChatUIKit.loggedInUser;\n    const _style =\n      message.getSender().getUid() === loggedInUser!.getUid()\n        ? theme.messageListStyles.outgoingMessageBubbleStyles?.videoBubbleStyles\n        : theme.messageListStyles.incomingMessageBubbleStyles?.videoBubbleStyles;\n    return (\n      <CometChatVideoBubble\n        videoUrl={videoUrl}\n        thumbnailUrl={image}\n        imageStyle={_style?.imageStyle}\n        playIcon={_style?.playIcon}\n        playIconStyle={_style?.playIconStyle}\n        playIconContainerStyle={_style?.playIconContainerStyle}\n        placeholderImage={_style?.placeholderImage}\n      />\n    );\n  }\n\n  /**\n   *\n   *\n   * @param {string} imageUrl\n   * @param {string} caption\n   * @param {CometChat.MediaMessage} message\n   * @param {CometChatTheme} theme\n   * @returns {JSX.Element}\n   * @description Returns the image message bubble element with thumbnail support.\n   */\n  getImageMessageBubble(\n    imageUrl: string,\n    caption: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element {\n    const image = this.checkThumbnail(message);\n    const loggedInUser = CometChatUIKit.loggedInUser!;\n    const _style =\n      message.getSender().getUid() === loggedInUser.getUid()\n        ? theme.messageListStyles.outgoingMessageBubbleStyles?.imageBubbleStyles\n        : theme.messageListStyles.incomingMessageBubbleStyles?.imageBubbleStyles;\n    return (\n      <CometChatImageBubble\n        imageUrl={imageUrl ? { uri: imageUrl } : image}\n        thumbnailUrl={image}\n        style={_style?.imageStyle}\n      />\n    );\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/ThumbnailGeneration/ThumbnailGenerationExtension.tsx",
    "content": "import { ChatConfigurator, DataSource, ExtensionsDataSource } from \"../../shared/framework\";\nimport { ThumbnailGenerationExtensionDecorator } from \"./ThumbnailGenerationDecorator\";\nimport { ExtensionConstants } from \"../ExtensionConstants\";\n\nexport class ThumbnailGenerationExtension extends ExtensionsDataSource {\n\n  constructor(\n  ) {\n    super();\n  }\n\n  //override addExtension method from ExtensionsDataSource interface\n  override addExtension(): void {\n    ChatConfigurator.enable((dataSource: DataSource) => {\n      return new ThumbnailGenerationExtensionDecorator(\n        dataSource\n      );\n    });\n  }\n\n  //override getExtensionId method from ExtensionsDataSource interface\n  override getExtensionId(): string {\n    return ExtensionConstants.thumbnailGeneration;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/ThumbnailGeneration/index.ts",
    "content": "import {\n  ThumbnailGenerationExtension,\n} from \"./ThumbnailGenerationExtension\";\n\nexport {ThumbnailGenerationExtension };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/ThumbnailGeneration/resources/index.ts",
    "content": "import defaultThumbnail from \"./default_image.png\";\n\nexport { defaultThumbnail };\n"
  },
  {
    "path": "packages/ChatUiKit/src/extensions/index.ts",
    "content": "import {\n  CollaborativeDocumentExtension,\n  CometChatCollaborativeDocumentBubble,\n} from \"./CollaborativeDocument\";\nimport {\n  CollaborativeWhiteboardExtension,\n  CometChatCollaborativeWhiteBoardBubble,\n} from \"./CollaborativeWhiteboard\";\nimport { ExtensionConstants } from \"./ExtensionConstants\";\nimport {\n  LinkPreviewBubble,\n  LinkPreviewBubbleInterface,\n  LinkPreviewExtension,\n} from \"./LinkPreview\";\nimport {\n  MessageTranslationBubble,\n  MessageTranslationExtension,\n} from \"./MessageTranslation\";\nimport {\n  CometChatCreatePoll,\n  CometChatCreatePollInterface,\n  PollsConfigurationInterface,\n  PollsExtension,\n} from \"./Polls\";\nimport {\n  CometChatStickerBubble,\n  CometChatStickerBubbleProps as CometChatStickerBubbleInterface,\n  StickerConfigurationInterface,\n  StickersExtension,\n} from \"./Stickers\";\nimport {\n  ThumbnailGenerationExtension,\n} from \"./ThumbnailGeneration\";\nexport {\n  CollaborativeDocumentExtension,\n  CollaborativeWhiteboardExtension,\n  CometChatCollaborativeDocumentBubble,\n  CometChatCollaborativeWhiteBoardBubble,\n  CometChatCreatePoll,\n  CometChatStickerBubble,\n  ExtensionConstants,\n  LinkPreviewBubble,\n  LinkPreviewExtension,\n  MessageTranslationBubble,\n  MessageTranslationExtension,\n  PollsExtension,\n  StickersExtension,\n  ThumbnailGenerationExtension,\n};\n\nexport type {\n  LinkPreviewBubbleInterface,\n  PollsConfigurationInterface,\n  StickerConfigurationInterface,\n  CometChatCreatePollInterface,\n  CometChatStickerBubbleInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/imageresolver.d.ts",
    "content": "declare module \"*.png\" {\n    const value: any;\n    export = value;\n}"
  },
  {
    "path": "packages/ChatUiKit/src/index.ts",
    "content": "/// <reference path=\"./imageresolver.d.ts\" />\n\nimport {\n  ActionItemInterface,\n  AdditionalParams,\n  AudioWaveformVisualizerProps,\n  //Add Call events here already exposed in Calls\n  //Framework\n  ChatConfigurator,\n  CometChatActionSheet,\n  CometChatAudioBubble,\n  CometChatAudioBubbleInterface,\n  CometChatAvatar,\n  CometChatBadge,\n  CometChatBottomSheet,\n  CometChatBottomSheetInterface,\n  CometChatConfirmDialog,\n  CometChatReportDialog,\n  CometChatConfirmDialogInterface,\n  CometChatConversationEvents,\n  //Utils\n  CometChatConversationUtils,\n  CometChatDate,\n  CometChatDateInterface,\n  CometChatEmojiKeyboard,\n  CometChatFileBubble,\n  CometChatFileBubbleInterface,\n  CometChatGroupsEvents,\n  CometChatImageBubble,\n  CometChatImageBubbleInterface,\n  CometChatInlineAudioRecorder,\n  CometChatInlineAudioRecorderProps,\n  CometChatInlineAudioRecorderStyle,\n  CometChatListActionsInterface,\n  //View\n  CometChatListItem,\n  CometChatListItemInterface,\n  CometChatListStylesInterface,\n  //Resources\n  CometChatMediaRecorder,\n  CometChatMediaRecorderInterface,\n  CometChatMentionsFormatter,\n  CometChatMessageComposerAction,\n  CometChatMessageInputInterface,\n  CometChatMessageOption,\n  CometChatMessagePreview,\n  CometChatMessageTemplate,\n  CometChatQuickReactions,\n  CometChatReactionList,\n  CometChatReactionListInterface,\n  CometChatReactions,\n  CometChatReactionsInterface,\n  CometChatSoundManager,\n  CometChatStatusIndicator,\n  CometChatStatusIndicatorInterface,\n  CometChatSuggestionList,\n  CometChatSuggestionListInterface,\n  CometChatTextBubble,\n  CometChatTextBubbleInterface,\n  CometChatTextFormatter,\n  //Events\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  //CometChatUIKit\n  CometChatUIKit,\n  CometChatUIKitHelper,\n  CometChatUiKitConstants,\n  CometChatUrlsFormatter,\n  CometChatRichTextFormatter,\n  RichTextStyle,\n  CometChatVideoBubble,\n  CometChatVideoBubbleInterface,\n  ConversationType,\n  DataSource,\n  DataSourceDecorator,\n  ExtensionsDataSource,\n  MentionTextStyle,\n  MessageBubbleAlignmentType,\n  MessageDataSource,\n  MessageEvents,\n  MessageListAlignmentType,\n  MessageTimeAlignmentType,\n  RecorderState,\n  SelectionMode,\n  SuggestionItem,\n  UIKitSettings,\n  UseAudioRecorderReturn,\n  WaveformStyle,\n  messageStatus,\n  Icon,\n  MenuItemInterface,\n  getCometChatTranslation,\n  getCurrentLanguage,\n} from \"./shared\";\n\nimport { stopStreamingForRunId, startStreamingForRunId, streamingState$, getStreamSpeed, setAIAssistantTools, setQueueCompletionCallback, removeQueueCompletionCallback, IStreamData,QueueCompletionCallback,checkAndTriggerQueueCompletion, getAIAssistantTools, handleWebsocketMessage, messageStream, notifyStreamRenderComplete, onConnected, onConnectionError, onDisconnected, setStreamSpeed, storeAIAssistantMessage, streamConnection$ } from \"./shared/services/stream-message.service\";\n\nimport  { StreamMessage}  from \"./shared/modals/StreamMessage\";\nimport {CometChatAIAssistantTools} from \"./shared/modals/CometChatAIAssistantTools\";\n\nimport {\n  CometChatUsers,\n  CometChatUsersActionsInterface,\n  CometChatUsersInterface,\n} from \"./CometChatUsers\";\n\nimport { CometChatGroups, CometChatGroupsInterface } from \"./CometChatGroups\";\n\nimport {\n  CometChatConversations,\n  ConversationInterface\n} from \"./CometChatConversations\";\n\nimport { CometChatGroupMembers, CometChatGroupMembersInterface } from \"./CometChatGroupMembers\";\n\nimport {\n  CometChatMessageInformation,\n  CometChatMessageInformationInterface,\n} from \"./CometChatMessageInformation\";\n\nimport {\n  CometChatMessageList,\n  CometChatMessageListActionsInterface,\n  CometChatMessageListProps,\n} from \"./CometChatMessageList\";\n\nimport {\n  CometChatMessageComposer,\n  CometChatMessageComposerInterface,\n} from \"./CometChatMessageComposer\";\n\nimport {\n  CometChatCompactMessageComposer,\n  CometChatCompactMessageComposerInterface,\n  SingleLineMessageComposerStyleInterface,\n} from \"./CometChatCompactMessageComposer\";\n\nimport { CometChatAIAssistantChatHistory } from \"./CometChatAIAssistantChatHistory\";\n\nimport { CometChatThreadHeader, CometChatThreadHeaderInterface } from \"./CometChatThreadHeader\";\n\nimport { CometChatSearch } from \"./CometChatSearch\";\n\nimport {\n  CallButtonStyle,\n  CallUIEvents,\n  CallingExtension,\n  CallingExtensionDecorator,\n  CallingPackage,\n  CometChatMeetCallBubble,\n  CometChatCallButtonConfiguration,\n  CometChatCallButtonConfigurationInterface,\n  CometChatCallButtons,\n  CometChatCallButtonsInterface,\n  CometChatCallLogs,\n  CometChatCallLogsConfigurationInterface,\n  CometChatIncomingCall,\n  CometChatOngoingCall,\n  CometChatOutgoingCall,\n} from \"./calls\";\n\nimport {\n  CollaborativeDocumentExtension,\n  CollaborativeWhiteboardExtension,\n  CometChatCollaborativeDocumentBubble,\n  CometChatCollaborativeWhiteBoardBubble,\n  CometChatCreatePoll,\n  CometChatCreatePollInterface,\n  CometChatStickerBubble,\n  CometChatStickerBubbleInterface,\n  ExtensionConstants,\n  LinkPreviewBubble,\n  LinkPreviewBubbleInterface,\n  LinkPreviewExtension,\n  MessageTranslationBubble,\n  MessageTranslationExtension,\n  PollsConfigurationInterface,\n  PollsExtension,\n  StickerConfigurationInterface,\n  StickersExtension,\n  ThumbnailGenerationExtension,\n} from \"./extensions\";\n\nimport { CometChatMessageListProps as CometChatMessageListInterface } from \"./CometChatMessageList\";\nimport { CometChatTheme } from \"./theme/type\";\nimport { getLastSeenTime } from \"./shared\";\nexport {\n  CallUIEvents,\n  CallingExtension,\n  CallingExtensionDecorator,\n  CallingPackage,\n  //\n  ChatConfigurator,\n  CollaborativeDocumentExtension,\n  CollaborativeWhiteboardExtension,\n  //\n  CometChatActionSheet,\n  CometChatAudioBubble,\n  CometChatAvatar,\n  CometChatBadge,\n  CometChatBottomSheet,\n  CometChatMeetCallBubble,\n  CometChatCallButtonConfiguration,\n  /* Call Buttons */\n  CometChatCallButtons,\n  /*Call Logs */\n  CometChatCallLogs,\n  CometChatCollaborativeDocumentBubble,\n  CometChatCollaborativeWhiteBoardBubble,\n  CometChatConfirmDialog,\n  CometChatReportDialog,\n  CometChatConversationEvents,\n  //\n  CometChatConversationUtils,\n  CometChatConversations,\n  CometChatCreatePoll,\n  CometChatDate,\n  /* Reactions */\n  /* Emoji Keyboard */\n  CometChatEmojiKeyboard,\n  CometChatFileBubble,\n  CometChatGroups,\n  CometChatGroupsEvents,\n  CometChatGroupMembers,\n  CometChatAIAssistantChatHistory,\n  CometChatImageBubble,\n  CometChatIncomingCall,\n  CometChatInlineAudioRecorder,\n  //\n  //\n  CometChatListItem,\n  //\n  CometChatMediaRecorder,\n  /* Emoji Keyboard */\n  /* Text Formatters */\n  CometChatMentionsFormatter,\n  CometChatMessageComposer,\n  CometChatCompactMessageComposer,\n  CometChatMessageInformation,\n  CometChatMessageList,\n  CometChatMessagePreview,\n  CometChatMessageTemplate,\n  CometChatOngoingCall,\n  //\n  CometChatOutgoingCall,\n  CometChatQuickReactions,\n  CometChatReactionList,\n  /*Call Logs */\n  /* Reactions */\n  CometChatReactions,\n  CometChatSoundManager,\n  CometChatStatusIndicator,\n  CometChatStickerBubble,\n  CometChatSuggestionList,\n  CometChatTextBubble,\n  CometChatTextFormatter,\n  CometChatThreadHeader,\n  CometChatSearch,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  CometChatUIKit,\n  CometChatUIKitHelper,\n  CometChatUiKitConstants,\n  CometChatUrlsFormatter,\n  CometChatRichTextFormatter,\n  RichTextStyle,\n  CometChatUsers,\n  CometChatVideoBubble,\n  DataSourceDecorator,\n  ExtensionConstants,\n  ExtensionsDataSource,\n  LinkPreviewBubble,\n  LinkPreviewExtension,\n  MentionTextStyle,\n  MessageDataSource,\n  MessageEvents,\n  MessageTranslationBubble,\n  MessageTranslationExtension,\n  PollsExtension,\n  StickersExtension,\n  SuggestionItem,\n  ThumbnailGenerationExtension,\n  UIKitSettings,\n  messageStatus,\n  Icon,\n  getCometChatTranslation,\n  getCurrentLanguage,\n  CometChatAIAssistantTools,\n  StreamMessage,\n  stopStreamingForRunId,\n  startStreamingForRunId,\n  streamingState$,\n  getStreamSpeed,\n  setAIAssistantTools,\n  setQueueCompletionCallback,\n  removeQueueCompletionCallback,\n  type IStreamData,\n  type QueueCompletionCallback,\n  checkAndTriggerQueueCompletion,\n  getAIAssistantTools,\n  handleWebsocketMessage,\n  messageStream,\n  notifyStreamRenderComplete,\n  onConnected,\n  onConnectionError,\n  onDisconnected,\n  setStreamSpeed,\n  storeAIAssistantMessage,\n  streamConnection$\n};\nexport { CometChatThemeProvider, useTheme } from \"./theme\";\n\nexport {CometChatI18nProvider,useCometChatTranslation} from \"./shared/resources/CometChatLocalizeNew\"\nexport {localizedDateHelperInstance,LocalizedDateHelper} from \"./shared/helper/LocalizedDateHelper\"\nexport { useLocalizedDate } from \"./shared/helper/useLocalizedDateHook\";\n\nexport { CometChatMessageHeader } from \"./CometChatMessageHeader\";\nexport {getLastSeenTime}\n\nexport type {\n  CometChatMessageComposerAction,\n  CallButtonStyle,\n  CometChatGroupsInterface,\n  CometChatGroupMembersInterface,\n  CometChatFileBubbleInterface,\n  CometChatImageBubbleInterface,\n  CometChatInlineAudioRecorderProps,\n  CometChatInlineAudioRecorderStyle,\n  CometChatListActionsInterface,\n  CometChatListItemInterface,\n  CometChatListStylesInterface,\n  CometChatMessageInformationInterface,\n  CometChatMessageInputInterface,\n  CometChatMessageListActionsInterface,\n  CometChatMessageListInterface,\n  CometChatMessageComposerInterface,\n  CometChatCompactMessageComposerInterface,\n  SingleLineMessageComposerStyleInterface,\n  CometChatMessageOption,\n  ActionItemInterface,\n  AdditionalParams,\n  AudioWaveformVisualizerProps,\n  SelectionMode,\n  CometChatReactionsInterface,\n  CometChatReactionListInterface,\n  CometChatMediaRecorderInterface,\n  CometChatDateInterface,\n  CometChatCreatePollInterface,\n  CometChatConfirmDialogInterface,\n  CometChatCallLogsConfigurationInterface,\n  CometChatCallButtonsInterface,\n  CometChatCallButtonConfigurationInterface,\n  CometChatBottomSheetInterface,\n  CometChatAudioBubbleInterface,\n  CometChatStatusIndicatorInterface,\n  CometChatStickerBubbleInterface,\n  CometChatSuggestionListInterface,\n  CometChatTextBubbleInterface,\n  CometChatThreadHeaderInterface,\n  CometChatUsersActionsInterface,\n  CometChatUsersInterface,\n  CometChatVideoBubbleInterface,\n  ConversationInterface,\n  LinkPreviewBubbleInterface,\n  PollsConfigurationInterface,\n  RecorderState,\n  StickerConfigurationInterface,\n  ConversationType,\n  DataSource,\n  MessageListAlignmentType,\n  MessageTimeAlignmentType,\n  MessageBubbleAlignmentType,\n  UseAudioRecorderReturn,\n  WaveformStyle,\n  CometChatTheme,\n  MenuItemInterface\n};\n\n// Rich Text Editor\nexport { default as RichTextEditor } from './CometChatRichTextEditor';\nexport type {\n  RichTextEditorRef,\n  ContentChangeEvent,\n  ActiveStylesState,\n  RichTextEditorProps,\n  RichTextEditorPropsExtended,\n} from './CometChatRichTextEditor';\nexport type {\n  Selection,\n  StyleRange,\n  Block,\n  BlockType,\n  TextAlignment,\n  EditorVariant,\n  ToolbarOption,\n  SelectionChangeEvent,\n  RichTextEditorRef as RichTextEditorRefType,\n} from './CometChatRichTextEditor/types';\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/CometChatUiKit/CometChatUIKit.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { Platform } from \"react-native\";\nimport { CallingExtension } from \"../../calls/CallingExtension\";\nimport { CallingPackage } from \"../../calls/CallingPackage\";\nimport { CollaborativeDocumentExtension } from \"../../extensions/CollaborativeDocument/CollaborativeDocumentExtension\";\nimport { CollaborativeWhiteboardExtension } from \"../../extensions/CollaborativeWhiteboard/CollaborativeWhiteboardExtension\";\nimport { LinkPreviewExtension } from \"../../extensions/LinkPreview\";\nimport { MessageTranslationExtension } from \"../../extensions/MessageTranslation\";\nimport { PollsExtension } from \"../../extensions/Polls/PollsExtension\";\nimport { StickersExtension } from \"../../extensions/Stickers\";\nimport { ThumbnailGenerationExtension } from \"../../extensions/ThumbnailGeneration\";\nimport { ListenerInitializer } from \"../events/ListenerInitializer\";\nimport { ChatConfigurator, ExtensionsDataSource } from \"../framework\";\nimport { CometChatSoundManager } from \"../resources\";\nimport { getUnixTimestampInMilliseconds, messageStatus } from \"../utils/CometChatMessageHelper\";\nimport { permissionUtil } from \"../utils/PermissionUtil\";\nimport { CometChatUIKitHelper } from \"./CometChatUIKitHelper\";\nimport { UIKitSettings } from \"./UIKitSettings\";\n\nexport class CometChatUIKit {\n  static uiKitSettings: UIKitSettings;\n  static loggedInUser: null | CometChat.User = null;\n  static conversationUpdateSettings: CometChat.ConversationUpdateSettings =\n    new CometChat.ConversationUpdateSettings();\n  private static loginListenerID: string = ``;\n  private static isLoginListenerAttached: boolean = false;\n  static init(uiKitSettings: UIKitSettings) {\n    //perform sdk init taking values from uiKitSettings\n    CometChatUIKit.uiKitSettings = {\n      ...uiKitSettings,\n    };\n    var appSetting = new CometChat.AppSettingsBuilder()\n      .autoEstablishSocketConnection(uiKitSettings.autoEstablishSocketConnection)\n      .overrideAdminHost(uiKitSettings?.overrideAdminHost || \"\")\n      .overrideClientHost(uiKitSettings?.overrideClientHost || \"\")\n      .setRegion(uiKitSettings.region);\n\n    appSetting.subscriptionType = uiKitSettings.subscriptionType || \"\";\n\n    if (\n      appSetting.subscriptionType === \"ROLES\" &&\n      Array.isArray(uiKitSettings.roles) &&\n      uiKitSettings.roles.length > 0\n    ) {\n      appSetting.roles = uiKitSettings.roles;\n    }\n\n    CometChatUIKit.attachListener();\n\n    return CometChat.init(uiKitSettings.appId, appSetting.build()).then(\n      async() => {\n        CometChat.setSource(\"uikit-v5\", Platform.OS, \"react-native\");\n        ListenerInitializer.attachListeners();\n        await CometChat.getLoggedinUser()\n          .then((user: any) => {\n            CometChatUIKit.setLoggedInUser(user);\n            if (user) {\n              this.enableExtensions();\n            }\n            CometChat.getConversationUpdateSettings().then(\n              (conversationUpdateSettings: CometChat.ConversationUpdateSettings) => {\n                CometChatUIKit.setConversationUpdateSettings(conversationUpdateSettings);\n              }\n            );\n            permissionUtil.init().then((res) => {\n              if (res !== true) {\n                console.warn(\"[IOS] Permission initialization failed.\");\n              }\n            });\n          })\n          .catch((error: any) => {\n            // CometChatUIKit.setLoggedInUser(null);\n          });\n      },\n      (error: any) => {\n        // console.log(\"Initialization failed with error:\", error);\n      }\n    );\n  }\n\n  static defaultExtensions: ExtensionsDataSource[] = [\n    new StickersExtension(),\n    new CollaborativeWhiteboardExtension(),\n    new CollaborativeDocumentExtension(),\n    new MessageTranslationExtension(),\n    new ThumbnailGenerationExtension(),\n    new LinkPreviewExtension(),\n    new PollsExtension(),\n  ];\n\n  private static attachListener() {\n    if (CometChatUIKit.isLoginListenerAttached) {\n      CometChatUIKit.removeListener();\n      CometChatUIKit.isLoginListenerAttached = false;\n      CometChatUIKit.loginListenerID = ``;\n    }\n\n    CometChatUIKit.loginListenerID = `__CometChatLoginListener__`;\n    CometChat.addLoginListener(\n      CometChatUIKit.loginListenerID,\n      new CometChat.LoginListener({\n        onLoggedIn: (user: CometChat.User) => {\n          CometChatUIKit.setLoggedInUser(user);\n          CometChat.getConversationUpdateSettings().then(\n            (conversationUpdateSettings: CometChat.ConversationUpdateSettings) => {\n              CometChatUIKit.setConversationUpdateSettings(conversationUpdateSettings);\n            }\n          );\n        },\n        onLoggedOut: () => {\n          CometChatUIKit.removeLoggedInUser();\n        },\n      })\n    );\n    CometChatUIKit.isLoginListenerAttached = true;\n  }\n\n  private static enableExtensions() {\n    ChatConfigurator.init(); //re-initialize data source\n    let isCallingExtensionEnabled = false;\n\n    let extensionList: ExtensionsDataSource[] =\n      this.uiKitSettings?.extensions || this.defaultExtensions;\n\n    if (this.uiKitSettings.callingExtension) {\n      this.uiKitSettings.callingExtension.enable();\n    }\n\n    if (extensionList.length > 0) {\n      extensionList.forEach((extension: ExtensionsDataSource) => {\n        if (extension.getExtensionId() == \"calling\") {\n          isCallingExtensionEnabled = true;\n        }\n        extension?.enable();\n      });\n    }\n\n    if (!CometChatUIKit.uiKitSettings.disableCalling && !isCallingExtensionEnabled) {\n      if (CallingPackage.isCallingPackageInstalled) new CallingExtension().enable();\n    }\n  }\n\n  static async getLoggedInUser(): Promise<CometChat.User> {\n    if (CometChatUIKit.checkAuthSettings(Promise.reject)) null;\n    let user = await CometChat.getLoggedinUser().catch((e) => Promise.reject(e));\n    if (user == null) {\n      throw new CometChat.CometChatException({\n        code: \"NOT_FOUND\",\n        message: \"Login user not found\",\n      });\n    } else {\n      this.enableExtensions();\n    }\n    return user;\n  }\n\n  private static setLoggedInUser(user: CometChat.User | null) {\n    this.loggedInUser = user;\n  }\n\n  private static setConversationUpdateSettings(\n    conversationUpdateSettings: CometChat.ConversationUpdateSettings\n  ) {\n    this.conversationUpdateSettings = conversationUpdateSettings;\n  }\n\n  static getConversationUpdateSettings(): CometChat.ConversationUpdateSettings {\n    return this.conversationUpdateSettings;\n  }\n\n  private static removeLoggedInUser() {\n    this.loggedInUser = null;\n  }\n\n  private static removeListener() {\n    if (CometChatUIKit.isLoginListenerAttached) {\n      CometChat.removeLoginListener(CometChatUIKit.loginListenerID);\n      CometChatUIKit.isLoginListenerAttached = false;\n      CometChatUIKit.loginListenerID = ``;\n    }\n  }\n\n  static async login({\n    uid,\n    authToken,\n  }: {\n    uid?: string;\n    authToken?: string;\n  }): Promise<CometChat.User> {\n    if (CometChatUIKit.checkAuthSettings(Promise.reject)) null;\n    if (uid) {\n      let user = await CometChat.login(uid, CometChatUIKit.uiKitSettings?.authKey).catch((e) =>\n        Promise.reject(e)\n      );\n      CometChatUIKit.setLoggedInUser(user);\n      CometChatUIKit.setConversationUpdateSettings(await CometChat.getConversationUpdateSettings());\n      this.enableExtensions();\n      return user;\n    }\n    if (authToken) {\n      let user = await CometChat.login(authToken).catch((e) => Promise.reject(e));\n      CometChatUIKit.setLoggedInUser(user);\n      CometChatUIKit.setConversationUpdateSettings(await CometChat.getConversationUpdateSettings());\n      this.enableExtensions();\n      return user;\n    }\n    return Promise.reject(\n      new CometChat.CometChatException({\n        code: \"INVALID_LOGIN_ATTEMPT\",\n        message: \"Provide uid or authToken\",\n      })\n    );\n  }\n\n  static logout(): Promise<Object> {\n    if (this.checkAuthSettings(Promise.reject)) {\n    }\n\n    return CometChat.logout();\n  }\n\n  static createUser(user: CometChat.User): Promise<CometChat.User> {\n    if (this.checkAuthSettings(Promise.reject)) {\n    }\n\n    return CometChat.createUser(user, this.uiKitSettings.authKey as string);\n  }\n\n  static updateUser(user: CometChat.User): Promise<CometChat.User> {\n    if (this.checkAuthSettings(Promise.reject)) {\n    }\n\n    return CometChat.updateUser(user, this.uiKitSettings.authKey as string);\n  }\n\n  //Error handling to give better logs\n  static checkAuthSettings(onError: (e: CometChat.CometChatException) => void): boolean {\n    if (this.uiKitSettings == null) {\n      if (onError != null) {\n        onError(\n          new CometChat.CometChatException({\n            code: \"ERR\",\n            name: \"Authentication null\",\n            message: \"Populate authSettings before initializing\",\n          })\n        );\n      }\n      return false;\n    }\n\n    if (!this.uiKitSettings?.appId) {\n      if (onError != null) {\n        onError(\n          new CometChat.CometChatException({\n            code: \"appIdErr\",\n            name: \"APP ID null\",\n            message: \"Populate appId in authSettings before initializing\",\n          })\n        );\n      }\n      return false;\n    }\n    return true;\n  }\n\n  //---------- Helper methods to send messages ----------\n  ///[sendCustomMessage] used to send a custom message\n  static sendCustomMessage(\n    message: CometChat.CustomMessage\n  ): Promise<CometChat.CustomMessage | CometChat.BaseMessage> {\n    return new Promise((resolve, reject) => {\n      if (!message.getMuid()) {\n        message.setMuid(String(getUnixTimestampInMilliseconds()));\n      }\n\n      if (!message.getSender() && this.loggedInUser) {\n        message.setSender(this.loggedInUser);\n      }\n\n      CometChatUIKitHelper.onMessageSent(message, messageStatus.inprogress);\n      CometChat.sendCustomMessage(message)\n        .then((customMessage: any) => {\n          CometChatUIKitHelper.onMessageSent(customMessage, messageStatus.success);\n          resolve(customMessage);\n        })\n        .catch((err: any) => {\n          let msg: any = message;\n          if (msg.data)\n            msg.data.metaData = { ...(msg.data.metaData ? msg.data.metaData : {}), error: true };\n          CometChatUIKitHelper.onMessageSent(msg, messageStatus.error);\n          reject(err);\n        });\n    });\n  }\n\n  ///[sendMediaMessage] used to send a media message\n  static sendMediaMessage(\n    message: CometChat.MediaMessage\n  ): Promise<CometChat.MediaMessage | CometChat.BaseMessage> {\n    return new Promise((resolve, reject) => {\n      if (!message.getMuid()) {\n        message.setMuid(String(getUnixTimestampInMilliseconds()));\n      }\n\n      if (!message.getSender() && this.loggedInUser) {\n        message.setSender(this.loggedInUser);\n      }\n\n      let hasAttachment;\n      try {\n        hasAttachment = message.getAttachment();\n      } catch (error) {\n        console.log(\"no attachment found\");\n      }\n      if (hasAttachment == undefined) {\n        let file = message[\"files\"][0];\n        if (file == undefined) {\n          reject(\n            new CometChat.CometChatException({\n              code: \"Invalid Media message object\",\n              message: \"file object not found.\",\n            })\n          );\n        }\n\n        let attachmentObject: CometChat.Attachment = new CometChat.Attachment(file);\n        attachmentObject.setName(file[\"name\"]);\n        attachmentObject.setExtension((file[\"name\"].lastIndexOf(\".\") + 1).toString());\n        attachmentObject.setMimeType(file[\"type\"]);\n        attachmentObject.setSize(0);\n        attachmentObject.setUrl(file[\"uri\"]);\n        message.setAttachment(attachmentObject);\n      }\n\n      CometChatUIKitHelper.onMessageSent(message, messageStatus.inprogress);\n      CometChat.sendMediaMessage(message)\n        .then((mediaMessage: any) => {\n          CometChatUIKitHelper.onMessageSent(mediaMessage, messageStatus.success);\n          resolve(mediaMessage);\n        })\n        .catch((err: any) => {\n          let msg: any = message;\n          if (msg.data)\n            msg.data.metaData = { ...(msg.data.metaData ? msg.data.metaData : {}), error: true };\n          CometChatUIKitHelper.onMessageSent(msg, messageStatus.error);\n          reject(err);\n        });\n    });\n  }\n\n  ///[sendTextMessage] used to send a text message\n  static sendTextMessage(\n    message: CometChat.TextMessage\n  ): Promise<CometChat.TextMessage | CometChat.BaseMessage> {\n    return new Promise((resolve, reject) => {\n      if (!message?.getMuid()) {\n        message.setMuid(String(getUnixTimestampInMilliseconds()));\n      }\n\n      if (!message?.getSender() && this.loggedInUser) {\n        message.setSender(this.loggedInUser);\n      }\n\n      CometChatUIKitHelper.onMessageSent(message, messageStatus.inprogress);\n      CometChat.sendMessage(message)\n        .then((textMessage: any) => {\n          CometChatUIKitHelper.onMessageSent(textMessage, messageStatus.success);\n          resolve(textMessage);\n        })\n        .catch((err: any) => {\n          let msg: any = message;\n          if (msg.data)\n            msg.data.metaData = { ...(msg.data.metaData ? msg.data.metaData : {}), error: true };\n          CometChatUIKitHelper.onMessageSent(msg, messageStatus.error);\n          reject(err);\n        });\n    });\n  }\n\n  static getDataSource() {\n    return ChatConfigurator.getDataSource();\n  }\n\n  static SoundManager: typeof CometChatSoundManager = CometChatSoundManager;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/CometChatUiKit/CometChatUIKitHelper.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport {\n  CometChatConversationEvents,\n  CometChatGroupsEvents,\n  CometChatUIEvents,\n  MessageEvents,\n} from \"../events\";\nimport { CometChatUIEventHandler } from \"../events/CometChatUIEventHandler/CometChatUIEventHandler\";\n\nexport class CometChatUIKitHelper {\n  //---------- Message Events ----------\n  static onMessageSent(message: CometChat.BaseMessage, status: string): void {\n    CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, { message, status });\n  }\n\n  static onMessageEdited(message: CometChat.BaseMessage, status: string): void {\n    CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, { message, status });\n  }\n\n  static onMessageDeleted(message: CometChat.BaseMessage): void {\n    CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageDeleted, { message });\n  }\n\n  static onMessageRead(message: CometChat.BaseMessage): void {\n    CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, { message });\n  }\n\n  //---------- User Events ----------\n  static onUserBlocked(user: CometChat.User): void {\n    CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, { user });\n  }\n\n  static onUserUnblocked(user: CometChat.User): void {\n    CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, { user });\n  }\n\n  //---------- Group Events ----------\n  static onGroupCreated(group: CometChat.Group): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupCreated, { group });\n  }\n\n  static onGroupDeleted(group: CometChat.Group): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupDeleted, { group });\n  }\n\n  static onGroupLeft(\n    message: CometChat.Action,\n    leftUser: CometChat.User,\n    leftGroup: CometChat.Group\n  ): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {\n      message,\n      leftUser,\n      leftGroup,\n    });\n  }\n\n  static onGroupMemberScopeChanged(\n    message: CometChat.Action,\n    updatedUser: CometChat.User,\n    scopeChangedTo: string,\n    scopeChangedFrom: string,\n    group: CometChat.Group\n  ): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupMemberScopeChanged, {\n      message,\n      updatedUser,\n      scopeChangedTo,\n      scopeChangedFrom,\n      group,\n    });\n  }\n\n  static onGroupMemberBanned(\n    message: CometChat.Action,\n    bannedUser: CometChat.User,\n    bannedBy: CometChat.User,\n    bannedFrom: CometChat.Group\n  ): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupMemberBanned, {\n      message,\n      bannedUser,\n      bannedBy,\n      group: bannedFrom,\n    });\n  }\n\n  static onGroupMemberKicked(\n    message: CometChat.Action,\n    kickedUser: CometChat.User,\n    kickedBy: CometChat.User,\n    kickedFrom: CometChat.Group\n  ): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatGroupsEvents.ccGroupMemberKicked, {\n      message,\n      kickedUser,\n      kickedBy,\n      group: kickedFrom,\n    });\n  }\n\n  static onGroupMemberUnbanned(\n    message: CometChat.Action,\n    unbannedUser: CometChat.User,\n    unbannedBy: CometChat.User,\n    unbannedFrom: CometChat.Group\n  ): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupMemberUnBanned, {\n      message,\n      unbannedUser,\n      unbannedBy,\n      unbannedFrom,\n    });\n  }\n\n  static onGroupMemberJoined(joinedUser: CometChat.User, joinedGroup: CometChat.Group): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupMemberJoined, {\n      joinedUser,\n      joinedGroup,\n    });\n  }\n\n  static onGroupMemberAdded(\n    messages: CometChat.Action[],\n    usersAdded: CometChat.User[],\n    groupAddedIn: CometChat.Group,\n    addedBy: CometChat.User\n  ): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupMemberAdded, {\n      messages,\n      usersAdded,\n      usersAddedIn: groupAddedIn,\n      addedBy,\n    });\n  }\n\n  static onOwnershipChanged(group: CometChat.Group, newOwner: CometChat.GroupMember): void {\n    CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccOwnershipChanged, {\n      group,\n      newOwner,\n    });\n  }\n\n  //---------- Conversation Events ----------\n  static onConversationDeleted(conversation: CometChat.Conversation): void {\n    CometChatUIEventHandler.emitConversationEvent(\n      CometChatConversationEvents.ccConversationDeleted,\n      { conversation }\n    );\n  }\n\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/CometChatUiKit/UIKitSettings.ts",
    "content": "import { CallingExtension } from \"../../calls\";\nimport { ExtensionsDataSource } from \"../framework\";\n\nexport type UIKitSettings = {\n  appId: string;\n  region: string;\n  authKey?: string;\n  subscriptionType?: \"NONE\" | \"ALL_USERS\" | \"ROLES\" | \"FRIENDS\";\n  autoEstablishSocketConnection?: boolean;\n  overrideAdminHost?: string;\n  overrideClientHost?: string;\n  deviceToken?: string;\n  googleApiKey?: string;\n  disableCalling?: boolean;\n  extensions?: ExtensionsDataSource[];\n  roles?: string[];\n  callingExtension?: CallingExtension;\n};\n\nexport function UIKitSettings({\n  appId = \"xxxxxxxxxx\",\n  region = \"xx\",\n  authKey = \"xxxxxxxxxxxxxxxxxxxxx\",\n  subscriptionType,\n  autoEstablishSocketConnection,\n  overrideAdminHost,\n  overrideClientHost,\n  deviceToken,\n  googleApiKey,\n  disableCalling,\n  extensions,\n  roles,\n  callingExtension,\n}: UIKitSettings): UIKitSettings {\n  return {\n    appId,\n    region,\n    authKey,\n    subscriptionType,\n    autoEstablishSocketConnection,\n    overrideAdminHost,\n    overrideClientHost,\n    deviceToken,\n    googleApiKey,\n    disableCalling,\n    extensions,\n    roles,\n    callingExtension,\n  };\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/CometChatUiKit/index.ts",
    "content": "import { CometChatUIKit } from \"./CometChatUIKit\";\nimport { CometChatUIKitHelper } from \"./CometChatUIKitHelper\";\nimport { UIKitSettings } from \"./UIKitSettings\";\n\nexport { CometChatUIKit, CometChatUIKitHelper, UIKitSettings };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/assets/images/index.ts",
    "content": "import BACK from \"./Back.png\";\nimport BACK_ARROW from \"./BackArrow.png\";\nimport BLUE_DOUBLE_TICK from \"./blue-double-tick-icon.png\";\nimport CALENDAR from \"./calendar.png\";\nimport CHECK_MARK from \"./checkmark.png\";\nimport CLOCK from \"./clock.png\";\nimport EARTH from \"./earth.png\";\nimport CLOCK_ALERT from \"./empty-slot.png\";\nimport ERROR_TICK from \"./error.png\";\nimport FORWARD_ARROW from \"./forwardArrow.png\";\nimport GREY_DOUBLE_TICK from \"./grey-double-tick-icon.png\";\nimport GREY_TICK from \"./grey-tick-icon.png\";\nimport WAITING from \"./sending.png\";\n\nexport const ICONS = {\n  BACK,\n  CALENDAR,\n  CLOCK,\n  CLOCK_ALERT,\n  EARTH,\n  BACK_ARROW,\n  FORWARD_ARROW,\n  CHECK_MARK,\n  WAITING,\n  GREY_TICK,\n  GREY_DOUBLE_TICK,\n  BLUE_DOUBLE_TICK,\n  ERROR_TICK,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/BaseStyle.ts",
    "content": "import { DimensionValue } from \"react-native\";\nimport { BorderStyle, BorderStyleInterface } from \"./BorderStyle\";\n\n/**\n * @class BaseStyle\n * @description BaseStyle class is the parent class used for defining the basic styling props.\n * @param {string} width\n * @param {string|number} height\n * @param {string} backgroundColor\n * @param {number} borderRadius\n * @param {object} border\n\n */\nexport class BaseStyle {\n  height?: DimensionValue;\n  width?: DimensionValue;\n  backgroundColor?: string;\n  border?: BorderStyleInterface;\n  borderRadius?: number;\n  constructor({\n    width = \"100%\",\n    height = \"100%\",\n    backgroundColor = \"white\",\n    border = new BorderStyle({}),\n    borderRadius = 8,\n  }: BaseStyleInterface) {\n    this.height = height;\n    this.width = width;\n    this.backgroundColor = backgroundColor;\n    this.border = border;\n    this.borderRadius = borderRadius;\n  }\n}\n\nexport interface BaseStyleInterface {\n  height?: DimensionValue;\n  width?: DimensionValue;\n  backgroundColor?: string;\n  border?: BorderStyleInterface;\n  borderRadius?: number;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/BorderStyle.ts",
    "content": "/**\n * *\n * @class BorderStyle\n * @description BorderStyle class is used for defining the border.\n * @param  {number} borderWidth\n * @param  {string} borderStyle\n * @param  {string} borderColor\n */\nexport class BorderStyle {\n  borderWidth?: number;\n  borderStyle?: \"solid\" | \"dotted\" | \"dashed\";\n  borderColor?: string;\n  constructor({\n    borderWidth = 0,\n    borderStyle = \"solid\",\n    borderColor = \"transparent\",\n  }: BorderStyleInterface) {\n    this.borderWidth = borderWidth;\n    this.borderStyle = borderStyle;\n    this.borderColor = borderColor;\n  }\n}\n\nexport interface BorderStyleInterface {\n  borderWidth?: number;\n  borderStyle?: \"solid\" | \"dotted\" | \"dashed\";\n  borderColor?: string;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/FontStyle.ts",
    "content": "/**\n * @class FontStyle\n * @description\n * @param {String} fontFamily\n * @param {String} fontWeight\n * @param {String} fontSize\n */\nexport class FontStyle {\n  fontFamily?: string | undefined;\n  fontWeight?:\n    | \"normal\"\n    | \"bold\"\n    | \"100\"\n    | \"200\"\n    | \"300\"\n    | \"400\"\n    | \"500\"\n    | \"600\"\n    | \"700\"\n    | \"800\"\n    | \"900\";\n  fontSize?: number;\n\n  constructor({\n    fontFamily = undefined,\n    fontWeight = \"normal\",\n    fontSize = 16,\n  }: FontStyleInterface) {\n    this.fontFamily = fontFamily;\n    this.fontWeight = fontWeight;\n    this.fontSize = fontSize;\n  }\n}\nexport interface FontStyleInterface {\n  fontFamily?: string | undefined;\n  fontWeight?:\n    | \"normal\"\n    | \"bold\"\n    | \"100\"\n    | \"200\"\n    | \"300\"\n    | \"400\"\n    | \"500\"\n    | \"600\"\n    | \"700\"\n    | \"800\"\n    | \"900\";\n  fontSize?: number;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/ShadowStyle.ts",
    "content": "/**\n * @class ShadowStyle\n * @description Style class creates an object, which can be applied to shadow property of component style\n */\nexport class ShadowStyle {\n  elevation?: number;\n  shadowOffset?: { width?: number; height?: number };\n  shadowColor?: string;\n  shadowOpacity?: number;\n  shadowRadius?: number;\n  /**\n   * @param {object} param0\n   * @param {number} param0.elevation\n   * @param {object} param0.shadowOffset\n   * @param {string} param0.shadowColor\n   * @param {number} param0.shadowOpacity\n   * @param {number} param0.shadowRadius\n   */\n  constructor({\n    elevation = 0,\n    shadowOffset = { width: 0, height: 0 },\n    shadowColor = \"#171717\",\n    shadowOpacity = 0,\n    shadowRadius = 0,\n  }: ShadowStyleInterface) {\n    this.elevation = elevation;\n    this.shadowOffset = shadowOffset;\n    this.shadowColor = shadowColor;\n    this.shadowOpacity = shadowOpacity;\n    this.shadowRadius = shadowRadius;\n  }\n}\n\nexport interface ShadowStyleInterface {\n  elevation?: number;\n  shadowOffset?: { width?: number; height?: number };\n  shadowColor?: string;\n  shadowOpacity?: number;\n  shadowRadius?: number;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/Types.ts",
    "content": "import { ImageSourcePropType, ImageStyle, ImageURISource } from \"react-native\";\nimport { CometChatTextFormatter } from \"../formatters\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { DeepPartial } from \"../helper/types\";\nimport { JSX } from \"react\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\n\nexport type SelectionMode = \"none\" | \"single\" | \"multiple\";\n\nexport type ConversationType = \"both\" | \"users\" | \"groups\";\n\nexport type MessageListAlignmentType = \"standard\" | \"leftAligned\";\n\nexport type MessageBubbleAlignmentType = \"left\" | \"right\" | \"center\";\n\nexport type MessageTimeAlignmentType = \"top\" | \"bottom\";\n\nexport type AdditionalParams = {\n  textFormatters?: CometChatTextFormatter[];\n  disableMentions?: boolean;\n  hideReplyOption?: boolean,\n  hideReplyInThreadOption?: boolean,\n  hideShareMessageOption?: boolean,\n  hideEditMessageOption?: boolean,\n  hideTranslateMessageOption?: boolean,\n  hideDeleteMessageOption?: boolean,\n  hideReactionOption?: boolean,\n  hideMessagePrivatelyOption?: boolean,\n  hideCopyMessageOption?: boolean,\n  hideMessageInfoOption?: boolean,\n  hideReportMessageOption?: boolean,\n  hideMarkAsUnreadOption?: boolean,\n  hideGroupActionMessages?: boolean,\n  onReplyClick?: (messageId: string) => void;\n};\n\nexport type AdditionalAttachmentOptionsParams = {\n  hideCameraOption?: boolean;\n  hideImageAttachmentOption?: boolean;\n  hideVideoAttachmentOption?: boolean;\n  hideAudioAttachmentOption?: boolean;\n  hideFileAttachmentOption?: boolean;\n  hidePollsAttachmentOption?: boolean;\n  hideCollaborativeDocumentOption?: boolean;\n  hideCollaborativeWhiteboardOption?: boolean;\n  replyToMessage?: CometChat.BaseMessage;\n  closeReplyPreview?: () => void;\n};\n\nexport type AdditionalAuxiliaryOptionsParams = {\n  stickerIconStyle?: {\n    active: ImageStyle,\n    inactive: ImageStyle;\n  };\n  stickerIcon?: JSX.Element | ImageSourcePropType;\n  hideStickersButton?: boolean;\n  replyToMessage?: CometChat.BaseMessage;\n  closeReplyPreview?: () => void;\n};\n\nexport type AdditionalAuxiliaryHeaderOptionsParams = {\n  callButtonStyle?: DeepPartial<CometChatTheme['callButtonStyles']>;\n  hideVoiceCallButton?: boolean;\n  hideVideoCallButton?: boolean;\n};\n\n//AdditionalParams"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/index.ts",
    "content": "import {\n  AdditionalParams,\n  ConversationType,\n  MessageBubbleAlignmentType,\n  MessageListAlignmentType,\n  MessageTimeAlignmentType,\n  SelectionMode,\n} from \"./Types\";\n            \nexport type {\n  MessageListAlignmentType,\n  SelectionMode,\n  ConversationType,\n  AdditionalParams,\n  MessageBubbleAlignmentType,\n  MessageTimeAlignmentType,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/base/vars.ts",
    "content": "interface IThis {\n  safeAreaInsets: {\n    top: number | null;\n    bottom: number | null;\n  };\n}\n\nexport const commonVars: IThis = {\n  safeAreaInsets: {\n    top: null,\n    bottom: null,\n  },\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/constants/UIKitConstants.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\n\nexport const USER_ONLINE_STATUS = \"online\";\nexport const NO_USERS_FOUND = \"no_users_found\";\nexport const NO_DATA_FOUND = \"no_data_found\";\nexport const SOMETHING_WRONG = \"something_wrong\";\nexport const LOADING = \"loading\";\nexport const NO_GROUPS_FOUND = \"no_groups_found\";\nexport const ON_USER_ITEM_CLICK = \"onItemClick\";\nexport const ON_USER_ERROR = \"onUserError\";\nexport const ON_USER_BLOCK = \"onUserBlock\";\nexport const ON_USER_UNBLOCK = \"onUserUnBlock\";\nexport const USERS_CLICK_LISTENER_ID = \"clicklistener2\";\nexport const USERS = \"Users\";\nexport const SEARCH = \"Search\";\n\nexport const CALL_INITIATED = CometChat.CALL_STATUS.INITIATED;\nexport const CALL_ONGOING = CometChat.CALL_STATUS.ONGOING;\nexport const CALL_REJECTED = CometChat.CALL_STATUS.REJECTED;\nexport const CALL_CANCELLED = CometChat.CALL_STATUS.CANCELLED;\nexport const CALL_BUSY = CometChat.CALL_STATUS.BUSY;\nexport const CALL_UNANSWERED = CometChat.CALL_STATUS.UNANSWERED;\nexport const CALL_ENDED = CometChat.CALL_STATUS.ENDED;\n\nexport const ON_INCOMING_CALL_RECEIVED = \"onIncomingCallReceived\";\nexport const ON_INCOMING_CALL_CANCELLED = \"onIncomingCallCancelled\";\nexport const ON_CUSTOM_MESSAGE_RECEIVED = \"onCustomMessageReceived\";\nexport const ON_MEDIA_MESSAGE_REVEIVED = \"onMediaMessageReceived\";\nexport const ON_MESSAGE_DELETED = \"onMessageDeleted\";\nexport const ON_MESSAGE_DELIVERED = \"ON_MESSAGE_DELIVERED\";\nexport const ON_MESSAGE_EDITED = \"messageEdited\";\nexport const ON_MESSAGE_READ = \"messageRead\";\nexport const ON_MESSAGE_RECEIVED = \"ON_MESSAGE_RECEIVED\";\nexport const ON_TEXT_MESSAGE_RECEIVED = \"onTextMessageReceived\";\nexport const ON_TRANSIENT_MESSAGE_RECEIVED = \"ON_TRANSIENT_MESSAGE_RECEIVED\";\nexport const ON_TYPING_ENDED = \"onTypingEnded\";\nexport const ON_TYPING_STARTED = \"onTypingStarted\";\nexport const ON_USER_OFFLIE = \"onUserOffline\";\nexport const ON_USER_ONLINE = \"onUserOnline\";\nexport const ON_GROUP_MEMBER_ADDED_TO_GROUP = \"onGroupMemberAddedToGroup\";\nexport const ON_GROUP_MEMBER_BANNED = \"onGroupMemberBanned\";\nexport const ON_GROUP_MEMBER_JOINED = \"onGroupMemberJoined\";\nexport const ON_GROUP_MEMBER_KICKED = \"onGroupMemberKicked\";\nexport const ON_GROUP_MEMBER_LEFT = \"onGroupMemberLeft\";\nexport const ON_GROUP_MEMBER_SCOPE_CHANGED = \"onGroupMemberScopeChanged\";\nexport const ON_GROUP_MEMBER_UNBANNED = \"onGroupMemberUnbanned\";\nexport const ON_CONVERSATION_ITEM_CLICK = \"onConversationItemClicked\";\n\nexport const PRIVATE_GROUP_COLOR = \"rgb(0, 200, 111)\";\nexport const PASSWORD_GROUP_COLOR = \"rgb(247, 165, 0)\";\n\nexport const IMAGE_PREFETCH_MAX_ATTEMPTS = 5;\nexport const IMAGE_PREFETCH_TIMEOUT = 800;\n\nconst wordBoundary = {\n  start: `(?:^|:|;|'|\"|,|{|}|\\\\.|\\\\s|\\\\!|\\\\?|\\\\(|\\\\)|\\\\[|\\\\]|\\\\*)`,\n  end: `(?=$|:|;|'|\"|,|{|}|\\\\.|\\\\s|\\\\!|\\\\?|\\\\(|\\\\)|\\\\[|\\\\]|\\\\*)`,\n};\n\nexport const emailPattern =\n  wordBoundary.start + `[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,6}` + wordBoundary.end;\n\nexport const urlPattern =\n  `((https?://|ftp://|www\\\\.|pic\\\\.)[-\\\\w;/?:@&=+$\\\\|\\\\_.!~*\\\\|'()\\\\[\\\\]%#,☺]+[\\\\w/#](\\\\(\\\\))?` +\n  `|[a-zA-Z][a-zA-Z0-9]*[-a-zA-Z0-9]*(?:\\\\.[a-zA-Z0-9][-a-zA-Z0-9]*)*\\\\.[a-zA-Z]{2,}(?:[:/][-\\\\w;/?:@&=+$\\\\|\\\\_.!~*\\\\|'()\\\\[\\\\]%#,☺]*[\\\\w/#](\\\\(\\\\))?)?)` +\n  wordBoundary.end;\n\nexport const phoneNumPattern =\n  wordBoundary.start +\n  `(?:\\\\+?(\\\\d{1,3}))?([-. (]*(\\\\d{3})[-. )]*)?((\\\\d{3})[-. ]*(\\\\d{2,4})(?:[-.x ]*(\\\\d+))?)` +\n  wordBoundary.end;\n\nexport const MetadataConstants = {\n  file: \"file\",\n  extensions: {\n    thumbnailGeneration: \"thumbnail-generation\",\n    polls: \"polls\",\n    document: \"document\",\n    whiteboard: \"whiteboard\",\n    xssFilter: \"xss-filter\",\n    dataMasking: \"data-masking\",\n    profanityFilter: \"profanity-filter\",\n    reactions: \"reactions\",\n    linkPreview: \"link-preview\",\n  },\n};\n\nexport const GroupMemberOptionConstants = {\n  joined: \"joined\",\n  left: \"left\",\n  added: \"added\",\n  kick: \"kick\",\n  ban: \"ban\",\n  unban: \"unban\",\n  changeScope: \"changeScope\",\n  view: \"viewMember\",\n  deleteGroup: \"deleteGroup\",\n  leave: \"leave\",\n};\n\nexport const ConversationOptionConstants = {\n  delete: \"delete\",\n  edit: \"edit\",\n  reply: \"reply\",\n};\n\nexport const ConversationTypeConstants = {\n  user: \"user\",\n  group: \"group\",\n  both: \"both\",\n};\n\nexport const GroupTypeConstants = {\n  private: CometChat.GROUP_TYPE.PRIVATE,\n  password: CometChat.GROUP_TYPE.PASSWORD,\n  public: CometChat.GROUP_TYPE.PUBLIC,\n};\n\nexport const GroupMemberScope = {\n  admin: CometChat.GROUP_MEMBER_SCOPE.ADMIN,\n  moderator: CometChat.GROUP_MEMBER_SCOPE.MODERATOR,\n  participant: CometChat.GROUP_MEMBER_SCOPE.PARTICIPANT,\n  owner: \"owner\",\n};\n\nexport const GroupMemberOptionKick = \"kick\";\nexport const GroupMemberOptionBan = \"ban\";\nexport const GroupMemberOptionUnban = \"unban\";\nexport const GroupMemberOptionChangeScope: string[] = [\n  GroupMemberScope.admin,\n  GroupMemberScope.moderator,\n  GroupMemberScope.participant,\n];\n\nexport const ONE_SECOND_IN_MS = 1000;\n\nexport const PATTERN = [1 * ONE_SECOND_IN_MS, 2 * ONE_SECOND_IN_MS, 3 * ONE_SECOND_IN_MS];\n\nexport const CallContstatnts = {\n  audioCall: CometChat.CALL_TYPE.AUDIO,\n  videoCall: CometChat.CALL_TYPE.VIDEO,\n  ongoing: CometChat.CALL_STATUS.ONGOING,\n  missed: CometChat.CALL_STATUS.UNANSWERED,\n  ended: CometChat.CALL_STATUS.ENDED,\n  cancelled: CometChat.CALL_STATUS.CANCELLED,\n  busy: CometChat.CALL_STATUS.BUSY,\n  rejected: CometChat.CALL_STATUS.REJECTED,\n};\n\nexport const CallTypeConstants = {\n  audio: CometChat.CALL_TYPE.AUDIO,\n  video: CometChat.CALL_TYPE.VIDEO,\n};\n\nexport const MessageCategoryConstants = {\n  message: CometChat.CATEGORY_MESSAGE,\n  custom: CometChat.CATEGORY_CUSTOM,\n  action: CometChat.CATEGORY_ACTION,\n  call: CometChat.CATEGORY_CALL,\n  interactive: CometChat.CATEGORY_INTERACTIVE,\n  agentic: CometChat.CATEGORY_AGENTIC,\n  stream: \"stream_message\",\n};\n\nexport const DefaultActionSheetItems = {\n  takeAPhoto: \"takeAPhoto\",\n  photoAndVideoLibrary: \"photoAndVideoLibrary\",\n  document: \"photoAndVideoLibrary\",\n  poll: \"poll\",\n  sticker: \"sticker\",\n  collaborative_whiteboard: \"collaborative_whiteboard\",\n  collaborative_document: \"collaborative_document\",\n};\n\nexport const MessageTypeConstants = {\n  text: CometChat.MESSAGE_TYPE.TEXT,\n  file: CometChat.MESSAGE_TYPE.FILE,\n  image: CometChat.MESSAGE_TYPE.IMAGE,\n  takePhoto: \"takePhoto\",\n  audio: CometChat.MESSAGE_TYPE.AUDIO,\n  video: CometChat.MESSAGE_TYPE.VIDEO,\n  groupMember: CometChat.ACTION_TYPE.TYPE_GROUP_MEMBER,\n  messageEdited: CometChat.ACTION_TYPE.MESSAGE_EDITED,\n  messageDeleted: CometChat.ACTION_TYPE.MESSSAGE_DELETED,\n  poll: \"extension_poll\",\n  sticker: \"extension_sticker\",\n  document: \"extension_document\",\n  whiteboard: \"extension_whiteboard\",\n  meeting: \"meeting\",\n  scheduler: \"scheduler\",\n  location: \"location\",\n  groupActions: \"groupActions\",\n  form: \"form\",\n  card: \"card\",\n  customInteractive: \"customInteractive\",\n  assistant: CometChat.MESSAGE_TYPE.ASSISTANT,\n  toolArguments: CometChat.MESSAGE_TYPE.TOOL_ARGUMENTS,\n  toolResults: CometChat.MESSAGE_TYPE.TOOL_RESULT,\n};\n\nexport const streamMessageTypes = {\n  run_started: CometChat.AI_ASSISTANT_EVENTS.RUN_STARTED,\n  text_message_start: CometChat.AI_ASSISTANT_EVENTS.TEXT_MESSAGE_START,\n  text_message_content: CometChat.AI_ASSISTANT_EVENTS.TEXT_MESSAGE_CONTENT,\n  text_message_end: CometChat.AI_ASSISTANT_EVENTS.TEXT_MESSAGE_END,\n  run_finished: CometChat.AI_ASSISTANT_EVENTS.RUN_FINISHED,\n  tool_call_start: CometChat.AI_ASSISTANT_EVENTS.TOOL_CALL_STARTED,\n  tool_call_end: CometChat.AI_ASSISTANT_EVENTS.TOOL_CALL_ENDED,\n  tool_call_args: CometChat.AI_ASSISTANT_EVENTS.TOOL_CALL_ARGUMENT,\n  tool_call_result: CometChat.AI_ASSISTANT_EVENTS.TOOL_CALL_RESULT\n}\n\nexport const ReceiverTypeConstants = {\n  user: CometChat.RECEIVER_TYPE.USER,\n  group: CometChat.RECEIVER_TYPE.GROUP,\n};\n\nexport const UserStatusConstants = {\n  online: CometChat.USER_STATUS.ONLINE,\n  offline: CometChat.USER_STATUS.OFFLINE,\n  blocked: \"blocked\",\n  unblocked: \"unblocked\",\n};\n\nexport const MessageOptionConstants = {\n  editMessage: \"editMessage\",\n  deleteMessage: \"deleteMessage\",\n  replyMessage: \"replyMessage\",\n  replyInThread: \"replyInThread\",\n  translateMessage: \"translateMessage\",\n  reactToMessage: \"reactToMessage\",\n  messageInformation: \"messageInfo\",\n  copyMessage: \"copyMessage\",\n  shareMessage: \"shareMessage\",\n  // forwardMessage: \"forwardMessage\",\n  sendMessagePrivately: \"sendMessagePrivately\",\n  replyMessagePrivately: \"replyMessagePrivately\",\n  reportMessage: \"reportMessage\",\n  markAsUnread: \"markAsUnread\",\n};\n\nexport const CometChatMessageTypes = Object.freeze({\n  text: CometChat.MESSAGE_TYPE.TEXT,\n  file: CometChat.MESSAGE_TYPE.FILE,\n  image: CometChat.MESSAGE_TYPE.IMAGE,\n  audio: CometChat.MESSAGE_TYPE.AUDIO,\n  video: CometChat.MESSAGE_TYPE.VIDEO,\n});\n\nexport const CometChatCustomMessageTypes = Object.freeze({\n  poll: \"extension_poll\",\n  sticker: \"extension_sticker\",\n  document: \"extension_document\",\n  whiteboard: \"extension_whiteboard\",\n  meeting: \"meeting\",\n  location: \"location\",\n});\n\nexport const MessageOptionForConstants = {\n  sender: \"sender\",\n  receiver: \"receiver\",\n  both: \"both\",\n};\nexport const ViewAlignment = {\n  composerTop: \"composerTop\",\n  composerBottom: \"composerBottom\",\n  messageListTop: \"messageListTop\",\n  messageListBottom: \"messageListBottom\",\n};\n\nexport const MessageStatusConstants = Object.freeze({\n  inprogress: \"inprogress\",\n  success: \"success\",\n  error: \"error\",\n});\n\nexport const GroupsConstants = {\n  MESSAGE_: \"message_\",\n  GROUP_MEMBERS: \"members\",\n  GROUP_MEMBER: \"member\",\n\n  GROUP_: \"group_\",\n  ENTER_GROUP_NAME: \"Name\",\n  CREATING_MESSSAGE: \"Creating...\",\n  GROUP_PASSWORD_BLANK: \"Group password cannot be blank\",\n  PARTICIPANT: \"Participant\",\n  PUBLIC: \"Public\",\n  PRIVATE: \"Private\",\n\n  CREATE_GROUP: \"Create Group\",\n  GROUP_LIST_: \"grouplist_\",\n  GROUPS: \"Groups\",\n  GUID: \"guid\",\n  VIEW_MESSAGE_THREAD: \"viewMessageThread\",\n  CLOSE_THREAD_CLICKED: \"closeThreadClicked\",\n  CLOSE_FULL_SCREEN_IMAGE: \"closeFullScreenImage\",\n  VIEW_ACTUAL_IMAGE: \"viewActualImage\",\n  ACTION_TYPE_GROUPMEMBER: \"groupMember\",\n  EDIT: \"edit\",\n  DELETE: \"delete\",\n  MENU_CLICKED: \"menuClicked\",\n  PUBLIC_GROUP: \"public\",\n  PRIVATE_GROUP: \"private\",\n  PROTECTED_GROUP: \"protected\",\n  MEMBER_SCOPE_CHANGED: \"memberScopeChanged\",\n  MEMBERS_ADDED: \"membersAdded\",\n  MEMBER_UNBANNED: \"memberUnbanned\",\n  MEMBERS_UPDATED: \"membersUpdated\",\n  MEMBER_UPDATED: \"memberUpdated\",\n  GROUP_UPDATED: \"groupUpdated\",\n  LEFT_GROUP: \"leftGroup\",\n  DELETE_GROUP: \"groupDeleted\",\n  BREAKPOINT_MIN_WIDTH: \"320\",\n  BREAKPOINT_MAX_WIDTH: \"767\",\n  UID: \"uid\",\n  SEARCH: \"Search\",\n  GROUP_TO_UPDATE: \"groupToUpdate\",\n  SCOPE: \"scope\",\n  GROUP_TO_DELETE: \"groupToDelete\",\n  MEMBERS_COUNT: \"membersCount\",\n  NO_GROUPS_FOUND: \"No groups found\",\n  LOADING_MESSSAGE: \"Loading...\",\n  ERROR: \"error\",\n  GROUP_MEMBER_KICKED: \"onGroupMemberKicked\",\n  HAS_JOINED: \"hasJoined\",\n  CLOSE_CREATE_GROUP_VIEW: \"closeCreateGroupView\",\n  GROUP_CREATED: \"groupCreated\",\n  GROUP_MEMBER_SCOPE_CHANGED: \"onGroupMemberScopeChanged\",\n  GROUP_MEMBER_BANNED: \"onGroupMemberBanned\",\n  GROUP_MEMBER_UNBANNED: \"onGroupMemberUnbanned\",\n  GROUP_MEMBER_ADDED: \"onMemberAddedToGroup\",\n  GROUP_MEMBER_LEFT: \"onGroupMemberLeft\",\n  GROUP_MEMBER_JOINED: \"onGroupMemberJoined\",\n};\n\nexport enum ElementType {\n  label = \"label\",\n  text = \"textInput\",\n  dropdown = \"dropdown\",\n  checkbox = \"checkbox\",\n  dateTime = \"dateTime\",\n  radio = \"radio\",\n  button = \"button\",\n  singleSelect = \"singleSelect\",\n}\n\nexport enum ButtonAction {\n  apiAction = \"apiAction\",\n  urlNavigation = \"urlNavigation\",\n  custom = \"custom\",\n}\n\nexport enum HTTPSRequestMethods {\n  POST = \"POST\",\n  PUT = \"PUT\",\n  DELETE = \"DELETE\",\n  PATCH = \"PATCH\",\n}\n\nexport enum goalType {\n  allOf = CometChat.GoalType.ALL_OF,\n  anyOf = CometChat.GoalType.ANY_OF,\n  anyAction = CometChat.GoalType.ANY_ACTION,\n  none = CometChat.GoalType.NONE,\n}\n\nexport enum MessageReceipt {\n  SENT = \"SENT\",\n  DELIVERED = \"DELIVERED\",\n  READ = \"READ\",\n  ERROR = \"ERROR\",\n  WAIT = \"WAIT\",\n}\n\nexport enum MentionsType {\n  users,\n  usersAndGroupMembers,\n}\n\nexport enum MentionsVisibility {\n  both,\n  usersConversationOnly,\n  groupsConversationOnly,\n}\n\nconst FILE_EXTENSION = {\n  doc: \"document-file-type\",\n  docx: \"document-file-type\",\n  md: \"document-file-type\",\n  csv: \"spreadsheet-file-type\",\n  xls: \"spreadsheet-file-type\",\n  xlsx: \"spreadsheet-file-type\",\n  ods: \"spreadsheet-file-type\",\n  jpg: \"image-file-type\",\n  jpeg: \"image-file-type\",\n  png: \"image-file-type\",\n  gif: \"image-file-type\",\n  bmp: \"image-file-type\",\n  svg: \"image-file-type\",\n  webp: \"image-file-type\",\n  tiff: \"image-file-type\",\n  psd: \"image-file-type\",\n  mp3: \"audio-file-type\",\n  wav: \"audio-file-type\",\n  ogg: \"audio-file-type\",\n  flac: \"audio-file-type\",\n  aac: \"audio-file-type\",\n  wma: \"audio-file-type\",\n  aiff: \"audio-file-type\",\n  mp4: \"video-file-type\",\n  avi: \"video-file-type\",\n  mov: \"video-file-type\",\n  mkv: \"video-file-type\",\n  flv: \"video-file-type\",\n  wmv: \"video-file-type\",\n  webm: \"video-file-type\",\n  mpg: \"video-file-type\",\n  mpeg: \"video-file-type\",\n  \"3gp\": \"video-file-type\",\n  pdf: \"pdf-file-type\",\n  ps: \"pdf-file-type\",\n  zip: \"zip-file-type\",\n  rar: \"zip-file-type\",\n  \"7z\": \"zip-file-type\",\n  ppt: \"presentation-file-type\",\n  pptx: \"presentation-file-type\",\n  odp: \"presentation-file-type\",\n  key: \"presentation-file-type\",\n  txt: \"text-file-type\",\n  wps: \"text-file-type\",\n  rtf: \"text-file-type\",\n  odt: \"text-file-type\",\n  tex: \"text-file-type\",\n  log: \"text-file-type\",\n  unknown: \"unknown-file-type\",\n} as const;\n\ntype FileExtensionKey = keyof typeof FILE_EXTENSION;\n\nexport const getFileTypeIcon = (fileName: string) => {\n  const fileExtension: string | undefined = fileName.split(\".\").pop()?.toLowerCase();\n\n  if (!fileExtension) {\n    return \"unknown-file-type\";\n  }\n\n  type FileExtensionKey = keyof typeof FILE_EXTENSION;\n  return FILE_EXTENSION[fileExtension as FileExtensionKey] || \"unknown-file-type\";\n};\n\nexport enum MentionsTargetElement {\n  textinput,\n  textbubble,\n  conversation,\n}\n\n/**\n * Enum for Enter key behavior in the composer.\n * On iOS, Enter always inserts a new line regardless of this setting.\n * @platform Android\n */\nexport enum EnterKeyBehavior {\n  SendMessage = \"sendMessage\",\n  NewLine = \"newLine\",\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/CometChatMessageEvents/index.ts",
    "content": "export class CometChatMessageEvents {\n  static messageReceived = Symbol(\"messageReceived\");\n  static customMessageReceived = Symbol(\"customMessageReceived\");\n  static groupActionMessageReceived = Symbol(\"groupActionMessageReceived\");\n  static callActionMessageReceived = Symbol(\"callActionMessageReceived\");\n  static messageRead = Symbol(\"messageRead\");\n  static messageDelivered = Symbol(\"messageDelivered\");\n  static messageEdited = Symbol(\"messageEdited\");\n  static messageDeleted = Symbol(\"messageDeleted\");\n\n  static messagesFetched = Symbol(\"messagesFetched\");\n  static previousMessagesFetched = Symbol(\"previousMessagesFetched\");\n  static refreshingMessages = Symbol(\"refreshingMessages\");\n  static messagesRefreshed = Symbol(\"messagesRefreshed\");\n  static storeMessage = Symbol(\"storeMessage\");\n  static scrolledUp = Symbol(\"scrolledUp\");\n  static scrolledToBottom = Symbol(\"scrolledToBottom\");\n\n  static markMessageAsRead = Symbol(\"markMessageAsRead\");\n\n  static onMessageSent = Symbol(\"onMessageSent\");\n  static onMessageEdit = Symbol(\"onMessageEdit\");\n  static onMessageDelete = Symbol(\"onMessageDelete\");\n  static onMessageReply = Symbol(\"onMessageReply\");\n  static onMessageRead = Symbol(\"onMessageRead\");\n  static onMessageReaction = Symbol(\"onMessageReaction\");\n  static onViewInformation = Symbol(\"onViewInformation\");\n  static onMessageError = Symbol(\"onMessageError\");\n  static ccReplyToMessage = Symbol(\"ccReplyToMessage\");\n\n  static onMessageReactionError = Symbol(\"onMessageReactionError\");\n  static previewMessageForEdit = Symbol(\"previewMessageForEdit\");\n\n  static onBackButtonClick = Symbol(\"onBackButtonClick\");\n  static onAIAssistantMessageReceived = Symbol(\"onAIAssistantMessageReceived\");\n  static onAIToolResultReceived = Symbol(\"onAIToolResultReceived\");\n  static onAIToolArgumentsReceived = Symbol(\"onAIToolArgumentsReceived\");\n  static _triggers: { [event: string]: { [id: string]: (params: any) => void } } = {};\n\n  static emit = (...args: any[]) => {\n    let event, params;\n    if (args.length === 2) {\n      [event, params] = args;\n    } else if (args.length === 1 && typeof args[0] === \"object\") {\n      event = args[0].event;\n      params = args[0].params;\n    } else {\n      throw new Error(\"Invalid arguments\");\n    }\n\n    if (CometChatMessageEvents._triggers[event as string]) {\n      for (const i in CometChatMessageEvents._triggers[event as string]) {\n        CometChatMessageEvents._triggers[event as string][i](params);\n      }\n    }\n  };\n\n  /**\n   * @param {string} event\n   * @param {string} id\n   */\n  static removeListener = (...args: any[]) => {\n    let event, id;\n    if (args.length === 2) {\n      [event, id] = args;\n    } else if (args.length === 1 && typeof args[0] === \"object\") {\n      event = args[0].event;\n      id = args[0].id;\n    } else {\n      throw new Error(\"Invalid arguments\");\n    }\n\n    if (CometChatMessageEvents._triggers[event]) {\n      delete CometChatMessageEvents._triggers[event][id];\n    }\n  };\n\n  /**\n   * @param {string} event\n   * @param {string} id\n   * @param {func} callback\n   */\n  static addListener = (...args: any[]) => {\n    let event, id, callback;\n    if (args.length === 3) {\n      [event, id, callback] = args;\n    } else if (args.length === 1 && typeof args[0] === \"object\") {\n      event = args[0].event;\n      id = args[0].id;\n      callback = args[0].callback;\n    } else {\n      throw new Error(\"Invalid arguments\");\n    }\n\n    if (!CometChatMessageEvents._triggers[event]) {\n      CometChatMessageEvents._triggers[event] = {};\n    }\n\n    CometChatMessageEvents._triggers[event][id] = callback;\n  };\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/CometChatUIEventHandler/CometChatUIEventHandler.ts",
    "content": "import { SuggestionItem } from \"../../views\";\nimport {\n  CallListener,\n  CallUIEventListener,\n  ConversationListener,\n  ConversationUIEventListener,\n  GroupListener,\n  GroupUIEventListener,\n  MessageListener,\n  MessageUIEventListener,\n  PanelListener,\n  UserListener,\n  UserUIEventListener,\n  UIEventListener,\n  UIListener,\n} from \"./Listener\";\n\nexport class CometChatUIEventHandler {\n  private static userHandlers?: UserListener[] = [];\n  private static uiHandlers?: UIListener[] = [];\n  private static messageHandlers?: MessageListener[] = [];\n  private static conversationHandlers?: ConversationListener[] = [];\n  private static groupHandlers?: GroupListener[] = [];\n  private static callHandlers?: CallListener[] = [];\n  private static panelHandlers?: PanelListener[] = [];\n\n  constructor() {}\n\n  static emitPanelEvent(name: string, param: object) {\n    CometChatUIEventHandler.panelHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.ccHidePanel?.name:\n          listener._eventListener?.ccHidePanel?.(param);\n          break;\n        case listener._eventListener?.ccShowPanel?.name:\n          listener._eventListener?.ccShowPanel?.(param);\n          break;\n      }\n    });\n  }\n\n  static emitCallEvent(name: string, param: object) {\n    CometChatUIEventHandler.callHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.ccIncomingCallReceived?.name:\n          listener._eventListener?.ccIncomingCallReceived?.(param);\n          break;\n        case listener._eventListener?.ccOutgoingCallAccepted?.name:\n          listener._eventListener?.ccOutgoingCallAccepted?.(param);\n          break;\n        case listener._eventListener?.ccOutgoingCallRejected?.name:\n          listener._eventListener?.ccOutgoingCallRejected?.(param);\n          break;\n        case listener._eventListener?.ccIncomingCallCancelled?.name:\n          listener._eventListener?.ccIncomingCallCancelled?.(param);\n          break;\n        case listener._eventListener?.ccOutgoingCall?.name:\n          listener._eventListener?.ccOutgoingCall?.(param);\n          break;\n        case listener._eventListener?.ccCallAccepted?.name:\n          listener._eventListener?.ccCallAccepted?.(param);\n          break;\n        case listener._eventListener?.ccCallRejected?.name:\n          listener._eventListener?.ccCallRejected?.(param);\n          break;\n        case listener._eventListener?.ccCallEnded?.name:\n          listener._eventListener?.ccCallEnded?.(param);\n          break;\n        case listener._eventListener?.ccOutgoingCallCancelled?.name:\n          listener._eventListener?.ccOutgoingCallCancelled?.(param);\n          break;\n        case listener._eventListener?.ccCallInitiated?.name:\n          listener._eventListener?.ccCallInitiated?.(param);\n          break;\n        case listener._eventListener?.ccShowOngoingCall?.name:\n          listener._eventListener?.ccShowOngoingCall?.(param);\n          break;\n        case listener._eventListener?.ccCallFailed?.name:\n          listener._eventListener?.ccCallFailed?.(param);\n          break;\n      }\n    });\n  }\n\n  static addCallListener(name: string, callHandler: CallUIEventListener) {\n    try {\n      CometChatUIEventHandler.callHandlers = CometChatUIEventHandler.callHandlers?.filter(\n        (listener) => {\n          return listener._name != name;\n        }\n      );\n      CometChatUIEventHandler.callHandlers = [\n        ...(CometChatUIEventHandler.callHandlers ? CometChatUIEventHandler.callHandlers : []),\n        new CallListener(name, callHandler),\n      ];\n    } catch (err) {\n      console.log(\"addCallListener\", err);\n    }\n  }\n\n  static removeCallListener(name: string) {\n    try {\n      CometChatUIEventHandler.callHandlers = CometChatUIEventHandler.callHandlers?.filter(\n        (listener) => {\n          return listener._name !== name;\n        }\n      );\n    } catch (err) {\n      console.log(\"removeCallListener\", err);\n    }\n  }\n\n  static emitMessageEvent(name: string, param: object) {\n    CometChatUIEventHandler.messageHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.ccMessageDeleted?.name:\n          listener._eventListener?.ccMessageDeleted?.(param);\n          break;\n        case listener._eventListener?.ccMessageEdited?.name:\n          listener._eventListener?.ccMessageEdited?.(param);\n          break;\n        case listener._eventListener?.ccMessageRead?.name:\n          listener._eventListener?.ccMessageRead?.(param);\n          break;\n        case listener._eventListener?.ccMessageSent?.name:\n          listener._eventListener?.ccMessageSent?.(param);\n          break;\n        case listener._eventListener?.ccMessageDelivered?.name:\n          listener._eventListener?.ccMessageSent?.(param);\n          break;\n        case listener._eventListener?.ccActiveChatChanged?.name:\n          listener._eventListener?.ccActiveChatChanged?.(param);\n          break;\n        case listener._eventListener?.onTextMessageReceived?.name:\n          listener._eventListener?.onTextMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onMediaMessageReceived?.name:\n          listener._eventListener?.onMediaMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onCustomMessageReceived?.name:\n          listener._eventListener?.onCustomMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onTypingStarted?.name:\n          listener._eventListener?.onTypingStarted?.(param);\n          break;\n        case listener._eventListener?.onTypingEnded?.name:\n          listener._eventListener?.onTypingEnded?.(param);\n          break;\n        case listener._eventListener?.onMessagesDelivered?.name:\n          listener._eventListener?.onMessagesDelivered?.(param);\n          break;\n        case listener._eventListener?.onMessagesRead?.name:\n          listener._eventListener?.onMessagesRead?.(param);\n          break;\n        case listener._eventListener?.onMessageEdited?.name:\n          listener._eventListener?.onMessageEdited?.(param);\n          break;\n        case listener._eventListener?.onMessageDeleted?.name:\n          listener._eventListener?.onMessageDeleted?.(param);\n          break;\n        case listener._eventListener?.onTransientMessageReceived?.name:\n          listener._eventListener?.onTransientMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onFormMessageReceived?.name:\n          listener._eventListener?.onFormMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onCardMessageReceived?.name:\n          listener._eventListener?.onCardMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onSchedulerMessageReceived?.name:\n          listener._eventListener?.onSchedulerMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onCustomInteractiveMessageReceived?.name:\n          listener._eventListener?.onCustomInteractiveMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onInteractionGoalCompleted?.name:\n          listener._eventListener?.onInteractionGoalCompleted?.(param);\n          break;\n        case listener._eventListener?.onMessageReactionRemoved?.name:\n          listener._eventListener?.onMessageReactionRemoved?.(param);\n          break;\n        case listener._eventListener?.onMessageReactionAdded?.name:\n          listener._eventListener?.onMessageReactionAdded?.(param);\n          break;\n        case listener._eventListener?.onMessagesDeliveredToAll?.name:\n          listener._eventListener?.onMessagesDeliveredToAll?.(param);\n          break;\n        case listener._eventListener?.onMessagesReadByAll?.name:\n          listener._eventListener?.onMessagesReadByAll?.(param);\n          break;\n        case listener._eventListener?.onAIAssistantMessageReceived?.name:\n          listener._eventListener?.onAIAssistantMessageReceived?.(param);\n          break;\n        case listener._eventListener?.onMessageModerated?.name:\n          listener._eventListener?.onMessageModerated?.(param);\n          break;\n      }\n    });\n  }\n\n  static addMessageListener(name: string, messageHandler: MessageUIEventListener) {\n    try {\n      CometChatUIEventHandler.messageHandlers = CometChatUIEventHandler.messageHandlers?.filter(\n        (listener) => {\n          return listener._name != name;\n        }\n      );\n      CometChatUIEventHandler.messageHandlers = [\n        ...(CometChatUIEventHandler.messageHandlers ? CometChatUIEventHandler.messageHandlers : []),\n        new MessageListener(name, messageHandler),\n      ];\n    } catch (err) {\n      console.log(\"addMessageListener\", err);\n    }\n  }\n\n  static removeMessageListener(name: string) {\n    try {\n      CometChatUIEventHandler.messageHandlers = CometChatUIEventHandler.messageHandlers?.filter(\n        (listener) => {\n          return listener._name !== name;\n        }\n      );\n    } catch (err) {\n      console.log(\"removeMessageListener\", err);\n    }\n  }\n\n  static emitConversationEvent(name: string, param: object) {\n    CometChatUIEventHandler.conversationHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.ccConversationDeleted?.name:\n          listener._eventListener?.ccConversationDeleted?.(param);\n          break;\n          //added for markAsUnread \n        case listener._eventListener?.ccUpdateConversation?.name:\n          listener._eventListener?.ccUpdateConversation?.(param);\n          break;\n      }\n    });\n  }\n\n  static addConversationListener(name: string, conversationHandler: ConversationUIEventListener) {\n    try {\n      CometChatUIEventHandler.conversationHandlers =\n        CometChatUIEventHandler.conversationHandlers?.filter((listener) => {\n          return listener._name != name;\n        });\n      CometChatUIEventHandler.conversationHandlers = [\n        ...(CometChatUIEventHandler.conversationHandlers\n          ? CometChatUIEventHandler.conversationHandlers\n          : []),\n        new ConversationListener(name, conversationHandler),\n      ];\n    } catch (err) {\n      console.log(\"addConversationListener\", err);\n    }\n  }\n\n  static removeConversationListener(name: string) {\n    try {\n      CometChatUIEventHandler.conversationHandlers =\n        CometChatUIEventHandler.conversationHandlers?.filter((listener) => {\n          return listener._name !== name;\n        });\n    } catch (err) {\n      console.log(\"removeConversationListener\", err);\n    }\n  }\n\n  static emitGroupEvent(name: string, param: object) {\n    CometChatUIEventHandler.groupHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.ccGroupCreated?.name:\n          listener._eventListener?.ccGroupCreated?.(param);\n          break;\n        case listener._eventListener?.ccGroupDeleted?.name:\n          listener._eventListener?.ccGroupDeleted?.(param);\n          break;\n        case listener._eventListener?.ccGroupLeft?.name:\n          listener._eventListener?.ccGroupLeft?.(param);\n          break;\n        case listener._eventListener?.ccGroupMemberBanned?.name:\n          listener._eventListener?.ccGroupMemberBanned?.(param);\n          break;\n        case listener._eventListener?.ccGroupMemberJoined?.name:\n          listener._eventListener?.ccGroupMemberJoined?.(param);\n          break;\n        case listener._eventListener?.ccGroupMemberKicked?.name:\n          listener._eventListener?.ccGroupMemberKicked?.(param);\n          break;\n        case listener._eventListener?.ccGroupMemberScopeChanged?.name:\n          listener._eventListener?.ccGroupMemberScopeChanged?.(param);\n          break;\n        case listener._eventListener?.ccGroupMemberUnBanned?.name:\n          listener._eventListener?.ccGroupMemberUnBanned?.(param);\n          break;\n        case listener._eventListener?.ccOwnershipChanged?.name:\n          listener._eventListener?.ccOwnershipChanged?.(param);\n          break;\n        case listener._eventListener?.ccGroupMemberAdded?.name:\n          listener._eventListener?.ccGroupMemberAdded?.(param);\n          break;\n      }\n    });\n  }\n\n  static addGroupListener(name: string, groupHandler: GroupUIEventListener) {\n    try {\n      CometChatUIEventHandler.groupHandlers = CometChatUIEventHandler.groupHandlers?.filter(\n        (listener) => {\n          return listener._name != name;\n        }\n      );\n      CometChatUIEventHandler.groupHandlers = [\n        ...(CometChatUIEventHandler.groupHandlers ? CometChatUIEventHandler.groupHandlers : []),\n        new GroupListener(name, groupHandler),\n      ];\n    } catch (err) {\n      console.log(\"addGrouplistener\", err);\n    }\n  }\n\n  static removeGroupListener(name: string) {\n    try {\n      CometChatUIEventHandler.groupHandlers = CometChatUIEventHandler.groupHandlers?.filter(\n        (listener) => {\n          return listener._name !== name;\n        }\n      );\n    } catch (err) {\n      console.log(\"removeGroupListener\", err);\n    }\n  }\n\n  static emitUserEvent(name: string, param: object) {\n    CometChatUIEventHandler.userHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.ccUserBlocked?.name:\n          listener._eventListener?.ccUserBlocked?.(param);\n          break;\n        case listener._eventListener?.ccUserUnBlocked?.name:\n          listener._eventListener?.ccUserUnBlocked?.(param);\n          break;\n      }\n    });\n  }\n\n  static addUserListener(name: string, userHandler: UserUIEventListener) {\n    try {\n      CometChatUIEventHandler.userHandlers = CometChatUIEventHandler.userHandlers?.filter(\n        (listener) => {\n          return listener._name != name;\n        }\n      );\n      CometChatUIEventHandler.userHandlers = [\n        ...(CometChatUIEventHandler.userHandlers ? CometChatUIEventHandler.userHandlers : []),\n        new UserListener(name, userHandler),\n      ];\n    } catch (err) {\n      console.log(\"addUserListener\", err);\n    }\n  }\n\n  static removeUserListener(name: string) {\n    try {\n      CometChatUIEventHandler.userHandlers = CometChatUIEventHandler.userHandlers?.filter(\n        (listener) => {\n          return listener._name !== name;\n        }\n      );\n    } catch (err) {\n      console.log(\"removeUserListener\", err);\n    }\n  }\n\n  static emitUIEvent(name: string, param: object) {\n    CometChatUIEventHandler.uiHandlers?.map((listener) => {\n      switch (name) {\n        case listener._eventListener?.hidePanel?.name:\n          listener._eventListener?.hidePanel?.(param);\n          break;\n        case listener._eventListener?.showPanel?.name:\n          listener._eventListener?.showPanel?.(param);\n          break;\n        case listener._eventListener?.openChat?.name:\n          listener._eventListener?.openChat?.(param);\n          break;\n        case listener._eventListener?.ccToggleBottomSheet?.name:\n          listener._eventListener?.ccToggleBottomSheet?.(param);\n          break;\n        case listener._eventListener?.ccComposeMessage?.name:\n          listener._eventListener?.ccComposeMessage?.(param);\n          break;\n        // case listener._eventListener?.ccMentionClick?.name:\n        //     listener._eventListener?.ccMentionClick(param);\n        //     break;\n        case listener._eventListener?.ccSuggestionData?.name:\n          listener._eventListener?.ccSuggestionData?.(\n            param as { id: string; data: Array<SuggestionItem> }\n          );\n          break;\n      }\n    });\n  }\n\n  static addUIListener(name: string, uiHandlers: UIEventListener) {\n    try {\n      CometChatUIEventHandler.uiHandlers = CometChatUIEventHandler.uiHandlers?.filter(\n        (listener) => {\n          return listener._name != name;\n        }\n      );\n      CometChatUIEventHandler.uiHandlers = [\n        ...(CometChatUIEventHandler.uiHandlers ? CometChatUIEventHandler.uiHandlers : []),\n        new UIListener(name, uiHandlers),\n      ];\n    } catch (err) {\n      console.log(\"addUserListener\", err);\n    }\n  }\n\n  static removeUIListener(name: string) {\n    try {\n      CometChatUIEventHandler.userHandlers = CometChatUIEventHandler.userHandlers?.filter(\n        (listener) => {\n          return listener._name !== name;\n        }\n      );\n    } catch (err) {\n      console.log(\"removeUserListener\", err);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/CometChatUIEventHandler/Listener.ts",
    "content": "import { SuggestionItem } from \"../../views\";\n\nexport function isFalsy($false: any) {\n  if ($false != null) {\n    if (typeof $false == \"string\") $false = $false.trim();\n    if (typeof $false == \"object\" && Object.keys($false).length === 0) $false = undefined;\n  }\n  let falsyvalues: any = [\"\", 0, \"0\", false, null, \"null\", undefined, \"undefined\"];\n  return falsyvalues.includes($false);\n}\n\ntype MessageUIEvents = {\n  ccMessageSent?: Function;\n  ccMessageEdited?: Function;\n  ccMessageDeleted?: Function;\n  ccMessageRead?: Function;\n  ccMessageDelivered?: Function;\n  ccMessageError?: Function;\n  ccActiveChatChanged?: Function;\n  onTextMessageReceived?: Function;\n  onMediaMessageReceived?: Function;\n  onCustomMessageReceived?: Function;\n  onTypingStarted?: Function;\n  onTypingEnded?: Function;\n  onMessagesDelivered?: Function;\n  onMessagesRead?: Function;\n  onMessageEdited?: Function;\n  onMessageDeleted?: Function;\n  onTransientMessageReceived?: Function;\n  onFormMessageReceived?: Function;\n  onCardMessageReceived?: Function;\n  onSchedulerMessageReceived?: Function;\n  onCustomInteractiveMessageReceived?: Function;\n  onInteractionGoalCompleted?: Function;\n  onMessageReactionAdded?: Function;\n  onMessageReactionRemoved?: Function;\n  onMessagesDeliveredToAll?: Function;\n  onMessagesReadByAll?: Function;\n  onMessageModerated?: Function;\n  onAIAssistantMessageReceived?: Function;\n  onMessageReply?: Function;\n};\nexport class MessageUIEventListener {\n  ccMessageSent?: Function = undefined;\n  ccMessageEdited?: Function = undefined;\n  ccMessageDeleted?: Function = undefined;\n  ccMessageRead?: Function = undefined;\n  ccMessageDelivered?: Function = undefined;\n  ccMessageError?: Function = undefined;\n  ccActiveChatChanged?: Function = undefined;\n  onTextMessageReceived?: Function = undefined;\n  onMediaMessageReceived?: Function = undefined;\n  onCustomMessageReceived?: Function = undefined;\n  onTypingStarted?: Function = undefined;\n  onTypingEnded?: Function = undefined;\n  onMessagesDelivered?: Function = undefined;\n  onMessagesRead?: Function = undefined;\n  onMessageEdited?: Function = undefined;\n  onMessageDeleted?: Function = undefined;\n  onTransientMessageReceived?: Function = undefined;\n  onFormMessageReceived?: Function = undefined;\n  onCardMessageReceived?: Function = undefined;\n  onSchedulerMessageReceived?: Function = undefined;\n  onCustomInteractiveMessageReceived?: Function = undefined;\n  onInteractionGoalCompleted?: Function = undefined;\n  onMessageReactionAdded?: Function = undefined;\n  onMessageReactionRemoved?: Function = undefined;\n  onMessagesDeliveredToAll?: Function = undefined;\n  onMessagesReadByAll?: Function = undefined;\n  onMessageModerated?: Function = undefined;\n  onAIAssistantMessageReceived?: Function = undefined;\n  onMessageReply?: Function = undefined;\n  constructor({\n    ccMessageSent,\n    ccMessageEdited,\n    ccMessageDeleted,\n    ccMessageRead,\n    ccMessageDelivered,\n    ccMessageError,\n    ccActiveChatChanged,\n    onTextMessageReceived,\n    onMediaMessageReceived,\n    onCustomMessageReceived,\n    onTypingStarted,\n    onTypingEnded,\n    onMessagesDelivered,\n    onMessagesRead,\n    onMessageEdited,\n    onMessageDeleted,\n    onTransientMessageReceived,\n    onFormMessageReceived,\n    onCardMessageReceived,\n    onSchedulerMessageReceived,\n    onCustomInteractiveMessageReceived,\n    onInteractionGoalCompleted,\n    onMessageReactionAdded,\n    onMessageReactionRemoved,\n    onMessagesDeliveredToAll,\n    onMessagesReadByAll,\n    onMessageModerated,\n    onAIAssistantMessageReceived,\n    onMessageReply,\n  }: MessageUIEvents) {\n    if (!isFalsy(ccMessageError)) this.ccMessageError = ccMessageError;\n    if (!isFalsy(ccMessageDelivered)) this.ccMessageDelivered = ccMessageDelivered;\n    if (!isFalsy(ccMessageRead)) this.ccMessageRead = ccMessageRead;\n    if (!isFalsy(ccMessageSent)) this.ccMessageSent = ccMessageSent;\n    if (!isFalsy(ccMessageEdited)) this.ccMessageEdited = ccMessageEdited;\n    if (!isFalsy(ccMessageDeleted)) this.ccMessageDeleted = ccMessageDeleted;\n    if (!isFalsy(ccActiveChatChanged)) this.ccActiveChatChanged = ccActiveChatChanged;\n    if (!isFalsy(onTextMessageReceived)) this.onTextMessageReceived = onTextMessageReceived;\n    if (!isFalsy(onMediaMessageReceived)) this.onMediaMessageReceived = onMediaMessageReceived;\n    if (!isFalsy(onCustomMessageReceived)) this.onCustomMessageReceived = onCustomMessageReceived;\n    if (!isFalsy(onTypingStarted)) this.onTypingStarted = onTypingStarted;\n    if (!isFalsy(onTypingEnded)) this.onTypingEnded = onTypingEnded;\n    if (!isFalsy(onMessagesDelivered)) this.onMessagesDelivered = onMessagesDelivered;\n    if (!isFalsy(onMessagesRead)) this.onMessagesRead = onMessagesRead;\n    if (!isFalsy(onMessageEdited)) this.onMessageEdited = onMessageEdited;\n    if (!isFalsy(onMessageDeleted)) this.onMessageDeleted = onMessageDeleted;\n    if (!isFalsy(onMessageDeleted)) this.onMessageDeleted = onMessageDeleted;\n    if (!isFalsy(onTransientMessageReceived))\n      this.onTransientMessageReceived = onTransientMessageReceived;\n    if (!isFalsy(onFormMessageReceived)) this.onFormMessageReceived = onFormMessageReceived;\n    if (!isFalsy(onCardMessageReceived)) this.onCardMessageReceived = onCardMessageReceived;\n    if (!isFalsy(onSchedulerMessageReceived))\n      this.onSchedulerMessageReceived = onSchedulerMessageReceived;\n    if (!isFalsy(onCustomInteractiveMessageReceived))\n      this.onCustomInteractiveMessageReceived = onCustomInteractiveMessageReceived;\n    if (!isFalsy(onInteractionGoalCompleted))\n      this.onInteractionGoalCompleted = onInteractionGoalCompleted;\n    if (!isFalsy(onMessageReactionAdded)) this.onMessageReactionAdded = onMessageReactionAdded;\n    if (!isFalsy(onMessageReactionRemoved))\n      this.onMessageReactionRemoved = onMessageReactionRemoved;\n    if (!isFalsy(onMessagesDeliveredToAll))\n      this.onMessagesDeliveredToAll = onMessagesDeliveredToAll;\n    if (!isFalsy(onMessagesReadByAll)) this.onMessagesReadByAll = onMessagesReadByAll;\n    if (!isFalsy(onAIAssistantMessageReceived)) this.onAIAssistantMessageReceived = onAIAssistantMessageReceived;\n    if (!isFalsy(onMessageReply)) this.onMessageReply = onMessageReply;\n    if (!isFalsy(onMessageModerated)) this.onMessageModerated = onMessageModerated;\n  }\n}\n\ntype CallUIEvents = {\n  ccIncomingCallReceived?: Function;\n  ccOutgoingCallAccepted?: Function;\n  ccOutgoingCallRejected?: Function;\n  ccIncomingCallCancelled?: Function;\n  ccOutgoingCall?: Function;\n  ccCallAccepted?: Function;\n  ccCallRejected?: Function;\n  ccOutgoingCallCancelled?: Function;\n  ccCallEnded?: Function;\n  ccCallInitiated?: Function;\n  ccCallFailed?: Function;\n  ccShowOngoingCall?: (CometChatOngoingComponent: any) => void;\n};\n\ntype PanelUIEvents = {\n  ccShowPanel?: Function;\n  ccHidePanel?: Function;\n};\n\nexport class PanelUIEventListener {\n  ccShowPanel?: Function = undefined;\n  ccHidePanel?: Function = undefined;\n\n  constructor({ ccShowPanel, ccHidePanel }: PanelUIEvents) {\n    if (!isFalsy(ccShowPanel)) this.ccShowPanel = ccShowPanel;\n    if (!isFalsy(ccHidePanel)) this.ccHidePanel = ccHidePanel;\n  }\n}\nexport class CallUIEventListener {\n  ccIncomingCallReceived?: Function = undefined;\n  ccOutgoingCallAccepted?: Function = undefined;\n  ccOutgoingCallRejected?: Function = undefined;\n  ccIncomingCallCancelled?: Function = undefined;\n  ccOutgoingCall?: Function = undefined;\n  ccCallAccepted?: Function = undefined;\n  ccCallRejected?: Function = undefined;\n  ccCallEnded?: Function = undefined;\n  ccOutgoingCallCancelled?: Function = undefined;\n  ccCallFailed?: Function = undefined;\n  ccCallInitiated?: Function = undefined;\n  ccShowOngoingCall?: (CometChatOngoingComponent: any) => void;\n\n  constructor({\n    ccIncomingCallReceived,\n    ccOutgoingCallAccepted,\n    ccOutgoingCallRejected,\n    ccIncomingCallCancelled,\n    ccOutgoingCall,\n    ccCallAccepted,\n    ccCallRejected,\n    ccCallEnded,\n    ccOutgoingCallCancelled,\n    ccCallFailed,\n    ccCallInitiated,\n    ccShowOngoingCall,\n  }: CallUIEvents) {\n    if (!isFalsy(ccCallFailed)) this.ccCallFailed = ccCallFailed;\n    if (!isFalsy(ccCallInitiated)) this.ccCallInitiated = ccCallInitiated;\n    if (!isFalsy(ccOutgoingCall)) this.ccOutgoingCall = ccOutgoingCall;\n    if (!isFalsy(ccCallAccepted)) this.ccCallAccepted = ccCallAccepted;\n    if (!isFalsy(ccCallRejected)) this.ccCallRejected = ccCallRejected;\n    if (!isFalsy(ccCallEnded)) this.ccCallEnded = ccCallEnded;\n    if (!isFalsy(ccOutgoingCallCancelled)) this.ccOutgoingCallCancelled = ccOutgoingCallCancelled;\n    if (!isFalsy(ccIncomingCallReceived)) this.ccIncomingCallReceived = ccIncomingCallReceived;\n    if (!isFalsy(ccOutgoingCallAccepted)) this.ccOutgoingCallAccepted = ccOutgoingCallAccepted;\n    if (!isFalsy(ccOutgoingCallRejected)) this.ccOutgoingCallRejected = ccOutgoingCallRejected;\n    if (!isFalsy(ccIncomingCallCancelled)) this.ccIncomingCallCancelled = ccIncomingCallCancelled;\n    if (!isFalsy(ccShowOngoingCall)) this.ccShowOngoingCall = ccShowOngoingCall;\n  }\n}\n\ntype UserUIEvents = {\n  ccUserBlocked?: Function;\n  ccUserUnBlocked?: Function;\n};\n\nexport class UserUIEventListener {\n  ccUserBlocked?: Function;\n  ccUserUnBlocked?: Function;\n  constructor({ ccUserBlocked, ccUserUnBlocked }: UserUIEvents) {\n    if (!isFalsy(ccUserUnBlocked)) this.ccUserUnBlocked = ccUserUnBlocked;\n    if (!isFalsy(ccUserBlocked)) this.ccUserBlocked = ccUserBlocked;\n  }\n}\n\ntype UIEvents = {\n  showPanel?: (item: any) => void;\n  hidePanel?: (item: any) => void;\n  ccToggleBottomSheet?: (item: any) => void;\n  openChat?: (item: any) => void;\n  ccComposeMessage?: (item: any) => void;\n  // ccMentionClick?:(item)=>void,\n  ccSuggestionData?: (item: { id: string | number; data: Array<SuggestionItem> }) => void;\n};\nexport class UIEventListener {\n  showPanel?: (item: any) => void;\n  hidePanel?: (item: any) => void;\n  ccToggleBottomSheet?: (item: any) => void;\n  openChat?: (item: any) => void;\n  ccComposeMessage?: (item: any) => void;\n  // ccMentionClick?:(item)=>void;\n  ccSuggestionData?: (item: { id: string | number; data: Array<SuggestionItem> }) => void;\n  constructor({\n    showPanel,\n    hidePanel,\n    ccToggleBottomSheet,\n    openChat,\n    ccComposeMessage,\n    // ccMentionClick,\n    ccSuggestionData,\n  }: UIEvents) {\n    if (!isFalsy(hidePanel)) this.hidePanel = hidePanel;\n    if (!isFalsy(showPanel)) this.showPanel = showPanel;\n    if (!isFalsy(openChat)) this.openChat = openChat;\n    if (!isFalsy(ccToggleBottomSheet)) this.ccToggleBottomSheet = ccToggleBottomSheet;\n    if (!isFalsy(ccComposeMessage)) this.ccComposeMessage = ccComposeMessage;\n    // if (!isFalsy(ccMentionClick)) this.ccMentionClick = ccMentionClick;\n    if (!isFalsy(ccSuggestionData)) this.ccSuggestionData = ccSuggestionData;\n  }\n}\nexport class GroupUIEventListener {\n  ccGroupCreated?: Function;\n  ccGroupMemberKicked?: Function;\n  ccGroupLeft?: Function;\n  ccGroupMemberBanned?: Function;\n  ccGroupDeleted?: Function;\n  ccOwnershipChanged?: Function;\n  ccGroupMemberScopeChanged?: Function;\n  ccGroupMemberUnBanned?: Function;\n  ccGroupMemberJoined?: Function;\n  ccGroupMemberAdded?: Function;\n  ccGropuMemberleft?: Function;\n\n  constructor({\n    ccGroupCreated,\n    ccGroupMemberKicked,\n    ccGroupLeft,\n    ccGroupMemberBanned,\n    ccGroupDeleted,\n    ccOwnershipChanged,\n    ccGroupMemberScopeChanged,\n    ccGroupMemberUnBanned,\n    ccGroupMemberJoined,\n    ccGroupMemberAdded,\n    ccGropuMemberleft,\n  }: any) {\n    if (!isFalsy(ccGropuMemberleft)) this.ccGropuMemberleft = ccGropuMemberleft;\n\n    if (!isFalsy(ccGroupMemberAdded)) this.ccGroupMemberAdded = ccGroupMemberAdded;\n\n    if (!isFalsy(ccGroupMemberJoined)) this.ccGroupMemberJoined = ccGroupMemberJoined;\n\n    if (!isFalsy(ccGroupCreated)) this.ccGroupCreated = ccGroupCreated;\n\n    if (!isFalsy(ccGroupMemberKicked)) this.ccGroupMemberKicked = ccGroupMemberKicked;\n\n    if (!isFalsy(ccGroupMemberBanned)) this.ccGroupMemberBanned = ccGroupMemberBanned;\n\n    if (!isFalsy(ccGroupLeft)) this.ccGroupLeft = ccGroupLeft;\n\n    if (!isFalsy(ccGroupDeleted)) this.ccGroupDeleted = ccGroupDeleted;\n\n    if (!isFalsy(ccOwnershipChanged)) this.ccOwnershipChanged = ccOwnershipChanged;\n\n    if (!isFalsy(ccGroupMemberScopeChanged))\n      this.ccGroupMemberScopeChanged = ccGroupMemberScopeChanged;\n\n    if (!isFalsy(ccGroupMemberUnBanned)) this.ccGroupMemberUnBanned = ccGroupMemberUnBanned;\n  }\n}\n\nexport class UserCallUIEventListener {\n  ccYouLeft?: Function;\n  ccYouJoined?: Function;\n  ccUserJoined?: Function;\n  ccUserLeft?: Function;\n  ccUserListUpdated?: Function;\n  ccAudioModesUpdated?: Function;\n  ccCallEnded?: Function;\n  ccError?: Function;\n  constructor({\n    ccYouLeft,\n    ccYouJoined,\n    ccUserJoined,\n    ccUserLeft,\n    ccUserListUpdated,\n    ccAudioModesUpdated,\n    ccCallEnded,\n    ccError,\n  }: any) {\n    if (!isFalsy(ccYouLeft)) this.ccYouLeft = ccYouLeft;\n    if (!isFalsy(ccYouJoined)) this.ccYouJoined = ccYouJoined;\n    if (!isFalsy(ccUserJoined)) this.ccUserJoined = ccUserJoined;\n    if (!isFalsy(ccUserLeft)) this.ccUserLeft = ccUserLeft;\n    if (!isFalsy(ccUserListUpdated)) this.ccUserListUpdated = ccUserListUpdated;\n    if (!isFalsy(ccAudioModesUpdated)) this.ccAudioModesUpdated = ccAudioModesUpdated;\n    if (!isFalsy(ccCallEnded)) this.ccCallEnded = ccCallEnded;\n    if (!isFalsy(ccError)) this.ccError = ccError;\n  }\n}\n\ntype ConversationUIEvents = {\n  ccConversationDeleted?: Function;\n  ccUpdateConversation?: Function;\n};\nexport class ConversationUIEventListener {\n  ccConversationDeleted?: Function;\n  ccUpdateConversation?: Function;\n  constructor({ ccConversationDeleted, ccUpdateConversation }: ConversationUIEvents) {\n    if (!isFalsy(ccConversationDeleted)) this.ccConversationDeleted = ccConversationDeleted;\n    if (!isFalsy(ccUpdateConversation)) this.ccUpdateConversation = ccUpdateConversation;\n  }\n}\n\nexport interface EventListener {\n  _name: string;\n  _callback: Function;\n  _eventListener?:\n    | MessageUIEventListener\n    | UserUIEventListener\n    | UserCallUIEventListener\n    | CallUIEventListener\n    | GroupUIEventListener\n    | ConversationUIEventListener\n    | UIEventListener\n    | PanelUIEventListener;\n}\n\nexport class Listener implements EventListener {\n  _name: string;\n  _callback: Function;\n\n  constructor(name: string, callback: Function) {\n    this._name = name;\n    this._callback = callback;\n  }\n}\n\nexport class MessageListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: MessageUIEventListener | undefined;\n  constructor(\n    name: string,\n    messageEventListener?: MessageUIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = messageEventListener;\n    if (cursor) this._cursor = cursor;\n  }\n}\n\nexport class ConversationListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: ConversationUIEventListener | undefined;\n  constructor(\n    name: string,\n    userEventHandler?: ConversationUIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = userEventHandler;\n    if (cursor) this._cursor = cursor;\n  }\n}\nexport class UserListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: UserUIEventListener | undefined;\n  constructor(\n    name: string,\n    userEventHandler?: UserUIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = userEventHandler;\n    if (cursor) this._cursor = cursor;\n  }\n}\n\nexport class UIListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: UIEventListener | undefined;\n  constructor(\n    name: string,\n    uiEventHandler?: UIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = uiEventHandler;\n    if (cursor) this._cursor = cursor;\n  }\n}\n\nexport class GroupListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: GroupUIEventListener | undefined;\n  constructor(\n    name: string,\n    groupEventHandler?: GroupUIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = groupEventHandler;\n    if (cursor) this._cursor = cursor;\n  }\n}\n\nexport class UserCallListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: UserCallUIEventListener | undefined;\n  constructor(callEventHandler?: UserCallUIEventListener, cursor?: number, callback?: Function) {\n    super(\"callListner\", callback || function () {});\n    this._eventListener = callEventHandler;\n  }\n}\n\nexport class CallListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: CallUIEventListener | undefined;\n  constructor(\n    name: string,\n    callEventListner?: CallUIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = callEventListner;\n  }\n}\n\nexport class PanelListener extends Listener implements EventListener {\n  _cursor?: number;\n  _eventListener: PanelUIEventListener | undefined;\n  constructor(\n    name: string,\n    panelEventListner?: PanelUIEventListener,\n    cursor?: number,\n    callback?: Function\n  ) {\n    super(name, callback || function () {});\n    this._eventListener = panelEventListner;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/CometChatUIEvents.ts",
    "content": "export enum CometChatUIEvents {\n  ccUserBlocked = \"ccUserBlocked\",\n  ccUserUnBlocked = \"ccUserUnBlocked\",\n  ccGroupDeleted = \"ccGroupDeleted\",\n  ccGroupMemberJoined = \"ccGroupMemberJoined\",\n  ccGroupMemberKicked = \"ccGroupMemberKicked\",\n  ccGroupMemberBanned = \"ccGroupMemberBanned\",\n  ccGroupMemberLeft = \"ccGroupMemberLeft\",\n  ccGroupMemberAdded = \"ccGroupMemberAdded\",\n  ccOwnershipChanged = \"ccOwnershipChanged\",\n  ccGroupMemberUnBanned = \"ccGroupMemberUnBanned\",\n  ccGroupLeft = \"ccGroupLeft\",\n  showPanel = \"showPanel\",\n  hidePanel = \"hidePanel\",\n  openChat = \"openChat\",\n  ccToggleBottomSheet = \"ccToggleBottomSheet\",\n  ccComposeMessage = \"ccComposeMessage\",\n  // ccMentionClick = \"ccMentionClick\",\n  ccSuggestionData = \"ccSuggestionData\",\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/ListenerInitializer.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { MessageTypeConstants } from \"../constants/UIKitConstants\";\nimport { CometChatUIEventHandler } from \"./CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { MessageEvents } from \"./messages\";\nimport * as CometChatUIKitConstants from '../constants/UIKitConstants';\n\nexport class ListenerInitializer {\n  private static messageListenerId = `ListenerInitializer_listener`;\n    static streamListenerId: string = \"agent_\" + new Date().getTime();\n\n  public static attachListeners(user?: CometChat.User) {\n    CometChat.addMessageListener(this.messageListenerId, this.getMessageListenerObject());\n  }\n\n  public static detachListeners() {\n    CometChat.removeMessageListener(this.messageListenerId);\n  }\n\n  private static getMessageListenerObject() {\n    return new CometChat.MessageListener({\n      onTextMessageReceived: (textMessage: CometChat.TextMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onTextMessageReceived, textMessage);\n      },\n      onMediaMessageReceived: (mediaMessage: CometChat.MediaMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(\n          MessageEvents.onMediaMessageReceived,\n          mediaMessage\n        );\n      },\n      onCustomMessageReceived: (customMessage: CometChat.CustomMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(\n          MessageEvents.onCustomMessageReceived,\n          customMessage\n        );\n      },\n      onTypingStarted: (typingIndicator: CometChat.TypingIndicator) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onTypingStarted, typingIndicator);\n      },\n      onTypingEnded: (typingIndicator: CometChat.TypingIndicator) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onTypingEnded, typingIndicator);\n      },\n      onMessagesDelivered: (messageReceipt: CometChat.MessageReceipt) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessagesDelivered, messageReceipt);\n      },\n      onMessagesRead: (messageReceipt: CometChat.MessageReceipt) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessagesRead, messageReceipt);\n      },\n      onMessageEdited: (message: CometChat.BaseMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessageEdited, message);\n      },\n      onMessageDeleted: (message: CometChat.BaseMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessageDeleted, message);\n      },\n      onTransientMessageReceived: (message: CometChat.TransientMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onTransientMessageReceived, message);\n      },\n      onInteractiveMessageReceived: (message: CometChat.InteractiveMessage) => {\n        switch (message.getType()) {\n          case MessageTypeConstants.form:\n            CometChatUIEventHandler.emitMessageEvent(MessageEvents.onFormMessageReceived, message);\n            break;\n          case MessageTypeConstants.card:\n            CometChatUIEventHandler.emitMessageEvent(MessageEvents.onCardMessageReceived, message);\n            break;\n          case MessageTypeConstants.scheduler:\n            CometChatUIEventHandler.emitMessageEvent(\n              MessageEvents.onSchedulerMessageReceived,\n              message\n            );\n            break;\n          default:\n            CometChatUIEventHandler.emitMessageEvent(\n              MessageEvents.onCustomInteractiveMessageReceived,\n              message\n            );\n            break;\n        }\n      },\n      onInteractionGoalCompleted: (reciept: CometChat.InteractionReceipt) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onInteractionGoalCompleted, reciept);\n      },\n      onMessageReactionAdded: (message: CometChat.ReactionEvent) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessageReactionAdded, message);\n      },\n      onMessageReactionRemoved: (message: CometChat.ReactionEvent) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessageReactionRemoved, message);\n      },\n      onAIAssistantMessageReceived: (message: CometChat.AIAssistantMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onAIAssistantMessageReceived, message);\n      },\n      onMessageModerated: (message: CometChat.BaseMessage) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessageModerated, message);\n      },\n      onMessagesDeliveredToAll: (messageReceipt: CometChat.MessageReceipt) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessagesDeliveredToAll, messageReceipt);\n      },\n      onMessagesReadByAll: (messageReceipt: CometChat.MessageReceipt) => {\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.onMessagesReadByAll, messageReceipt);\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/conversations.ts",
    "content": "export enum CometChatConversationEvents {\n  ccConversationDeleted = \"ccConversationDeleted\",\n  ccUpdateConversation = \"ccUpdateConversation\",\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/groups.ts",
    "content": "export enum CometChatGroupsEvents {\n  ccGroupMemberKicked = \"ccGroupMemberKicked\",\n  ccGroupMemberBanned = \"ccGroupMemberBanned\",\n  ccGroupMemberUnBanned = \"ccGroupMemberUnBanned\",\n  ccGroupMemberScopeChanged = \"ccGroupMemberScopeChanged\",\n  ccGroupMemberJoined = \"ccGroupMemberJoined\",\n  ccGroupMemberAdded = \"ccGroupMemberAdded\",\n  ccGroupCreated = \"ccGroupCreated\",\n  ccGroupDeleted = \"ccGroupDeleted\",\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/index.ts",
    "content": "import { CometChatUIEventHandler } from \"./CometChatUIEventHandler/CometChatUIEventHandler\";\nimport { CometChatUIEvents } from \"./CometChatUIEvents\";\nimport { CometChatConversationEvents } from \"./conversations\";\nimport { CometChatGroupsEvents } from \"./groups\";\nimport { MessageEvents } from \"./messages\";\n\nexport {\n  CometChatConversationEvents,\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  MessageEvents,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/events/messages.ts",
    "content": "export enum MessageEvents {\n  ccMessageSent = \"ccMessageSent\",\n  ccMessageDelivered = \"ccMessageDelivered\",\n  ccMessageRead = \"ccMessageRead\",\n  ccMessageDeleted = \"ccMessageDeleted\",\n  ccMessageEdited = \"ccMessageEdited\",\n  ccMarkMessageAsRead = \"ccMarkMessageAsRead\",\n  ccMessageError = \"ccMessageError\",\n  ccActiveChatChanged = \"ccActiveChatChanged\",\n  onTextMessageReceived = \"onTextMessageReceived\",\n  onMediaMessageReceived = \"onMediaMessageReceived\",\n  onCustomMessageReceived = \"onCustomMessageReceived\",\n  onTypingStarted = \"onTypingStarted\",\n  onTypingEnded = \"onTypingEnded\",\n  onMessagesDelivered = \"onMessagesDelivered\",\n  onMessagesRead = \"onMessagesRead\",\n  onMessageEdited = \"onMessageEdited\",\n  onMessageDeleted = \"onMessageDeleted\",\n  onTransientMessageReceived = \"onTransientMessageReceived\",\n  onFormMessageReceived = \"onFormMessageReceived\",\n  onCardMessageReceived = \"onCardMessageReceived\",\n  onSchedulerMessageReceived = \"onSchedulerMessageReceived\",\n  onInteractionGoalCompleted = \"onInteractionGoalCompleted\",\n  onCustomInteractiveMessageReceived = \"onCustomInteractiveMessageReceived\",\n  onMessageReactionAdded = \"onMessageReactionAdded\",\n  onMessageReactionRemoved = \"onMessageReactionRemoved\",\n  onMessageModerated = \"onMessageModerated\",\n  onAIAssistantMessageReceived = \"onAIAssistantMessageReceived\",\n  onMessagesDeliveredToAll = \"onMessagesDeliveredToAll\",\n  onMessagesReadByAll = \"onMessagesReadByAll\",\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/CometChatMentionsFormatter/CometChatMentionsFormatter.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { ColorValue, StyleProp, Text, TextStyle, View } from \"react-native\";\nimport { CometChatUIKit } from \"../../CometChatUiKit\";\nimport {\n  MentionsTargetElement,\n  MentionsType,\n  MentionsVisibility,\n} from \"../../constants/UIKitConstants\";\nimport { CometChatUIEventHandler, CometChatUIEvents } from \"../../events\";\nimport { SuggestionItem } from \"../../views/CometChatSuggestionList\";\nimport { CometChatTextFormatter } from \"../CometChatTextFormatter\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { getCometChatTranslation } from \"../../resources/CometChatLocalizeNew/LocalizationManager\";\n\nconst t = getCometChatTranslation();\n\n/**\n * Represents the CometChatMentionsFormatter class.\n * This class extends the CometChatTextFormatter class and provides methods for handling mentions in text.\n * @extends CometChatTextFormatter\n */\n\ntype MentionsSubStyle = {\n  textStyle?: TextStyle;\n  selfTextStyle?: TextStyle;\n  backgroundColor?: ColorValue;\n  selfBackgroundColor?: ColorValue;\n};\n\ntype MentionContext = \"composer\" | \"conversation\" | \"incoming\" | \"outgoing\";\n\nfunction isCometChatTheme(t: unknown): t is CometChatTheme {\n  return (\n    !!t &&\n    typeof t === \"object\" &&\n    \"typography\" in t &&\n    \"color\" in t &&\n    typeof (t as any).typography === \"object\" &&\n    typeof (t as any).color === \"object\"\n  );\n}\n\nexport class CometChatMentionsFormatter extends CometChatTextFormatter {\n  /**\n   * List of users for mentions.\n   */\n  protected SuggestionItems: Array<SuggestionItem> = [];\n\n  /**\n   * List of searched data.\n   */\n  protected searchData: Array<SuggestionItem> = [];\n\n  /**\n   * Stores the formatting style for mentions.\n   */\n  protected mentionsStyle: CometChatTheme[\"mentionsStyle\"];\n\n  /**\n   * Default search request object to fetch users or group members.\n   */\n  private searchRequest?: CometChat.UsersRequest | CometChat.GroupMembersRequest;\n\n  /**\n   * Custom request object to fetch users or group members.\n   */\n  private customRequest?: CometChat.UsersRequestBuilder | CometChat.GroupMembersRequestBuilder;\n\n  private composerStyle: MentionsSubStyle;\n  private conversationStyle: MentionsSubStyle;\n  private incomingBubbleStyle: MentionsSubStyle;\n  private outgoingBubbleStyle: MentionsSubStyle;\n  private currentContext: MentionContext = \"incoming\";\n  private resolveStyle(isSelf: boolean): { textStyle: TextStyle; backgroundColor: ColorValue | undefined } {\n    let style: MentionsSubStyle;\n    switch (this.currentContext) {\n      case \"composer\":\n        style = this.composerStyle;\n        break;\n      case \"conversation\":\n        style = this.conversationStyle;\n        break;\n      case \"outgoing\":\n        style = this.outgoingBubbleStyle;\n        break;\n      default:\n        style = this.incomingBubbleStyle;\n    }\n    const textStyle = isSelf ? (style.selfTextStyle ?? {}) : (style.textStyle ?? {});\n    const backgroundColor = isSelf ? style.selfBackgroundColor : style.backgroundColor;\n    return { textStyle, backgroundColor };\n  }\n\n  /**\n   * Limit of unique users to be added in the composer.\n   */\n  protected limit: number = 10;\n\n  /**\n   * visibleIn property to determine where the mentions should be visible.\n   * @type {MentionsVisibility}\n   * @default MentionsVisibility.both\n   */\n  protected visibleIn: MentionsVisibility = MentionsVisibility.both;\n\n  /**\n   * type property to determine the type of mention list.\n   * @type {MentionsType}\n   * @default MentionsType.usersAndGroupMembers\n   */\n  protected type: MentionsType = MentionsType.usersAndGroupMembers;\n\n  /**\n   * type property to determine the type of mention list.\n   * @type {MentionsType}\n   * @default MentionsType.usersAndGroupMembers\n   */\n  protected target: MentionsTargetElement = MentionsTargetElement.textbubble;\n\n  protected textStyle: any = {};\n\n  /**\n   * Initializes a new CometChatMentionsFormatter.\n   * @param {CometChat.User} loggedInUser - The user who is currently logged in.\n   */\n  constructor(themeOrUser?: CometChatTheme | CometChat.User, loggedInUser?: CometChat.User) {\n    super();\n    // Support user mentions <@uid:UID> and group-wide alias mentions <@all:Alias>\n    this.regexPattern = /<@(?:uid|all):(.*?)>/g;\n    this.trackCharacter = \"@\";\n\n    const theme = isCometChatTheme(themeOrUser) ? themeOrUser : undefined;\n    this.loggedInUser = isCometChatTheme(themeOrUser)\n      ? loggedInUser\n      : (themeOrUser as CometChat.User | undefined);\n\n    // Incoming bubble style - purple/blue text with purple background\n    const incomingFallback: MentionsSubStyle = {\n      textStyle: { fontWeight: \"700\", fontSize: 14, lineHeight: 19.6, color: \"#6852D6\" },\n      selfTextStyle: { fontWeight: \"700\", fontSize: 14, lineHeight: 19.6, color: \"#FFAB00\" },\n      backgroundColor: \"rgba(104, 82, 214, 0.15)\", // Purple/blue matching text color\n      selfBackgroundColor: \"rgba(255, 171, 0, 0.2)\", // Yellow/warning for @all\n    };\n\n    // Outgoing bubble style - white text with white background\n    const outgoingFallback: MentionsSubStyle = {\n      textStyle: { fontWeight: \"700\", fontSize: 14, lineHeight: 19.6, color: \"#FFFFFF\" },\n      selfTextStyle: { fontWeight: \"700\", fontSize: 14, lineHeight: 19.6, color: \"#FFAB00\" },\n      backgroundColor: \"rgba(255, 255, 255, 0.2)\", // White matching text color\n      selfBackgroundColor: \"rgba(255, 171, 0, 0.2)\", // Yellow/warning for @all\n    };\n\n    const incomingFromTheme = (c: ColorValue): MentionsSubStyle => {\n      const baseStyle = theme?.typography?.body?.bold || {};\n      return {\n        textStyle: { ...baseStyle, color: c },\n        selfTextStyle: { ...baseStyle, color: theme?.color?.warning || \"#FFAB00\" },\n        backgroundColor: \"rgba(104, 82, 214, 0.15)\", // Purple/blue matching primary color\n        selfBackgroundColor: \"rgba(255, 171, 0, 0.2)\", // Yellow/warning for @all\n      };\n    };\n\n    const outgoingFromTheme = (c: ColorValue): MentionsSubStyle => {\n      const baseStyle = theme?.typography?.body?.bold || {};\n      return {\n        textStyle: { ...baseStyle, color: c },\n        selfTextStyle: { ...baseStyle, color: theme?.color?.warning || \"#FFAB00\" },\n        backgroundColor: \"rgba(255, 255, 255, 0.2)\", // White matching outgoing text color\n        selfBackgroundColor: \"rgba(255, 171, 0, 0.2)\", // Yellow/warning for @all\n      };\n    };\n\n    // Composer-specific style — bold text with subtle background pill (original behavior)\n    const composerFallback: MentionsSubStyle = {\n      textStyle: { fontWeight: \"700\", fontSize: 14, lineHeight: 19.6, color: \"#6852D6\" },\n      selfTextStyle: { fontWeight: \"700\", fontSize: 14, lineHeight: 19.6, color: \"#FFAB00\" },\n      backgroundColor: \"rgba(104, 82, 214, 0.15)\",\n      selfBackgroundColor: \"rgba(255, 171, 0, 0.2)\",\n    };\n\n    const composerFromTheme = (): MentionsSubStyle => {\n      const baseStyle = theme?.typography?.body?.bold || {};\n      const primaryColor = theme?.color?.primary || \"#6852D6\";\n      return {\n        textStyle: { ...baseStyle, color: primaryColor },\n        selfTextStyle: { ...baseStyle, color: theme?.color?.warning || \"#FFAB00\" },\n        backgroundColor: \"rgba(104, 82, 214, 0.15)\",\n        selfBackgroundColor: \"rgba(255, 171, 0, 0.2)\",\n      };\n    };\n\n    this.incomingBubbleStyle = theme ? incomingFromTheme(theme.color.receiveBubbleTextHighlight) : incomingFallback;\n    this.outgoingBubbleStyle = theme ? outgoingFromTheme(theme.color.sendBubbleTextHighlight) : outgoingFallback;\n    this.composerStyle = theme ? composerFromTheme() : composerFallback;\n    this.conversationStyle = this.incomingBubbleStyle;\n  }\n\n  /** Group-wide alias mention feature */\n  private mentionAllLabel: string = \"all\";\n  private disableMentionAll: boolean = false;\n\n  setMentionAllLabel(label: string) {\n    if (label && label.trim()) this.mentionAllLabel = label.trim();\n    return this;\n  }\n\n  setDisableMentionAll(disable: boolean) {\n    this.disableMentionAll = !!disable;\n    return this;\n  }\n\n  getMentionAllLabel() {\n    return this.mentionAllLabel;\n  }\n\n  isMentionAllDisabled() {\n    return this.disableMentionAll;\n  }\n\n  private buildGroupMentionItem(): SuggestionItem | null {\n    if (this.disableMentionAll) return null;\n    if (!this.group) return null; // only relevant inside a group context\n    const alias = this.mentionAllLabel || \"all\";\n    return new SuggestionItem({\n      id: alias, // id matches regex capture for highlighting\n      name: alias,\n      promptText: \"@\" + alias,\n      trackingCharacter: \"@\",\n      underlyingText: `<@all:${alias}>`,\n      hideLeadingIcon: false,\n    });\n  }\n\n  /**\n   * Sets the message object.\n   *\n   * @param {CometChat.BaseMessage} messageObject - The message object to be set.\n   */\n  setMessage(messageObject: CometChat.BaseMessage) {\n    this.messageObject = messageObject;\n\n    let mentionedUsers =\n      (messageObject?.getMentionedUsers && messageObject?.getMentionedUsers()) || [];\n    let cometchatUIUserArray: Array<SuggestionItem> =\n      this.convertCCUsersToSuggestionsItem(mentionedUsers);\n\n    // Inject alias mentions present in raw text (incoming/outgoing)\n    try {\n      const rawText = (messageObject as any)?.getText?.() || \"\";\n      const aliasRegex = /<@all:(.*?)>/g;\n      let m: RegExpExecArray | null;\n      while ((m = aliasRegex.exec(rawText)) !== null) {\n        const alias = m[1];\n        if (alias && !cometchatUIUserArray.find((i) => i.underlyingText === `<@all:${alias}>`)) {\n          cometchatUIUserArray.push(\n            new SuggestionItem({\n              id: alias,\n              name: alias,\n              promptText: \"@\" + alias,\n              trackingCharacter: \"@\",\n              underlyingText: `<@all:${alias}>`,\n              hideLeadingIcon: true,\n            })\n          );\n        }\n      }\n    } catch (e) {\n      console.log(\"🚀 ~ CometChatMentionsFormatter ~ setMessage ~ e:\", e);\n    }\n\n    this.setSuggestionItems(cometchatUIUserArray);\n  }\n\n  setTargetElement(target: MentionsTargetElement) {\n    this.target = target;\n  }\n\n  setTextStyle(textStyle: any) {\n    this.textStyle = textStyle;\n  }\n\n  handlePreMessageSend(message: CometChat.TextMessage): CometChat.TextMessage {\n    let CCUsers = this.getSuggestionItems()\n      .filter((item) => !item.underlyingText.startsWith(\"<@all:\"))\n      .map((item) => {\n        let user = new CometChat.User(item.id);\n        user.setAvatar(item?.leadingIconUrl!);\n        user.setName(item?.name!);\n        return user;\n      });\n    if (CCUsers.length) message.setMentionedUsers(CCUsers);\n    return message;\n  }\n\n  handleComposerPreview(message: CometChat.TextMessage): void {\n    const userItems = this.convertCCUsersToSuggestionsItem(message.getMentionedUsers());\n\n    // Preserve any existing @all alias items we discovered in setMessage\n    let aliasItems = this.SuggestionItems.filter((item) =>\n      item.underlyingText?.startsWith(\"<@all:\")\n    );\n\n    // If alias items are missing (common on re-open edit), parse from message text\n    try {\n      const rawText = message.getText?.() || \"\";\n      const aliasRegex = /<@all:(.*?)>/g;\n      let m: RegExpExecArray | null;\n      while ((m = aliasRegex.exec(rawText)) !== null) {\n        const alias = m[1];\n        if (\n          alias &&\n          !aliasItems.find((i) => i.underlyingText === `<@all:${alias}>`)\n        ) {\n          const aliasItem = new SuggestionItem({\n            id: alias,\n            name: alias,\n            promptText: \"@\" + alias,\n            trackingCharacter: \"@\",\n            underlyingText: `<@all:${alias}>`,\n            hideLeadingIcon: true,\n          });\n          aliasItems.push(aliasItem);\n        }\n      }\n    } catch (e) {\n      console.log(\"🚀 ~ CometChatMentionsFormatter ~ handleComposerPreview ~ e:\", e)\n    }\n\n    const merged = [...aliasItems, ...userItems];\n    this.setSuggestionItems(merged);\n  }\n\n  private convertCCUsersToSuggestionsItem(users: CometChat.User[]) {\n    return users.map((item: CometChat.User) => {\n      return new SuggestionItem({\n        id: item?.getUid(),\n        name: item?.getName(),\n        promptText: \"@\" + item?.getName(),\n        trackingCharacter: \"@\",\n        underlyingText: `<@uid:${item?.getUid()}>`,\n        leadingIconUrl: item?.getAvatar(),\n        hideLeadingIcon: false,\n      });\n    });\n  }\n\n  /**\n   * Sets the search request builder.\n   * @param requestBuilder - The request builder to set.\n   */\n  setSearchRequestBuilder(\n    requestBuilder: CometChat.UsersRequestBuilder | CometChat.GroupMembersRequestBuilder\n  ) {\n    this.customRequest = requestBuilder;\n  }\n\n  private shouldLoadLocalData(searchKey?: string) {\n    if (this.getUniqueUsersList().size >= this.limit) {\n      let data = searchKey\n        ? [...this.SuggestionItems].filter(\n            (item) =>\n              item.name?.toLowerCase().includes(searchKey.trim().toLowerCase()) ||\n              item.id.toLowerCase().includes(searchKey.trim().toLowerCase())\n          )\n        : [...this.SuggestionItems];\n      this.searchData = [...data];\n      this.setSearchData(this.searchData);\n      return true;\n    }\n    return false;\n  }\n\n  search(searchKey: string): void {\n    if (this.shouldLoadLocalData(searchKey)) return;\n    let requestBuilder =\n      this.customRequest ||\n      (this.group && this.type === MentionsType.usersAndGroupMembers\n        ? new CometChat.GroupMembersRequestBuilder(this.group.getGuid())\n        : new CometChat.UsersRequestBuilder());\n\n    this.searchRequest = requestBuilder.setLimit(10).setSearchKeyword(searchKey).build();\n    this.searchData = [];\n    // Prepend group-wide alias suggestion if enabled and matches search\n    const groupItem = this.buildGroupMentionItem();\n    if (groupItem) {\n      const include =\n        !searchKey || groupItem.name?.toLowerCase().startsWith(searchKey.toLowerCase());\n      if (include) this.searchData.push(groupItem);\n    }\n    this.fetchNext(true);\n  }\n\n  fetchNext(freshCall?: boolean): void | null {\n    if (this.getUniqueUsersList().size >= this.limit) return;\n    this.searchRequest?.fetchNext &&\n      this.searchRequest\n        ?.fetchNext()\n        .then((users: CometChat.User[]) => {\n          let structuredData = this.convertCCUsersToSuggestionsItem(users);\n          // Deduplicate: only add items whose id is not already present\n          const existingIds = new Set(this.searchData.map((item) => item.id));\n          const uniqueNewItems = structuredData.filter(\n            (item) => !existingIds.has(item.id)\n          );\n          this.searchData = [...this.searchData, ...uniqueNewItems];\n          this.setSearchData(this.searchData);\n        })\n        .catch((err) => {\n          console.log(\"searchRequest fetchNext failed:\", err);\n          this.setSearchData(this.searchData);\n        });\n  }\n\n  setSearchData(data: Array<SuggestionItem>) {\n    this.searchData = [...data];\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.ccSuggestionData, {\n      id: this.composerId,\n      data: [...this.searchData],\n    });\n  }\n\n  /**\n   * Sets the limit of unique users to be added in the composer.\n   */\n  setLimit(limit: number) {\n    this.limit = limit;\n  }\n\n  /**\n   * Retrieves the limit of unique users to be added in the composer.\n   */\n  getLimit() {\n    return this.limit;\n  }\n\n  /**\n   * Retrieves the unique users list.\n   */\n  getUniqueUsersList(): Set<number | string> {\n    // A Set to store unique user IDs\n    const uniqueUserIds: Set<number | string> = new Set();\n\n    // Populate the Set with user IDs from the existing user list\n    this.SuggestionItems.forEach((item) => {\n      if (!item.underlyingText.startsWith(\"<@all:\")) uniqueUserIds.add(item.id);\n    });\n\n    return uniqueUserIds;\n  }\n\n  /**\n   * Retrieves the message object.\n   *\n   * @returns {CometChat.BaseMessage} - The current message object.\n   */\n  getMessage(): CometChat.BaseMessage {\n    return this.messageObject;\n  }\n\n  /**\n   * Sets the regex pattern for matching text.\n   *\n   * @param {<RegExp>} regexPattern - Regex patterns.\n   */\n  setRegexPattern = (regexPattern: RegExp) => {\n    this.regexPattern = regexPattern;\n  };\n\n  /**\n   * Gets the regex pattern for matching text.\n   */\n  getRegexPattern = () => {\n    return this.regexPattern;\n  };\n\n  /**\n   * Retrieves the SuggestionItems.\n   *\n   * @returns {Array<SuggestionItem>} - The current SuggestionItems.\n   */\n  getSuggestionItems(): Array<SuggestionItem> {\n    return this.SuggestionItems;\n  }\n\n  /**\n   * Sets the SuggestionItems.\n   *\n   * @param {Array<SuggestionItem>} SuggestionItems - The SuggestionItems to be set.\n   */\n  setSuggestionItems(SuggestionItems: Array<SuggestionItem>) {\n    this.SuggestionItems = [...SuggestionItems];\n  }\n\n  /**\n   * Retrieves the mentions style.\n   *\n   * @returns {CometChatTheme[\"mentionsStyle\"]} - The current mentions style.\n   */\n  getMentionsStyle(): CometChatTheme[\"mentionsStyle\"] {\n    return this.mentionsStyle;\n  }\n\n  setComposerMentionStyle(s: MentionsSubStyle) {\n    this.composerStyle = deepMerge(this.composerStyle, s);\n    return this;\n  }\n  setConversationMentionStyle(s: MentionsSubStyle) {\n    this.conversationStyle = deepMerge(this.conversationStyle, s);\n    return this;\n  }\n  setIncomingBubbleMentionStyle(s: MentionsSubStyle) {\n    this.incomingBubbleStyle = deepMerge(this.incomingBubbleStyle, s);\n    return this;\n  }\n  setOutgoingBubbleMentionStyle(s: MentionsSubStyle) {\n    this.outgoingBubbleStyle = deepMerge(this.outgoingBubbleStyle, s);\n    return this;\n  }\n\n  /** maintain back-compat */\n  setMentionsStyle(patch?: CometChatTheme[\"mentionsStyle\"]) {\n    if (!patch) return this;\n    const apply = (dst: MentionsSubStyle) => deepMerge(dst, patch);\n    this.setIncomingBubbleMentionStyle(apply(this.incomingBubbleStyle))\n      .setOutgoingBubbleMentionStyle(apply(this.outgoingBubbleStyle))\n      .setComposerMentionStyle(apply(this.composerStyle))\n      .setConversationMentionStyle(apply(this.conversationStyle));\n    return this;\n  }\n\n  /** called from data-sources */\n  setContext(ctx: MentionContext) {\n    this.currentContext = ctx;\n    return this;\n  }\n\n  getFormattedText(\n    inputText: string | null | JSX.Element,\n    textStyle?: StyleProp<TextStyle>\n  ): string | JSX.Element {\n    if (!inputText) {\n      return \"\";\n    }\n    let formattedText = this.addMentionsView(inputText, textStyle);\n    return formattedText;\n  }\n\n  temp!: Function;\n\n  setOnMentionClick(callBack: (message: CometChat.BaseMessage, uid: string) => void) {\n    // callBack(this.messageObject, \"uid\");\n    this.temp = callBack;\n  }\n\n  /**\n   * Emits the event for mention click.\n   * @param {any} event - The event object.\n   * @param {string} uid - The user id.\n   */\n  private onMentionClick = (event: any, uid: string) => {\n    if (this.temp) {\n      this.temp(this.messageObject, uid);\n      return;\n    }\n  };\n\n  /**\n   * This function adds the mention view to the input text.\n   *\n   * @param {string} inputText - The input text where the view needs to be added.\n   * @returns {string} - The modified input text.\n   */\n  protected addMentionsView(\n    inputText: string | JSX.Element,\n    textStyle: StyleProp<TextStyle> = {}\n  ): any {\n    if (typeof inputText === \"string\") {\n      let mentions: JSX.Element[] = [];\n\n      if (this.SuggestionItems) {\n        const userRegistry: { [key: string]: string } = {};\n        const aliasIdSet: Set<string> = new Set();\n        for (let i = 0; i < this.SuggestionItems?.length; i++) {\n          const item = this.SuggestionItems[i];\n          const userUid = item.id;\n          const userName = item.promptText;\n          userRegistry[userUid] = userName!;\n          if (item.underlyingText.startsWith(\"<@all:\")) aliasIdSet.add(userUid);\n        }\n\n        // Define the regex pattern\n        const regex = this.getRegexPattern();\n\n        // Break the string into segments split by the regex\n        let match;\n        let lastIndex = 0;\n        let segments: any[] = [];\n\n        while ((match = regex.exec(inputText)) !== null) {\n          // Add preceding non-UID segment, if any\n          if (match.index > lastIndex) {\n            segments.push(inputText.slice(lastIndex, match.index));\n          }\n\n          // Add UID segment\n          segments.push(match[1]);\n\n          // Update lastIndex\n          lastIndex = match.index + match[0].length;\n        }\n\n        // Append trailing non-UID segment, if any\n        if (lastIndex < inputText.length) {\n          segments.push(inputText.slice(lastIndex));\n        }\n\n        // Now create an array of JSX elements from the segments\n        const elements = segments.map((segment, index) => {\n          // Check if segment is a UID\n          if (userRegistry.hasOwnProperty(segment)) {\n            let _loggedInUser = this.loggedInUser || CometChatUIKit.loggedInUser;\n\n            const isAlias = aliasIdSet.has(segment);\n            const isSelf = isAlias || _loggedInUser?.getUid() === segment;\n\n            const isOutgoing =\n              this.messageObject?.getSender()?.getUid() === _loggedInUser?.getUid();\n\n            if (this.target === MentionsTargetElement.textbubble) {\n              this.setContext(isOutgoing ? \"outgoing\" : \"incoming\");\n            }\n\n            const { textStyle, backgroundColor } = this.resolveStyle(isSelf);\n\n            let onPressProp =\n              this.temp && !isAlias\n                ? { onPress: (event: any) => this.onMentionClick(event, segment) }\n                : {}; // still suppress click for alias\n\n            const mentionContainerStyle = backgroundColor ? {\n              backgroundColor,\n              borderRadius: 4,\n              paddingHorizontal: 2,\n            } : {};\n\n            if (this.target === MentionsTargetElement.textbubble) {\n              return (\n                <Text suppressHighlighting={true} key={index} {...onPressProp} style={[textStyle, mentionContainerStyle]}>\n                  {userRegistry[segment]}\n                </Text>\n              );\n            }\n\n            return (\n              <Text suppressHighlighting={true} key={index} {...onPressProp} style={[textStyle, mentionContainerStyle]}>\n                {userRegistry[segment]}\n              </Text>\n            );\n          } else {\n            // Handle group-wide alias formatting if present\n            const aliasItem = this.SuggestionItems.find(\n              (it) => it.underlyingText === `<@all:${segment}>`\n            );\n            if (aliasItem) {\n              const { textStyle: styleResolved, backgroundColor } = this.resolveStyle(true);\n              const mentionContainerStyle = backgroundColor ? {\n                backgroundColor,\n                borderRadius: 4,\n                paddingHorizontal: 2,\n              } : {};\n              // If context is 'conversation', render @all and description inline\n              if (this.currentContext === \"conversation\") {\n                return (\n                  <Text suppressHighlighting={true} key={index} style={[styleResolved, mentionContainerStyle]}>\n                    <Text style={[styleResolved, { fontWeight: \"700\" }]}>\n                      @{aliasItem.promptText}\n                    </Text>\n                    <Text style={[styleResolved, { fontWeight: \"normal\", opacity: 0.7 }]}>\n                      {\" \"}\n                      {t(\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\")}\n                    </Text>\n                  </Text>\n                );\n              }\n              return (\n                <Text suppressHighlighting={true} key={index} style={[styleResolved, mentionContainerStyle]}>\n                  {aliasItem.promptText}\n                </Text>\n              );\n            }\n            if (this.target === MentionsTargetElement.textbubble)\n              return (\n                <Text key={index} style={{ ...this.textStyle }}>\n                  {segment}\n                </Text>\n              );\n            return (\n              <Text key={index} style={{ ...this.textStyle }}>\n                {segment}\n              </Text>\n            );\n          }\n        });\n\n        if (elements.length > 0)\n          return <Text style={textStyle}>{elements.map((item) => item)}</Text>;\n      }\n      return inputText;\n    } else if (React.isValidElement(inputText)) {\n      // inputText is a React element\n      if ((inputText as React.ReactElement<any>).props.children) {\n        // If the React element have children, we map over these children\n        // and call addMentionsView recursively for each child.\n        return React.cloneElement(inputText as React.ReactElement<any>, {\n          children: React.Children.map(\n            (inputText as React.ReactElement<any>).props.children,\n            (child) => {\n              return this.addMentionsView(child);\n            }\n          ),\n        });\n      } else {\n        // If the React element does not have children, return it as is\n        return inputText;\n      }\n    } else {\n      throw new Error(`Unsupported inputText type: ${typeof inputText}`);\n    }\n  }\n\n  /**\n   * Sets the type of mention list.\n   * @param type - The type of mention list.\n   */\n  setType(type: MentionsType) {\n    this.type = type;\n  }\n\n  /**\n   * Sets the visibleIn property to determine where the mentions should be visible.\n   * @param visibleIn - The visibleIn property to set.\n   */\n  setVisibleIn(visibleIn: MentionsVisibility) {\n    this.visibleIn = visibleIn;\n  }\n\n  /**\n   * Retrieves the visibleIn property to determine where the mentions should be visible.\n   */\n  getVisibleIn() {\n    return this.visibleIn;\n  }\n\n  /**\n   * Retrieves the type of mention list.\n   */\n  getType() {\n    return this.type;\n  }\n\n  getErrorString() {\n    return `${t(\"MENTION_UPTO\")} ${this.limit} ${\n      this.limit === 1 ? t(\"TIME\") : t(\"TIMES\")\n    } ${t(\"AT_A_TIME\")}.`;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/CometChatMentionsFormatter/MentionTextStyle.ts",
    "content": "import { StyleProp, TextStyle } from \"react-native\";\n/**\n * MentionTextStyle\n * Stores the styles for user mention.\n * @param textStyle: TextStyle\n * @param loggedInUserTextStyle: TextStyle\n */\nexport class MentionTextStyle {\n  textStyle?: StyleProp<TextStyle>;\n  loggedInUserTextStyle?: StyleProp<TextStyle>;\n  constructor(props: MentionTextStyle) {\n    Object.assign(this, props);\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/CometChatMentionsFormatter/index.ts",
    "content": "import { CometChatMentionsFormatter } from \"./CometChatMentionsFormatter\";\nimport { MentionTextStyle } from \"./MentionTextStyle\";\n\nexport { CometChatMentionsFormatter, MentionTextStyle };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/CometChatRichTextFormatter/index.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { Text, TextStyle, ViewStyle, Platform, Linking, View } from \"react-native\";\nimport { CometChatTextFormatter } from \"../CometChatTextFormatter\";\n\n/**\n * Style configuration for rich text formatting\n */\nexport interface RichTextStyle {\n  boldStyle?: TextStyle;\n  italicStyle?: TextStyle;\n  underlineStyle?: TextStyle;\n  strikethroughStyle?: TextStyle;\n  inlineCodeStyle?: TextStyle;\n  inlineCodeContainerStyle?: TextStyle;\n  codeBlockStyle?: TextStyle;\n  codeBlockContainerStyle?: ViewStyle;\n  blockquoteContainerStyle?: ViewStyle;\n  blockquoteBarStyle?: ViewStyle;\n  bulletListStyle?: TextStyle;\n  orderedListStyle?: TextStyle;\n  linkStyle?: TextStyle;\n}\n\n// Pre-compiled regex patterns — avoids recompilation on every call\nconst ORDERED_LIST_REGEX = /^(\\d+)\\.(?:\\s(.*))?$/;\nconst ORDERED_LIST_DETECT_REGEX = /^\\d+\\.(\\s|$)/;\nconst LINK_REGEX = /\\[([^\\]]+)\\]\\(([^)]+)\\)/;\nconst URL_PROTOCOL_REGEX = /^(https?|mailto|tel):/i;\n// Patterns for detecting list items inside blockquote content (ENG-31998)\nconst QUOTE_BULLET_REGEX = /^- (.*)$/;\nconst QUOTE_ORDERED_REGEX = /^(\\s*)(\\d+)\\.\\s(.*)$/;\n// Mention pattern — used to exclude mention UIDs from markdown marker detection\nconst MENTION_PATTERN_REGEX = /<@(?:uid|all):[^>]*>/g;\n\n// Pre-allocated style objects — avoids creating new objects on every render\nconst LIST_ROW_STYLE = { flexDirection: 'row' as const, flexShrink: 1 as const };\nconst BULLET_MARKER_STYLE = { width: 18 };\nconst ORDERED_MARKER_STYLE = { width: 24 };\nconst LIST_CONTENT_STYLE = { flexShrink: 1, flexGrow: 1 };\n\n/** Converts a number to lowercase alpha (1→a, 2→b, ..., 26→z, 27→aa) */\nfunction toAlpha(n: number): string {\n  let result = '';\n  while (n > 0) {\n    n--;\n    result = String.fromCharCode(97 + (n % 26)) + result;\n    n = Math.floor(n / 26);\n  }\n  return result;\n}\n\n/** Converts a number to lowercase roman numerals */\nfunction toRoman(n: number): string {\n  const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];\n  const syms = ['m', 'cm', 'd', 'cd', 'c', 'xc', 'l', 'xl', 'x', 'ix', 'v', 'iv', 'i'];\n  let result = '';\n  for (let i = 0; i < vals.length; i++) {\n    while (n >= vals[i]) {\n      result += syms[i];\n      n -= vals[i];\n    }\n  }\n  return result;\n}\n\n/** Formats a list counter based on nesting level (0→decimal, 1→alpha, 2+→roman) */\nfunction formatListMarker(count: number, level: number): string {\n  if (level === 0) return `${count}. `;\n  if (level === 1) return `${toAlpha(count)}. `;\n  return `${toRoman(count)}. `;\n}\n\n/** Indentation width per nesting level for nested lists */\nconst NESTED_LIST_INDENT = 20;\n\n/** Detect indentation level from leading spaces (4 spaces = 1 level) */\nfunction getIndentLevel(line: string): number {\n  const match = line.match(/^(\\s*)/);\n  if (!match) return 0;\n  return Math.floor(match[1].length / 4);\n}\n\nconst BQ_TEXT_ROW_STYLE = { flexDirection: 'row' as const, flexShrink: 1 as const };\n\n// Blockquote container styles — matches Figma spec (node 14736:1573987)\n// Rounded container with semi-transparent white bg, thick left bar, content area.\n// The bubble itself sizes to the text content; the blockquote fills the bubble width.\nconst BQ_CONTAINER_STYLE = {\n  flexDirection: 'row' as const,\n  alignItems: 'stretch' as const,\n  backgroundColor: 'rgba(255,255,255,0.2)',\n  borderRadius: 8,\n  overflow: 'hidden' as const,\n};\n// Thick rounded vertical bar on the left — Figma \"Border\" element (node 14736:1573988)\nconst BQ_BAR_STYLE = {\n  width: 4,\n  backgroundColor: 'rgba(255,255,255,0.6)',\n  borderRadius: 2,\n  marginVertical: 4,\n  marginLeft: 4,\n};\nconst BQ_CONTENT_STYLE = {\n  flexShrink: 1 as const,\n  paddingHorizontal: 8,\n  paddingVertical: 4,\n};\n\n\n\n/** Check if a line is a bullet list item (- prefix) */\nfunction isBulletLine(l: string): boolean {\n  const trimmed = l.trim();\n  return trimmed.startsWith(\"- \") || trimmed === \"-\" || l.startsWith(\"- \");\n}\n\nconst defaultRichTextStyle: RichTextStyle = {\n  boldStyle: { fontWeight: \"700\" },\n  // React Native Fabric (New Architecture, RN 0.76+) has a known bug on iOS where\n  // fontStyle: \"italic\" is ignored when a custom fontFamily (e.g. \"Inter\") is set\n  // on a parent <Text>. The workaround is to override fontFamily on the italic\n  // child with the iOS system font (\".AppleSystemUIFont\"), which allows fontStyle\n  // to work correctly. The visual difference between Inter italic and SF Pro italic\n  // at body size (14px) is minimal.\n  // See: https://github.com/facebook/react-native/issues/46090\n  italicStyle: Platform.OS === \"ios\"\n    ? { fontFamily: \".AppleSystemUIFont\", fontStyle: \"italic\" }\n    : { fontStyle: \"italic\" },\n  underlineStyle: { textDecorationLine: \"underline\" },\n  strikethroughStyle: { textDecorationLine: \"line-through\" },\n  inlineCodeStyle: {\n    fontSize: 14,\n    fontWeight: \"400\",\n    lineHeight: 16.8,\n    color: \"#6852D6\",\n  },\n  inlineCodeContainerStyle: {\n    backgroundColor: \"rgba(120, 120, 128, 0.22)\",\n    borderRadius: 4,\n    borderWidth: 0.5,\n    borderColor: \"rgba(120, 120, 128, 0.35)\",\n    paddingHorizontal: 4,\n    paddingVertical: 1,\n  },\n  codeBlockStyle: {\n    fontFamily: Platform.OS === \"ios\" ? \"Menlo\" : \"monospace\",\n    fontSize: 13,\n    color: \"#141414\",\n  },\n  codeBlockContainerStyle: {\n    backgroundColor: \"#FAFAFA\",\n    borderRadius: 4,\n    borderWidth: 1,\n    borderColor: \"#E8E8E8\",\n    padding: 12,\n  },\n  blockquoteContainerStyle: {},\n  blockquoteBarStyle: {},\n  bulletListStyle: {},\n  orderedListStyle: {},\n};\n\n/**\n * CometChatRichTextFormatter\n * \n * Parses markdown syntax in message text and renders styled React Native Text components.\n * \n * Supported formats:\n * - Bold: **text**\n * - Italic: _text_\n * - Underline: __text__\n * - Strikethrough: ~~text~~\n * - Inline code: `text`\n * - Code blocks: ```code```\n * - Bullet lists: - item\n * - Ordered lists: 1. item\n * - Blockquotes: > text\n */\nexport class CometChatRichTextFormatter extends CometChatTextFormatter {\n  protected style: RichTextStyle = { ...defaultRichTextStyle };\n\n  constructor(loggedInUser?: CometChat.User) {\n    super();\n    this.loggedInUser = loggedInUser;\n  }\n\n  setStyle = (style: Partial<RichTextStyle>): void => {\n    this.style = { ...defaultRichTextStyle, ...style };\n  };\n\n  getStyle = (): RichTextStyle => {\n    return this.style;\n  };\n\n  getFormattedText(inputText: string | null | JSX.Element): string | null | JSX.Element {\n    if (!inputText) return null;\n    if (typeof inputText === \"string\") {\n      if (!this.hasMarkdown(inputText)) return inputText;\n      return this.renderMarkdown(inputText);\n    } else if (React.isValidElement(inputText)) {\n      const element = inputText as React.ReactElement<any>;\n      if (element.props?.children && typeof element.props.children === \"string\") {\n        if (!this.hasMarkdown(element.props.children)) return inputText;\n        return React.cloneElement(element, {\n          ...element.props,\n          children: this.renderMarkdown(element.props.children),\n        });\n      }\n      return inputText;\n    }\n\n    return inputText;\n  }\n\n  private hasMarkdown(text: string): boolean {\n    if (!text) return false;\n    // Strip mention patterns before checking — mention UIDs can contain\n    // underscores, brackets, and \"> \" sequences that trigger false positives.\n    const cleaned = text.replace(MENTION_PATTERN_REGEX, '');\n    if (cleaned.indexOf(\"**\") >= 0) return true;\n    if (cleaned.indexOf(\"__\") >= 0) return true;\n    if (cleaned.indexOf(\"<u>\") >= 0) return true;\n    if (cleaned.indexOf(\"~~\") >= 0) return true;\n    if (cleaned.indexOf(\"`\") >= 0) return true;\n    if (cleaned.indexOf(\"_\") >= 0) return true;\n    if (cleaned.indexOf(\"[\") >= 0) return true;\n    if (cleaned.indexOf(\"- \") >= 0) return true;\n    if (cleaned.indexOf(\"> \") >= 0) return true;\n    if (cleaned.indexOf(\"▎ \") >= 0) return true;\n    const lines = cleaned.split(\"\\n\");\n    for (let i = 0; i < lines.length; i++) {\n      if (ORDERED_LIST_DETECT_REGEX.test(lines[i].trim())) return true;\n    }\n    return false;\n  }\n\n  private renderMarkdown(text: string): JSX.Element {\n    const lines = text.split(\"\\n\");\n    const elements: JSX.Element[] = [];\n    let lineIndex = 0;\n    let hasViewChildren = false;\n    // Running counter for ordered lists — continues across bullet list interruptions (Slack behavior)\n    let orderedListCounter = 0;\n\n    while (lineIndex < lines.length) {\n      const line = lines[lineIndex];\n      const trimmedLine = line.trim();\n\n      // Code blocks (```)\n      if (trimmedLine.startsWith(\"```\")) {\n        // Check for single-line ```content``` (opening and closing on same line)\n        const afterOpen = trimmedLine.substring(3);\n        const firstClose = afterOpen.indexOf(\"```\");\n        if (firstClose > 0) {\n          const afterClose = afterOpen.substring(firstClose + 3).trim();\n          if (afterClose.length === 0) {\n            // Standalone ```content``` — render as block-level code block (same as fenced)\n            hasViewChildren = true;\n            const content = afterOpen.substring(0, firstClose);\n            elements.push(\n              <View\n                key={`code-${elements.length}`}\n                style={[\n                  {\n                    backgroundColor: \"transparent\",\n                    borderRadius: 4,\n                    padding: 12,\n                  },\n                  this.style.codeBlockContainerStyle,\n                ]}\n              >\n                <Text style={[this.style.codeBlockStyle]}>{content}</Text>\n              </View>\n            );\n            lineIndex++;\n            continue;\n          }\n          // Mixed content line (text + ```code``` + text) — inline rendering\n          elements.push(\n            <Text key={`line-${elements.length}`}>\n              {this.parseInlineFormats(line)}\n            </Text>\n          );\n          lineIndex++;\n          if (lineIndex < lines.length && line.length > 0) {\n            elements.push(<Text key={`nl-${elements.length}`}>{\"\\n\"}</Text>);\n          }\n          continue;\n        }\n\n        // Multi-line fenced code block\n        hasViewChildren = true;\n        const codeLines: string[] = [];\n        lineIndex++;\n        while (lineIndex < lines.length && !lines[lineIndex].trim().startsWith(\"```\")) {\n          codeLines.push(lines[lineIndex]);\n          lineIndex++;\n        }\n        if (lineIndex < lines.length) {\n          lineIndex++; // Skip closing ``` only if it exists\n        }\n        // Render code block content as plain monospaced text — skip parseInlineFormats\n        // and findLink() so URLs inside code blocks remain unformatted (Req 2.1)\n        elements.push(\n          <View\n            key={`code-${elements.length}`}\n            style={[\n              {\n                backgroundColor: \"transparent\",\n                borderRadius: 4,\n                padding: 12,\n              },\n              this.style.codeBlockContainerStyle,\n            ]}\n          >\n            <Text\n              style={[\n                this.style.codeBlockStyle,\n              ]}\n            >\n              {codeLines.join(\"\\n\")}\n            </Text>\n          </View>\n        );\n        if (lineIndex < lines.length) {\n          elements.push(<Text key={`nl-${elements.length}`}>{\"\\n\"}</Text>);\n        }\n        continue;\n      }\n\n      // Blockquotes (> or ▎ ) — Slack-style with vertical bar on left\n      if (trimmedLine.startsWith(\"> \") || trimmedLine.startsWith(\"▎ \")) {\n        hasViewChildren = true;\n        // Collect consecutive blockquote lines into one block\n        const quoteLines: string[] = [];\n        while (lineIndex < lines.length && (lines[lineIndex].trim().startsWith(\"> \") || lines[lineIndex].trim().startsWith(\"▎ \"))) {\n          const ql = lines[lineIndex].trim();\n          quoteLines.push(ql.startsWith(\"> \") ? ql.substring(2) : ql.substring(2));\n          lineIndex++;\n        }\n        // Render blockquote content — detect list items (ENG-31998)\n        const quoteContent: JSX.Element[] = [];\n        const quoteLevelCounters: Map<number, number> = new Map();\n        for (let i = 0; i < quoteLines.length; i++) {\n          const ql = quoteLines[i];\n          const bulletMatch = ql.match(QUOTE_BULLET_REGEX);\n          const orderedMatch = ql.match(QUOTE_ORDERED_REGEX);\n          if (bulletMatch) {\n            // Bullet items inside blockquote: render with marker + flex row\n            // Don't reset quoteLevelCounters — numbering continues across bullet interruptions (Slack behavior)\n            quoteContent.push(\n              <View key={`ql-${i}`} style={LIST_ROW_STYLE}>\n                <Text style={BULLET_MARKER_STYLE}>{\"‧ \"}</Text>\n                <Text style={LIST_CONTENT_STYLE}>\n                  {bulletMatch[1].trim() === '' ? ' ' : this.parseInlineFormats(bulletMatch[1])}\n                </Text>\n              </View>\n            );\n          } else if (orderedMatch) {\n            // Numbered item inside blockquote: supports nested numbering (1. → a. → i.)\n            const level = Math.floor(orderedMatch[1].length / 4);\n            // Reset counters for deeper levels when returning to a shallower level\n            for (const [k] of quoteLevelCounters) {\n              if (k > level) quoteLevelCounters.delete(k);\n            }\n            const currentCount = (quoteLevelCounters.get(level) ?? 0) + 1;\n            quoteLevelCounters.set(level, currentCount);\n            const indent = level * NESTED_LIST_INDENT;\n            quoteContent.push(\n              <View key={`ql-${i}`} style={[LIST_ROW_STYLE, indent > 0 ? { marginLeft: indent } : undefined]}>\n                <Text style={ORDERED_MARKER_STYLE}>{formatListMarker(currentCount, level)}</Text>\n                <Text style={LIST_CONTENT_STYLE}>\n                  {orderedMatch[3].trim() === '' ? ' ' : this.parseInlineFormats(orderedMatch[3])}\n                </Text>\n              </View>\n            );\n          } else {\n            // Plain blockquote text — inline formats supported (Req 15.3)\n            // Wrap in View with flex:1 so long text wraps within blockquote bounds\n            quoteLevelCounters.clear();\n            quoteContent.push(\n              <View key={`ql-${i}`} style={BQ_TEXT_ROW_STYLE}>\n                <Text style={LIST_CONTENT_STYLE}>\n                  {this.parseInlineFormats(ql)}\n                </Text>\n              </View>\n            );\n            // No \\n separator — View elements stack naturally; \\n caused large\n            // vertical gaps between plain text lines inside blockquotes.\n          }\n        }\n        // Blockquote container with left bar — Figma spec (node 14736:1573987)\n        // Uses style config properties for theme-aware customization (OCP)\n        elements.push(\n          <View\n            key={`quote-${elements.length}`}\n            style={[\n              BQ_CONTAINER_STYLE,\n              this.style.blockquoteContainerStyle,\n            ]}\n          >\n            <View style={[BQ_BAR_STYLE, this.style.blockquoteBarStyle]} />\n            <View style={BQ_CONTENT_STYLE}>\n              {quoteContent}\n            </View>\n          </View>\n        );\n        // No \\n separator after blockquote — same reason as bullet/ordered lists:\n        // View elements stack naturally, and the whitespace-only filter in\n        // flushTextGroup handles any stray newline Text nodes.\n        continue;\n      }\n\n      // Bullet lists (- ) — supports nested lists via indentation (4 spaces per level)\n      if (isBulletLine(line)) {\n        hasViewChildren = true;\n        const listItems: Array<{ text: string; level: number }> = [];\n        while (lineIndex < lines.length && isBulletLine(lines[lineIndex])) {\n          const raw = lines[lineIndex];\n          const level = getIndentLevel(raw);\n          const trimmed = raw.trim();\n          const itemText = trimmed.startsWith(\"- \") ? trimmed.substring(2) : \"\";\n          listItems.push({ text: itemText, level });\n          lineIndex++;\n        }\n        for (let i = 0; i < listItems.length; i++) {\n          const { text: itemText, level } = listItems[i];\n          const indent = level * NESTED_LIST_INDENT;\n          elements.push(\n            <View key={`bullet-${elements.length}`} style={[LIST_ROW_STYLE, indent > 0 ? { marginLeft: indent } : undefined]}>\n              <Text style={BULLET_MARKER_STYLE}>{\"‧ \"}</Text>\n              <Text style={LIST_CONTENT_STYLE}>\n                {itemText.trim() === '' ? ' ' : this.parseInlineFormats(itemText)}\n              </Text>\n            </View>\n          );\n        }\n        // No \\n separator — View elements stack naturally; \\n caused blank lines\n        // between mixed ordered/bullet list groups in the bubble renderer.\n        continue;\n      }\n\n      // Ordered lists (1. ) — supports nested lists via indentation (4 spaces per level)\n      const orderedMatch = trimmedLine.match(ORDERED_LIST_REGEX);\n      if (orderedMatch) {\n        hasViewChildren = true;\n        // Collect all consecutive ordered list lines with their indentation levels\n        const listItems: Array<{ text: string; level: number }> = [];\n        while (lineIndex < lines.length) {\n          const rawLine = lines[lineIndex];\n          const itemMatch = rawLine.trim().match(ORDERED_LIST_REGEX);\n          if (itemMatch) {\n            const level = getIndentLevel(rawLine);\n            listItems.push({ text: itemMatch[2] ?? '', level });\n            lineIndex++;\n          } else {\n            break;\n          }\n        }\n        // Track counters per nesting level\n        const levelCounters: Map<number, number> = new Map();\n        for (let i = 0; i < listItems.length; i++) {\n          const { text: itemText, level } = listItems[i];\n          // Reset counters for deeper levels when we go back to a shallower level\n          for (const [k] of levelCounters) {\n            if (k > level) levelCounters.delete(k);\n          }\n          const currentCount = (levelCounters.get(level) ?? 0) + 1;\n          levelCounters.set(level, currentCount);\n          const indent = level * NESTED_LIST_INDENT;\n          elements.push(\n            <View key={`ordered-${elements.length}`} style={[LIST_ROW_STYLE, indent > 0 ? { marginLeft: indent } : undefined]}>\n              <Text style={ORDERED_MARKER_STYLE}>{formatListMarker(currentCount, level)}</Text>\n              <Text style={LIST_CONTENT_STYLE}>\n                {itemText.trim() === '' ? ' ' : this.parseInlineFormats(itemText)}\n              </Text>\n            </View>\n          );\n        }\n        // No \\n separator — same reason as bullet lists above.\n        continue;\n      }\n\n      // Regular line\n      if (line.length > 0) {\n        elements.push(\n          <Text key={`line-${elements.length}`}>\n            {this.parseInlineFormats(line)}\n          </Text>\n        );\n      }\n      lineIndex++;\n      if (lineIndex < lines.length && line.length > 0) {\n        elements.push(<Text key={`nl-${elements.length}`}>{\"\\n\"}</Text>);\n      }\n    }\n\n    if (elements.length === 0) return <Text key=\"empty\">{text}</Text>;\n\n    // If View children are present (blockquotes or inline code containers),\n    // we must use a View root because View children cannot be nested inside Text.\n    if (hasViewChildren) {\n      // Group consecutive Text elements together, keep View elements separate\n      const grouped: JSX.Element[] = [];\n      let textGroup: JSX.Element[] = [];\n\n      const flushTextGroup = () => {\n        if (textGroup.length > 0) {\n          // Skip groups that are only newline/whitespace Text nodes — these appear\n          // between list blocks and would render as blank lines in the bubble.\n          const isOnlyWhitespace = textGroup.every(el => {\n            if (el.type !== Text) return false;\n            const child = el.props?.children;\n            return typeof child === 'string' && child.trim() === '';\n          });\n          if (!isOnlyWhitespace) {\n            grouped.push(\n              <Text key={`tg-${grouped.length}`}>{textGroup}</Text>\n            );\n          }\n          textGroup = [];\n        }\n      };\n\n      for (const el of elements) {\n        if (el.type === View) {\n          flushTextGroup();\n          grouped.push(el);\n        } else {\n          textGroup.push(el);\n        }\n      }\n      flushTextGroup();\n\n      return <View key=\"root\" style={{ flexDirection: 'column', flexShrink: 1 }}>{grouped}</View>;\n    }\n\n    if (elements.length === 1) return elements[0];\n    return <Text key=\"root\">{elements}</Text>;\n  }\n\n  private parseInlineFormats(text: string): JSX.Element | string {\n    if (!text) return \"\";\n    // Strip mention patterns before checking for markdown markers — mention UIDs\n    // can contain underscores/brackets that would trigger false-positive parsing.\n    const textWithoutMentions = text.replace(MENTION_PATTERN_REGEX, '');\n    if (textWithoutMentions.indexOf(\"**\") < 0 && textWithoutMentions.indexOf(\"__\") < 0 && textWithoutMentions.indexOf(\"<u>\") < 0 &&\n        textWithoutMentions.indexOf(\"~~\") < 0 && textWithoutMentions.indexOf(\"`\") < 0 && textWithoutMentions.indexOf(\"_\") < 0 &&\n        textWithoutMentions.indexOf(\"[\") < 0) {\n      return text;\n    }\n\n    // Replace mention patterns with null-byte placeholders before parsing so\n    // findItalic/findNextFormat never see underscores or brackets inside UIDs.\n    // After parsing, restore the original mention strings in the output.\n    const mentionSlots: string[] = [];\n    const sanitized = text.replace(MENTION_PATTERN_REGEX, (match) => {\n      const idx = mentionSlots.length;\n      mentionSlots.push(match);\n      return `\\x00M${idx}\\x00`;\n    });\n\n    // Restore mention placeholders in a string or JSX tree\n    const restoreMentions = (node: JSX.Element | string): JSX.Element | string => {\n      if (typeof node === 'string') {\n        if (mentionSlots.length === 0 || node.indexOf('\\x00') < 0) return node;\n        return node.replace(/\\x00M(\\d+)\\x00/g, (_, idx) => mentionSlots[Number(idx)] ?? _);\n      }\n      if (!React.isValidElement(node)) return node;\n      const el = node as React.ReactElement<any>;\n      const children = el.props.children;\n      if (!children) return node;\n      const restored = React.Children.map(children, (child: any) => {\n        if (typeof child === 'string') return restoreMentions(child);\n        if (React.isValidElement(child)) return restoreMentions(child as JSX.Element);\n        return child;\n      });\n      return React.cloneElement(el, {}, ...(restored || []));\n    };\n\n    const elements: (JSX.Element | string)[] = [];\n    let remaining = sanitized;\n    let keyCounter = 0;\n    let iterations = 0;\n    const maxIterations = 500;\n\n    while (remaining.length > 0 && iterations < maxIterations) {\n      iterations++;\n      const match = this.findNextFormat(remaining);\n      if (match) {\n        if (match.startIndex > 0) {\n          elements.push(restoreMentions(remaining.substring(0, match.startIndex)) as string);\n        }\n        // Restore mentions in matched content before recursive parsing\n        const restoredContent = (typeof match.content === 'string' && match.content.indexOf('\\x00') >= 0)\n          ? restoreMentions(match.content) as string\n          : match.content;\n        if (match.type === \"link\" && match.url) {\n          // Render link as tappable blue underlined text (same as CometChatUrlsFormatter)\n          const innerContent = this.parseInlineFormats(restoredContent);\n          const linkUrl = match.url;\n          elements.push(\n            <Text\n              key={`fmt-${keyCounter++}`}\n              style={this.style.linkStyle || { color: \"#1a73e8\", textDecorationLine: \"underline\" }}\n              onPress={() => {\n                let finalUrl = linkUrl;\n                if (!URL_PROTOCOL_REGEX.test(linkUrl)) {\n                  finalUrl = `http://${linkUrl}`;\n                }\n                Linking.openURL(finalUrl).catch((err) => {\n                  console.log(\"Error opening URL:\", err);\n                });\n              }}\n            >{innerContent}</Text>\n          );\n        } else if (match.type === \"inlineCode\") {\n          const innerContent = this.parseInlineFormats(restoredContent);\n          // Use a single Text element (not View) so inline code stays on the\n          // same text baseline as surrounding content (mentions, plain text).\n          // Text supports backgroundColor, borderRadius, and padding in RN.\n          elements.push(\n            <Text key={`fmt-${keyCounter++}`} style={[this.style.inlineCodeContainerStyle, this.style.inlineCodeStyle]}>{innerContent}</Text>\n          );\n        } else if (match.type === \"codeBlock\") {\n          // Inline ```text``` — render identically to single-backtick inline code\n          // (compact pill with background, no block-level padding).\n          // Block-level code block rendering with View container is handled in renderMarkdown.\n          elements.push(\n            <Text\n              key={`fmt-${keyCounter++}`}\n              style={[this.style.inlineCodeContainerStyle, this.style.inlineCodeStyle]}\n            >{restoredContent}</Text>\n          );\n        } else {\n          const style = this.getStyleForFormat(match.type);\n          const innerContent = this.parseInlineFormats(restoredContent);\n          // Merge textDecorationLine with child elements so underline\n          // and strikethrough can coexist (React Native child overrides parent).\n          const mergedInner = this.mergeTextDecoration(innerContent, style);\n          elements.push(\n            <Text key={`fmt-${keyCounter++}`} style={style}>{mergedInner}</Text>\n          );\n        }\n        remaining = remaining.substring(match.endIndex);\n      } else {\n        elements.push(restoreMentions(remaining) as string);\n        remaining = \"\";\n      }\n    }\n\n    if (elements.length === 0) return text;\n    if (elements.length === 1 && typeof elements[0] === \"string\") return elements[0];\n    return <Text key={`inline-${keyCounter}`}>{elements}</Text>;\n  }\n\n  private findNextFormat(text: string): { type: string; content: string; startIndex: number; endIndex: number; url?: string } | null {\n    const matches: Array<{ type: string; content: string; startIndex: number; endIndex: number; url?: string }> = [];\n\n    const codeBlockMatch = this.findPair(text, \"```\", \"```\");\n    if (codeBlockMatch) matches.push({ ...codeBlockMatch, type: \"codeBlock\" });\n\n    const inlineCodeMatch = this.findPair(text, \"`\", \"`\");\n    if (inlineCodeMatch) {\n      const isPartOfCodeBlock = codeBlockMatch && \n        inlineCodeMatch.startIndex >= codeBlockMatch.startIndex && \n        inlineCodeMatch.startIndex < codeBlockMatch.endIndex;\n      if (!isPartOfCodeBlock) matches.push({ ...inlineCodeMatch, type: \"inlineCode\" });\n    }\n\n    const boldMatch = this.findPair(text, \"**\", \"**\");\n    if (boldMatch) matches.push({ ...boldMatch, type: \"bold\" });\n\n    const underlineMatch = this.findPair(text, \"__\", \"__\");\n    if (underlineMatch) matches.push({ ...underlineMatch, type: \"underline\" });\n\n    // HTML underline tag: <u>text</u>\n    const htmlUnderlineMatch = this.findHtmlUnderline(text);\n    if (htmlUnderlineMatch) matches.push({ ...htmlUnderlineMatch, type: \"underline\" });\n\n    const strikeMatch = this.findPair(text, \"~~\", \"~~\");\n    if (strikeMatch) matches.push({ ...strikeMatch, type: \"strikethrough\" });\n\n    const italicMatch = this.findItalic(text);\n    if (italicMatch) matches.push({ ...italicMatch, type: \"italic\" });\n\n    const linkMatch = this.findLink(text);\n    if (linkMatch) matches.push(linkMatch);\n\n    if (matches.length === 0) return null;\n    let earliest = matches[0];\n    for (let i = 1; i < matches.length; i++) {\n      if (matches[i].startIndex < earliest.startIndex) earliest = matches[i];\n    }\n    return earliest;\n  }\n\n  private findPair(text: string, openMarker: string, closeMarker: string): { content: string; startIndex: number; endIndex: number } | null {\n    const openIndex = text.indexOf(openMarker);\n    if (openIndex < 0) return null;\n    const contentStart = openIndex + openMarker.length;\n    if (contentStart >= text.length) return null;\n\n    // Handle triple underscore: ___content___ should be parsed as __( _content_ )__\n    // When looking for __ pairs and we see ___ (triple), skip the extra _ so the\n    // inner _ is preserved as italic content for recursive parsing.\n    if (openMarker === \"__\" && text.charAt(contentStart) === \"_\") {\n      // We have ___..., look for closing ___ (triple)\n      const tripleCloseIndex = text.indexOf(\"___\", contentStart + 1);\n      if (tripleCloseIndex > contentStart) {\n        // Content between the outer __ markers is _innerContent_\n        // which will be recursively parsed as italic\n        const content = text.substring(contentStart, tripleCloseIndex + 1); // includes the inner _ on both sides\n        return { content, startIndex: openIndex, endIndex: tripleCloseIndex + 3 }; // +3 for ___\n      }\n    }\n\n    const closeIndex = text.indexOf(closeMarker, contentStart);\n    if (closeIndex < 0 || closeIndex <= contentStart) return null;\n    const content = text.substring(contentStart, closeIndex);\n    if (content.length === 0) return null;\n    return { content, startIndex: openIndex, endIndex: closeIndex + closeMarker.length };\n  }\n\n  private findItalic(text: string): { content: string; startIndex: number; endIndex: number } | null {\n    const doublePositions = new Set<number>();\n    let searchPos = 0;\n    while (searchPos < text.length - 1) {\n      const idx = text.indexOf(\"__\", searchPos);\n      if (idx < 0) break;\n      doublePositions.add(idx);\n      doublePositions.add(idx + 1);\n      searchPos = idx + 2;\n    }\n    for (let i = 0; i < text.length; i++) {\n      if (text.charAt(i) === \"_\" && !doublePositions.has(i)) {\n        for (let j = i + 1; j < text.length; j++) {\n          if (text.charAt(j) === \"_\" && !doublePositions.has(j)) {\n            const content = text.substring(i + 1, j);\n            if (content.length > 0) return { content, startIndex: i, endIndex: j + 1 };\n            break;\n          }\n        }\n      }\n    }\n    return null;\n  }\n\n  private findLink(text: string): { type: string; content: string; startIndex: number; endIndex: number; url?: string } | null {\n    const match = LINK_REGEX.exec(text);\n    if (!match) return null;\n    return {\n      type: \"link\",\n      content: match[1],\n      url: match[2],\n      startIndex: match.index,\n      endIndex: match.index + match[0].length,\n    };\n  }\n\n  /** Detect <u>text</u> HTML underline tags (case-insensitive) */\n  private findHtmlUnderline(text: string): { content: string; startIndex: number; endIndex: number } | null {\n    const lower = text.toLowerCase();\n    const openIdx = lower.indexOf(\"<u>\");\n    if (openIdx < 0) return null;\n    const contentStart = openIdx + 3;\n    const closeIdx = lower.indexOf(\"</u>\", contentStart);\n    if (closeIdx < 0) return null;\n    const content = text.substring(contentStart, closeIdx);\n    if (content.length === 0) return null;\n    return { content, startIndex: openIdx, endIndex: closeIdx + 4 };\n  }\n\n  /**\n   * When a parent Text has textDecorationLine (e.g. \"underline\") and a child\n   * also has textDecorationLine (e.g. \"line-through\"), React Native's child\n   * value overrides the parent instead of merging. This helper walks the\n   * immediate children and combines the parent's decoration into any child\n   * that also declares textDecorationLine, so both render correctly.\n   */\n  private mergeTextDecoration(\n    inner: JSX.Element | string,\n    parentStyle: TextStyle\n  ): JSX.Element | string {\n    const parentDeco = parentStyle.textDecorationLine;\n    if (!parentDeco || typeof inner === \"string\") return inner;\n    if (!React.isValidElement(inner)) return inner;\n\n    const el = inner as React.ReactElement<any>;\n    const children = React.Children.map(el.props.children, (child) => {\n      if (!React.isValidElement(child)) return child;\n      const childEl = child as React.ReactElement<any>;\n      const childStyle = childEl.props.style;\n      if (!childStyle) return child;\n\n      // Flatten style to find textDecorationLine\n      const flat = Array.isArray(childStyle)\n        ? Object.assign({}, ...childStyle.filter(Boolean))\n        : childStyle;\n      const childDeco = flat.textDecorationLine as string | undefined;\n      if (!childDeco || childDeco === parentDeco) return child;\n\n      // Combine: e.g. \"underline\" + \"line-through\" → \"underline line-through\"\n      const parts = new Set([...parentDeco.split(\" \"), ...childDeco.split(\" \")]);\n      parts.delete(\"none\");\n      const combined = parts.size > 0 ? Array.from(parts).join(\" \") : \"none\";\n\n      return React.cloneElement(childEl, {\n        style: Array.isArray(childStyle)\n          ? [...childStyle, { textDecorationLine: combined }]\n          : { ...flat, textDecorationLine: combined },\n      });\n    });\n\n    if (!children) return inner;\n    return React.cloneElement(el, {}, ...children);\n  }\n\n  private getStyleForFormat(type: string): TextStyle {\n      switch (type) {\n        case \"bold\": return this.style.boldStyle || {};\n        case \"italic\": return this.style.italicStyle || {};\n        case \"underline\": return this.style.underlineStyle || {};\n        case \"strikethrough\": return this.style.strikethroughStyle || {};\n        case \"inlineCode\": return this.style.inlineCodeStyle || {};\n        case \"codeBlock\": return this.style.codeBlockStyle || {};\n        default: return {};\n      }\n    }\n\n  getMessage() { return this.messageObject; }\n  setMessage(messageObject: CometChat.BaseMessage) { this.messageObject = messageObject; }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/CometChatTextFormatter.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatUIEventHandler, CometChatUIEvents } from \"../events\";\nimport { SuggestionItem } from \"../views/CometChatSuggestionList\";\nimport { JSX } from \"react\";\n\n/**\n * CometChatTextFormatter\n * Abstract class for text formatter.\n */\nexport abstract class CometChatTextFormatter {\n  /**\n   * The regex patterns to find specific text pattern in the user input text.\n   */\n  protected regexPattern = /(@w+)/g;\n\n  /**\n   * List of users for suggestion item.\n   */\n  protected SuggestionItems: Array<SuggestionItem> = [];\n\n  /**\n   * The message object in context.\n   */\n  protected messageObject!: CometChat.BaseMessage;\n\n  /**\n   * List of searched data.\n   */\n  protected searchData: Array<SuggestionItem> = [];\n\n  /**\n   * The user in context.\n   */\n  protected user!: CometChat.User;\n\n  /**\n   * The group in context.\n   */\n  protected group!: CometChat.Group;\n\n  /**\n   * The composer ID.\n   */\n  protected composerId!: string | number;\n\n  /**\n   * The formatter id.\n   */\n  protected id!: string | number;\n\n  /**\n   * The character to track once typed in the text input field.\n   */\n  protected trackCharacter: string = \"#\";\n\n  /**\n   * The user who is currently logged in.\n   */\n  protected loggedInUser?: CometChat.User;\n\n  /**\n   * Sets the regex patterns to match.\n   * @param regexPattern - The regex patterns.\n   */\n  setRegexPatterns(regexPattern: RegExp) {\n    this.regexPattern = regexPattern;\n  }\n\n  /**\n   * Gets the regex pattern for matching text.\n   * @returns The regex pattern.\n   */\n  getRegexPattern = (): RegExp => {\n    return this.regexPattern;\n  };\n\n  /**\n   * Gets the composer ID.\n   * @returns The composer ID.\n   */\n  getComposerId = (): string | number => {\n    return this.composerId;\n  };\n\n  /**\n   * Gets the formatter ID.\n   * @returns The formatter ID.\n   */\n  getId = (): string | number => {\n    return this.id;\n  };\n\n  /**\n   * Sets the tracking character.\n   * @param trackCharacter - The character to track.\n   */\n  setTrackingCharacter(trackCharacter: string) {\n    this.trackCharacter = trackCharacter;\n  }\n\n  /**\n   * Sets the composer ID.\n   * @param composerId - The composer ID.\n   */\n  setComposerId(composerId: string | number) {\n    this.composerId = composerId;\n  }\n\n  /**\n   * Sets the formatter ID.\n   * @param id - The formatter ID.\n   */\n  setId(id: string | number) {\n    this.id = id;\n  }\n\n  /**\n   * Search function used to call an API with searched text.\n   * @param searchKey - The search key.\n   */\n  search(searchKey: string) {}\n\n  /**\n   * Sets the search data.\n   * @param data - The search data.\n   */\n  setSearchData(data: Array<SuggestionItem>) {\n    this.searchData = [...data];\n    CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.ccSuggestionData, {\n      id: this.composerId,\n      data: [...this.searchData],\n    });\n  }\n\n  /**\n   * Sets the message object.\n   * @param messageObject - The message object to be set.\n   */\n  setMessage(messageObject: CometChat.BaseMessage) {\n    this.messageObject = messageObject;\n  }\n\n  /**\n   * Retrieves the message object.\n   * @returns The current message object.\n   */\n  getMessage() {\n    return this.messageObject;\n  }\n\n  /**\n   * Fetches the next set of data.\n   */\n  fetchNext() {}\n\n  /**\n   * Sets the user.\n   * @param user - The user to set.\n   */\n  setUser(user: CometChat.User) {\n    this.user = user;\n  }\n\n  /**\n   * Retrieves the user.\n   * @returns The current user.\n   */\n  getUser(): CometChat.User {\n    return this.user;\n  }\n\n  /**\n   * Sets the group.\n   * @param group - The group to set.\n   */\n  setGroup(group: CometChat.Group) {\n    this.group = group;\n  }\n\n  /**\n   * Retrieves the group.\n   * @returns The current group.\n   */\n  getGroup(): CometChat.Group {\n    return this.group;\n  }\n\n  /**\n   * Retrieves the currently logged in user.\n   * @returns The currently logged in user.\n   */\n  getLoggedInUser() {\n    return this.loggedInUser;\n  }\n\n  /**\n   * Sets the currently logged in user.\n   * @param loggedInUser - The user to set as currently logged in.\n   */\n  setLoggedInUser(loggedInUser: CometChat.User) {\n    this.loggedInUser = loggedInUser;\n  }\n\n  /**\n   * If the input text is provided, it returns the formatted text. Otherwise, it edits the text using the current cursor position.\n   * @param inputText - The text to format.\n   * @returns The formatted text.\n   */\n  getFormattedText(inputText: string | null | JSX.Element): string | null | JSX.Element {\n    if (!inputText) {\n      return \"\";\n    }\n\n    return inputText;\n  }\n\n  /**\n   * Handles the message before sending it.\n   * @param message - The message to handle.\n   * @returns The message after handling.\n   */\n  handlePreMessageSend(message: CometChat.TextMessage): CometChat.TextMessage {\n    return message;\n  }\n\n  /**\n   * Handles the message before editing it.\n   * @param message - The message to handle.\n   * @returns The message after handling.\n   */\n  handleComposerPreview(message: CometChat.TextMessage) {}\n\n  /**\n   * Gets the tracking character.\n   * @returns The tracking character.\n   */\n  getTrackingCharacter() {\n    return this.trackCharacter;\n  }\n\n  /**\n   * Retrieves the suggestion items.\n   * @returns The current suggestion items.\n   */\n  getSuggestionItems(): Array<SuggestionItem> {\n    return this.SuggestionItems;\n  }\n\n  /**\n   * Sets the suggestion items.\n   * @param SuggestionItems - The suggestion items to be set.\n   */\n  setSuggestionItems(SuggestionItems: Array<SuggestionItem>) {\n    this.SuggestionItems = SuggestionItems;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/CometChatUrlsFormatter/index.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { ColorValue, Linking, Text, TextStyle } from \"react-native\";\nimport { emailPattern, phoneNumPattern, urlPattern } from \"../../constants/UIKitConstants\";\nimport { CometChatTextFormatter } from \"../CometChatTextFormatter\";\n\nexport class CometChatUrlsFormatter extends CometChatTextFormatter {\n  protected style: {\n    linkTextColor?: ColorValue;\n    linkTextFont?: TextStyle;\n  } = {\n    linkTextColor: \"blue\",\n    linkTextFont:{ fontSize: 17, fontWeight: \"400\" },\n  };\n\n  constructor(loggedInUser?: CometChat.User) {\n    super();\n    this.loggedInUser = loggedInUser;\n  }\n\n  private Link = ({ text, url, style }: any) => {\n    const handlePress = async () => {\n      try {\n        let finalUrl = url;\n        if (!url.match(/^(https?|mailto|tel):/i)) {\n          finalUrl = `http://${url}`;\n        }\n        \n        const canOpen = await Linking.canOpenURL(finalUrl);\n        if (canOpen) {\n          Linking.openURL(finalUrl);\n        } else {\n          // Try opening anyway as fallback\n          Linking.openURL(finalUrl).catch((err) => {\n            console.log(\"Can not open link\", finalUrl, err);\n          });\n        }\n      } catch (err) {\n        console.log(\"Error opening URL:\", err);\n      }\n    };\n\n    return (\n      <Text\n        style={{\n          color: style?.linkTextColor,\n          ...style?.linkTextFont,\n          textDecorationLine: \"underline\",\n        }}\n        onPress={handlePress}\n      >\n        {text}\n      </Text>\n    );\n  };\n\n  setStyle = (style: { linkTextFont?: TextStyle; linkTextColor?: ColorValue }) => {\n    this.style = style;\n  };\n  private getPatternGroup = (str: string): { phone?: string; email?: string; url?: string } => {\n    let result: any = {};\n    if (str.match(phoneNumPattern)) result[\"phone\"] = str;\n    if (str.match(emailPattern)) result[\"email\"] = str;\n    if (str.match(urlPattern)) result[\"url\"] = str;\n    return result;\n  };\n\n  /**\n   * Formats the input text if provided, otherwise edits the text at the cursor position.\n   *\n   * @param {string|null} inputText - The input text to be formatted.\n   * @returns {string|void} - The formatted input text, or void if inputText is not provided.\n   */\n  getFormattedText(inputText: string | null | JSX.Element) {\n    if (!inputText) {\n      return null;\n    }\n    try {\n      let formattedText = this.getFormatTextForLinks({ str: inputText, style: this.style });\n      return formattedText;\n    } catch {\n      // Gracefully handle regex stack overflow on Android Hermes for very long messages.\n      // Return the input as-is rather than crashing the app.\n      return inputText;\n    }\n  }\n\n  getFormatTextForLinks = ({ str, style }: any): any => {\n    if (typeof str === \"string\") {\n      let res = str.matchAll(\n        (phoneNumPattern + \"|\" + emailPattern + \"|\" + urlPattern) as unknown as RegExp\n      );\n      for (let resPart of res) {\n        let { email, phone } = this.getPatternGroup(resPart[0]);\n        let pre: string, post: string;\n        pre = str.substring(0, resPart?.index);\n        post = str.substring(resPart.index! + resPart[0].length);\n        let urlLink = \"\";\n        if (email) urlLink = \"mailto:\";\n        if (phone) urlLink = \"tel:\";\n        return (\n          <Text>\n            <Text>{pre}</Text>\n            {this.Link({\n              text: resPart[0].trim(),\n              url: urlLink.trim() + resPart[0].trim(),\n              style: { ...this.style },\n            })}\n            {this.getFormatTextForLinks({\n              str: post,\n              style: style,\n            })}\n          </Text>\n        );\n      }\n\n      return <Text>{str}</Text>;\n    } else if (React.isValidElement(str)) {\n      // str is a React element\n      if ((str as React.ReactElement<any>).props.children) {\n        // If the React element have children, we map over these children\n        // and call addMentionsSpan recursively for each child.\n        return React.cloneElement(str as React.ReactElement<any>, {\n          children: React.Children.map((str as React.ReactElement<any>).props.children, (child) => {\n            return this.getFormatTextForLinks({ str: child, style: style });\n          }),\n        });\n      } else {\n        // If the React element does not have children, return it as is\n        return str;\n      }\n    } else {\n      throw new Error(`Unsupported str type: ${typeof str}`);\n    }\n  };\n\n  /**\n   * Retrieves the message object.\n   *\n   * @returns {CometChat.BaseMessage} - The current message object.\n   */\n  getMessage() {\n    return this.messageObject;\n  }\n\n  /**\n   * Sets the message object.\n   *\n   * @param {CometChat.BaseMessage} messageObject - The message object to be set.\n   */\n  setMessage(messageObject: CometChat.BaseMessage) {\n    this.messageObject = messageObject;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/formatters/index.ts",
    "content": "import { CometChatMentionsFormatter, MentionTextStyle } from \"./CometChatMentionsFormatter\";\nimport { CometChatTextFormatter } from \"./CometChatTextFormatter\";\nimport { CometChatUrlsFormatter } from \"./CometChatUrlsFormatter\";\nimport { CometChatRichTextFormatter, RichTextStyle } from \"./CometChatRichTextFormatter\";\n\nexport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUrlsFormatter,\n  CometChatRichTextFormatter,\n  MentionTextStyle,\n  RichTextStyle,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/ChatConfigurator.ts",
    "content": "import { DataSource } from \"./DataSource\";\nimport { MessageDataSource } from \"./MessageDataSource\";\n\nexport class ChatConfigurator {\n  static dataSource: DataSource = new MessageDataSource();\n\n  static names: string[] = [\"message_utils\"];\n\n  static init(initialSource?: DataSource) {\n    this.dataSource = initialSource ?? new MessageDataSource();\n    this.names = [\"message_utils\"];\n    this.names.push(this.dataSource.getId());\n  }\n\n  static enable(fun: (source: DataSource) => DataSource) {\n    let oldSource: DataSource = this.dataSource;\n    let newSource: DataSource = fun(oldSource);\n    if (!this.names.find((nm) => nm == newSource.getId())) {\n      this.dataSource = newSource;\n      this.names.push(this.dataSource.getId());\n    }\n  }\n\n  static getDataSource() {\n    return ChatConfigurator.dataSource;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/DataSource.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport {\n  AdditionalAttachmentOptionsParams,\n  AdditionalAuxiliaryHeaderOptionsParams,\n  AdditionalAuxiliaryOptionsParams,\n  AdditionalParams,\n  MessageBubbleAlignmentType,\n} from \"../base/Types\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUrlsFormatter,\n} from \"../formatters\";\nimport { CometChatMessageComposerAction } from \"../helper/types\";\nimport { CometChatMessageOption } from \"../modals/CometChatMessageOption\";\nimport { CometChatMessageTemplate } from \"../modals/CometChatMessageTemplate\";\nimport { JSX } from \"react\";\n\nexport interface DataSource {\n  //message options based on types\n  getTextMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n  getAudioMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n  getVideoMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n  getImageMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n  getFileMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n  getMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n  getCommonOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageOption>;\n\n  //views\n  getBottomView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ): JSX.Element | null;\n  getReplyView?(\n    message: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element | null;\n  getDeleteMessageBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element;\n  getVideoMessageBubble(\n    videoUrl: string,\n    thumbnailUrl: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element | null;\n  getTextMessageBubble(\n    messageText: string,\n    message: CometChat.TextMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element;\n  getImageMessageBubble(\n    imageUrl: string,\n    caption: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element;\n  getAudioMessageBubble(\n    audioUrl: string,\n    title: string,\n    style: any, //ToDoM: remove any\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element;\n  getFileMessageBubble(\n    fileUrl: string,\n    title: string,\n    style: any, //ToDoM: remove any\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element;\n  getGroupActionBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element | null;\n  getAgentAssistantMessageBubble: (message: CometChat.BaseMessage, theme: CometChatTheme) => JSX.Element;\n  getAgentAssistantMessageTemplate: (theme: CometChatTheme, additionalParams?: AdditionalParams) => CometChatMessageTemplate;\n  //content views\n  getTextMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element;\n  getAudioMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element;\n  getVideoMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element | null;\n  getImageMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element | null;\n  getFileMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element;\n\n  //templates\n  getTextMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getAudioMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getVideoMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getImageMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getFileMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): Array<CometChatMessageTemplate>;\n  getMessageTemplate(\n    messageType: string,\n    MessageCategory: string,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams,\n    message?: CometChat.BaseMessage\n  ): CometChatMessageTemplate | null;\n  getGroupActionTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getFormMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getSchedulerMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n  getCardMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate;\n\n  //attachment options\n  // getAttachmentOptions(theme: CometChatTheme, conversation: CometChat.User | CometChat.Group): Array<CometChatMessageComposerAction>\n\n  getAllMessageTypes(): Array<string>;\n  getAllMessageCategories(): Array<string>;\n\n  //auxiliary options\n  getAuxiliaryOptions(\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    id?: Map<string, any>,\n    additionalAuxiliaryParams?: AdditionalAuxiliaryOptionsParams\n  ): JSX.Element[];\n\n  getId(): string;\n\n  //unknown\n  getMessageTypeToSubtitle(messageType: string): string;\n  //Message Composer\n  getAttachmentOptions: (\n    theme: CometChatTheme,\n    user?: any,\n    group?: any,\n    composerId?: any,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ) => any;\n  getAuxiliaryButtonOptions: () => any;\n\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element;\n\n  getAuxiliaryHeaderAppbarOptions(\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    additionalAuxiliaryHeaderOptionsParams?: AdditionalAuxiliaryHeaderOptionsParams\n  ): JSX.Element | null;\n\n  getAllTextFormatters(loggedInUser?: CometChat.User, theme?: CometChatTheme): CometChatTextFormatter[];\n  getMentionsFormatter(\n    loggedInUser?: CometChat.User,\n    theme?: CometChatTheme\n  ): CometChatMentionsFormatter;\n  getUrlsFormatter(loggedInUser?: CometChat.User): CometChatUrlsFormatter;\n  \n  getMessagePreviewSubtitle(message: CometChat.BaseMessage): string;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/DataSourceDecorator.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { AdditionalAttachmentOptionsParams, AdditionalAuxiliaryHeaderOptionsParams, AdditionalAuxiliaryOptionsParams, AdditionalParams, MessageBubbleAlignmentType } from \"../base/Types\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUrlsFormatter,\n} from \"../formatters\";\nimport { CometChatMessageComposerAction } from \"../helper/types\";\nimport { CometChatMessageOption, CometChatMessageTemplate } from \"../modals\";\nimport { DataSource } from \"./DataSource\";\nimport { JSX } from \"react\";\n\nexport class DataSourceDecorator implements DataSource {\n  dataSource: DataSource;\n\n  constructor(dataSource: DataSource) {\n    this.dataSource = dataSource;\n  }\n\n  getId(): string {\n    throw new Error(\"Method not implemented.\");\n  }\n\n  getTextMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getTextMessageOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getAudioMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getAudioMessageOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getVideoMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getVideoMessageOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getImageMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getImageMessageOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getFileMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getFileMessageOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getMessageOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getCommonOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams,\n  ): CometChatMessageOption[] {\n    return this.dataSource.getCommonOptions(loggedInUser, messageObject, theme, group, additionalParams);\n  }\n\n  getBottomView(message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) {\n    return this.dataSource.getBottomView(message, alignment);\n  }\n\n  getDeleteMessageBubble(message: CometChat.BaseMessage, theme: CometChatTheme) {\n    return this.dataSource.getDeleteMessageBubble(message, theme);\n  }\n\n  getVideoMessageBubble(\n    videoUrl: string,\n    thumbnailUrl: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element | null {\n    return this.dataSource.getVideoMessageBubble(videoUrl, thumbnailUrl, message, theme);\n  }\n\n  getTextMessageBubble(\n    messageText: string,\n    message: CometChat.TextMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ) {\n    return this.dataSource.getTextMessageBubble(\n      messageText,\n      message,\n      alignment,\n      theme,\n      additionalParams\n    );\n  }\n\n  getImageMessageBubble(\n    imageUrl: string,\n    caption: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getImageMessageBubble(imageUrl, caption, message, theme);\n  }\n\n  getAudioMessageBubble(\n    audioUrl: string,\n    title: string,\n    style: {}, //ToDoM: remove any\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getAudioMessageBubble(audioUrl, title, style, message, theme);\n  }\n\n  getFileMessageBubble(\n    fileUrl: string,\n    title: string,\n    style: any, //ToDoM: remove any\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getFileMessageBubble(fileUrl, title, style, message, theme);\n  }\n\n  getGroupActionBubble(message: CometChat.BaseMessage, theme: CometChatTheme) {\n    return this.dataSource.getGroupActionBubble(message, theme);\n  }\n\n    getAgentAssistantMessageBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element {\n    return this.dataSource.getAgentAssistantMessageBubble(message, theme);\n  }\n  getAgentAssistantMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getAgentAssistantMessageTemplate(theme, additionalParams);\n  }\n\n  getTextMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ) {\n    return this.dataSource.getTextMessageContentView(message, alignment, theme, additionalParams);\n  }\n\n  getAudioMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getAudioMessageContentView(message, alignment, theme);\n  }\n\n  getVideoMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getVideoMessageContentView(message, alignment, theme);\n  }\n\n  getImageMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getImageMessageContentView(message, alignment, theme);\n  }\n\n  getFileMessageContentView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ) {\n    return this.dataSource.getFileMessageContentView(message, alignment, theme);\n  }\n\n  getTextMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return this.dataSource.getTextMessageTemplate(theme, additionalParams);\n  }\n\n  getFormMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getFormMessageTemplate(theme, additionalParams);\n  }\n\n  getSchedulerMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getSchedulerMessageTemplate(theme, additionalParams);\n  }\n\n  getCardMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getCardMessageTemplate(theme, additionalParams);\n  }\n\n  getAudioMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getAudioMessageTemplate(theme, additionalParams);\n  }\n\n  getVideoMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getVideoMessageTemplate(theme, additionalParams);\n  }\n\n  getImageMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getImageMessageTemplate(theme, additionalParams);\n  }\n\n  getFileMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return this.dataSource.getFileMessageTemplate(theme, additionalParams);\n  }\n\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    return this.dataSource.getAllMessageTemplates(theme, additionalParams);\n  }\n\n  getMessageTemplate(\n    messageType: string,\n    MessageCategory: string,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams,\n    message?: CometChat.BaseMessage\n  ): CometChatMessageTemplate | null {\n    return this.dataSource.getMessageTemplate(messageType, MessageCategory, theme, additionalParams, message);\n  }\n\n  getGroupActionTemplate(theme: CometChatTheme): CometChatMessageTemplate {\n    return this.dataSource.getGroupActionTemplate(theme);\n  }\n\n  getAllMessageTypes(): string[] {\n    return this.dataSource.getAllMessageTypes();\n  }\n\n  getAllMessageCategories(): string[] {\n    return this.dataSource.getAllMessageCategories();\n  }\n\n  getAuxiliaryOptions(\n    user: CometChat.User,\n    group: CometChat.Group,\n    id: Map<string, any>,\n    additionalAuxiliaryParams?: AdditionalAuxiliaryOptionsParams\n  ) {\n    return this.dataSource.getAuxiliaryOptions(user, group, id, additionalAuxiliaryParams);\n  }\n\n  getMessageTypeToSubtitle(messageType: string): string {\n    return this.dataSource.getMessageTypeToSubtitle(messageType);\n  }\n\n  getAttachmentOptions(\n    theme: CometChatTheme,\n    user?: any,\n    group?: any,\n    composerId?: any,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ): CometChatMessageComposerAction[] {\n    return this.dataSource.getAttachmentOptions(theme, user, group, composerId, additionalAttachmentOptionsParams);\n  }\n\n  getAuxiliaryButtonOptions() {\n    return this.dataSource.getAuxiliaryButtonOptions();\n  }\n\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element {\n    return this.dataSource.getLastConversationMessage(conversation, theme);\n  }\n\n  getAuxiliaryHeaderAppbarOptions(\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    additionalAuxiliaryHeaderOptionsParams?: AdditionalAuxiliaryHeaderOptionsParams\n  ) {\n    return this.dataSource.getAuxiliaryHeaderAppbarOptions(user, group, additionalAuxiliaryHeaderOptionsParams);\n  }\n\n  getAllTextFormatters(loggedInUser?: CometChat.User, theme?: CometChatTheme): CometChatTextFormatter[] {\n    return [\n      this.dataSource.getMentionsFormatter(loggedInUser, theme),\n      this.dataSource.getUrlsFormatter(loggedInUser),\n    ];\n  }\n\n  getMentionsFormatter(loggedInUser?: CometChat.User, theme?: CometChatTheme): CometChatMentionsFormatter {\n    return this.dataSource.getMentionsFormatter(loggedInUser, theme);\n  }\n\n  getUrlsFormatter(loggedInUser?: CometChat.User): CometChatUrlsFormatter {\n    return this.dataSource.getUrlsFormatter(loggedInUser);\n  }\n\n  getMessagePreviewSubtitle(message: CometChat.BaseMessage): string {\n    return this.dataSource.getMessagePreviewSubtitle(message);\n  }\n\n  getReplyView(\n    message: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element | null {\n    return this.dataSource.getReplyView?.(message, theme, additionalParams) || null;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/ExtensionsDataSource.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\n\nabstract class ExtensionsDataSource {\n  abstract addExtension(): void;\n  abstract getExtensionId(): string;\n\n  enable(): void {\n    CometChat.isExtensionEnabled(this.getExtensionId()).then((enabled: Boolean) => {\n      if (enabled) this.addExtension();\n    });\n  }\n}\n\nexport { ExtensionsDataSource };\n\n// export interface ExtensionsDataSource {\n//     enable()\n// }\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/MessageDataSource.tsx",
    "content": "import React, { JSX } from \"react\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUIKit,\n  CometChatUrlsFormatter,\n  CometChatRichTextFormatter,\n} from \"../..\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport {\n  AdditionalAttachmentOptionsParams,\n  AdditionalAuxiliaryHeaderOptionsParams,\n  AdditionalAuxiliaryOptionsParams,\n  AdditionalParams,\n  MessageBubbleAlignmentType,\n} from \"../base/Types\";\nimport {\n  CometChatMessageTypes,\n  GroupMemberScope,\n  MentionsTargetElement,\n  MessageCategoryConstants,\n  MessageOptionConstants,\n  MessageTypeConstants,\n} from \"../constants/UIKitConstants\";\nimport { CometChatUiKitConstants } from \"../index\";\nimport { CometChatMessageComposerAction } from \"../helper/types\";\nimport { Icon } from \"../icons/Icon\";\nimport { CometChatMessageOption } from \"../modals/CometChatMessageOption\";\nimport { CometChatMessageTemplate } from \"../modals/CometChatMessageTemplate\";\nimport { CometChatConversationUtils } from \"../utils/conversationUtils\";\nimport { CometChatAudioBubble } from \"../views/CometChatAudioBubble\";\nimport { CometChatDeletedBubble } from \"../views/CometChatDeletedBubble\";\nimport { CometChatFileBubble } from \"../views/CometChatFileBubble\";\nimport { CometChatImageBubble } from \"../views/CometChatImageBubble\";\nimport { CometChatMessagePreview } from \"../utils/CometChatMessagePreview\";\nimport { CometChatTextBubble } from \"../views/CometChatTextBubble\";\nimport { CometChatVideoBubble } from \"../views/CometChatVideoBubble\";\nimport CometChatAIAssistantMessageBubble from '../views/CometChatAIAssistantMessageBubble/CometChatAIAssistantMessageBubble';\nimport CometChatStreamMessageBubble from '../views/CometChatStreamMessageBubble/CometChatStreamMessageBubble';\nimport { ChatConfigurator } from \"./ChatConfigurator\";\nimport { DataSource } from \"./DataSource\";\nimport { CommonUtils } from \"../utils/CommonUtils\";\nimport { DimensionValue, TouchableOpacity, ViewStyle, View, Text, Platform } from \"react-native\";\nimport Clipboard from \"@react-native-clipboard/clipboard\";\nimport { getCometChatTranslation } from \"../resources/CometChatLocalizeNew/LocalizationManager\";\nimport { CometChatMessageEvents } from \"../events/CometChatMessageEvents\";\n\nconst t = getCometChatTranslation();\n\nexport enum MentionContext {\n  Incoming = 'incoming',\n  Outgoing = 'outgoing',\n}\n\nfunction isAudioMessage(message: CometChat.BaseMessage): message is CometChat.MediaMessage {\n  return (\n    message.getCategory() == CometChat.CATEGORY_MESSAGE &&\n    message.getType() == CometChat.MESSAGE_TYPE.AUDIO\n  );\n}\n\nfunction isVideoMessage(message: CometChat.BaseMessage): message is CometChat.MediaMessage {\n  return (\n    message.getCategory() == CometChat.CATEGORY_MESSAGE &&\n    message.getType() == CometChat.MESSAGE_TYPE.VIDEO\n  );\n}\n\nfunction isFileMessage(message: CometChat.BaseMessage): message is CometChat.MediaMessage {\n  return (\n    message.getCategory() == CometChat.CATEGORY_MESSAGE &&\n    message.getType() == CometChat.MESSAGE_TYPE.FILE\n  );\n}\n\nfunction isActionMessage(message: CometChat.BaseMessage): message is CometChat.Action {\n  return message.getCategory() == CometChat.CATEGORY_ACTION;\n}\n\nfunction isTextMessage(message: CometChat.BaseMessage): message is CometChat.TextMessage {\n  return (\n    message.getCategory() == CometChat.CATEGORY_MESSAGE &&\n    message.getType() == CometChat.MESSAGE_TYPE.TEXT\n  );\n}\n\nfunction isImageMessage(message: CometChat.BaseMessage): message is CometChat.MediaMessage {\n  return (\n    message.getCategory() == CometChat.CATEGORY_MESSAGE &&\n    message.getType() == CometChat.MESSAGE_TYPE.IMAGE\n  );\n}\n\nfunction isDeletedMessage(message: CometChat.BaseMessage): boolean {\n  return message.getDeletedBy() != null;\n}\n\nexport class MessageDataSource implements DataSource {\n\n  // --- AI/Tool/Stream Bubble Implementations ---\n  getAgentAssistantMessageBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element {\n    return <CometChatAIAssistantMessageBubble message={message} theme={theme} />;\n  }\n\n  getStreamMessageBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element {\n    return <CometChatStreamMessageBubble key={message.getId() + message.getType()} message={message} theme={theme} />;\n  }\n\n  handleCopy = (message: CometChat.BaseMessage) => {\n    try {\n      let textToCopy = \"\";\n\n      if (isTextMessage(message)) {\n        textToCopy = message.getText();\n      } else {\n        const messageData = message as any;\n        textToCopy = messageData.data?.text ||\n          messageData.data?.content ||\n          messageData.text ||\n          messageData.content || \"\";\n      }\n\n      if (textToCopy?.trim()) {\n        Clipboard.setString(textToCopy);\n      }\n    } catch (err) {\n      console.error(err);\n    }\n  };\n\n\n  getAgentAssistantMessageTemplate(theme: CometChatTheme): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: 'assistant',\n      category: 'agentic',\n      ContentView: (message: CometChat.BaseMessage) => \n        this.getAgentAssistantMessageBubble(message, theme),\n\n      options: undefined,\n\n      FooterView: (message: CometChat.BaseMessage) => (\n        <TouchableOpacity onPress={() => this.handleCopy(message as CometChat.AIAssistantMessage)}>\n          <Icon name=\"ai-copy-option\" width={24} height={24} containerStyle={{marginLeft: 10, marginBottom: -20}} color={theme.color.textSecondary} />\n        </TouchableOpacity>\n      ),\n    });\n  }\n\n  getStreamMessageTemplate(theme: CometChatTheme, additionalParams?: AdditionalParams): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: CometChatUiKitConstants.streamMessageTypes.run_started,\n      category: MessageCategoryConstants.stream,\n      ContentView: (message: CometChat.BaseMessage) => this.getStreamMessageBubble(message, theme),\n      options: undefined,\n      FooterView: undefined,\n    });\n  }\n  getEditOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.editMessage,\n      title: t(\"EDIT\"),\n      icon: (\n        <Icon\n          name='edit'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n\n  getDeleteOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.deleteMessage,\n      title: t(\"DELETE\"),\n      icon: (\n        <Icon\n          name='delete'\n          color={theme.color.error}\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n      style: {\n        titleStyle: {\n          color: theme.color.error,\n        },\n      },\n    };\n  }\n  getReplyOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.replyMessage,\n      title: t(\"REPLY\"),\n      icon: (\n        <Icon\n          name='reply'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n  getReplyInThreadOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.replyInThread,\n      title: t(\"REPLY_IN_THREAD\"),\n      icon: (\n        <Icon\n          name='subdirectory-arrow-right'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n\n  getReportOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.reportMessage,\n      title: t(\"Message_List_Option_Flag_Message\"),\n      icon: (\n        <Icon\n          name='info'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n\n  getShareOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.shareMessage,\n      title: t(\"SHARE\"),\n      icon: (\n        <Icon\n          name='share'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n  getCopyOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.copyMessage,\n      title: t(\"COPY\"),\n      icon: (\n        <Icon\n          name='content-copy'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n\n  getMarkAsUnreadOption(theme: CometChatTheme): CometChatMessageOption {\n      return {\n      id: MessageOptionConstants.markAsUnread,\n      title: t(\"MARK_AS_UNREAD\"),\n      icon: (\n        <Icon\n          name='unread'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n  // getForwardOption(): CometChatMessageOption {\n  //     return {\n  //         id: MessageOptionConstants.forwardMessage,\n  //         title: t(\"FORWARD\"),\n  //         icon: ICONS.FORWARD\n  //     }\n  // }\n  getInformationOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.messageInformation,\n      title: t(\"INFO\"),\n      icon: (\n        <Icon\n          name='info'\n          color={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.tintColor\n          }\n          height={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.height}\n          width={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle?.width}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n\n  getPrivateMessageOption(theme: CometChatTheme): CometChatMessageOption {\n    return {\n      id: MessageOptionConstants.sendMessagePrivately,\n      title: t(\"MESSAGE_PRIVATELY\"),\n      icon: (\n        <Icon\n          name='reply'\n          imageStyle={theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconStyle}\n          containerStyle={\n            theme.messageListStyles.messageOptionsStyles?.optionsItemStyle?.iconContainerStyle\n          }\n        ></Icon>\n      ),\n    };\n  }\n\n  isSentByMe(loggedInUser: CometChat.User, message: CometChat.BaseMessage) {\n    if (!loggedInUser) return false;\n    return loggedInUser.getUid() == message?.getSender()?.getUid();\n  }\n\n  getTextMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let messageOptionList: CometChatMessageOption[] = [];\n\n    if (isDeletedMessage(messageObject)) return messageOptionList;\n\n    // reply in thread\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.replyInThread,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getReplyInThreadOption(theme));\n    }\n\n    // reply\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.replyMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getReplyOption(theme));\n    }\n\n    // share\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.shareMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getShareOption(theme));\n    }\n\n    // copy\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.copyMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getCopyOption(theme));\n    }\n\n    // mark as unread\n    if(\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.markAsUnread,\n        group,\n        additionalParams,\n\n      )\n    ) {\n      messageOptionList.push(this.getMarkAsUnreadOption(theme));\n    }\n\n    // report\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.reportMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getReportOption(theme));\n    }\n\n    // message privately\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.sendMessagePrivately,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getPrivateMessageOption(theme));\n    }\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.editMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getEditOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.messageInformation,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getInformationOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.deleteMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getDeleteOption(theme));\n    }\n\n    return messageOptionList;\n  }\n\n  getAudioMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let optionsList: Array<CometChatMessageOption> = [];\n    if (!isDeletedMessage(messageObject))\n      optionsList.push(\n        ...ChatConfigurator.dataSource.getCommonOptions(\n          loggedInUser,\n          messageObject,\n          theme,\n          group,\n          additionalParams\n        )\n      );\n    return optionsList;\n  }\n  getVideoMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let optionsList: Array<CometChatMessageOption> = [];\n    if (!isDeletedMessage(messageObject))\n      optionsList.push(\n        ...ChatConfigurator.dataSource.getCommonOptions(\n          loggedInUser,\n          messageObject,\n          theme,\n          group,\n          additionalParams\n        )\n      );\n    return optionsList;\n  }\n  getImageMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let optionsList: Array<CometChatMessageOption> = [];\n    if (!isDeletedMessage(messageObject))\n      optionsList.push(\n        ...ChatConfigurator.dataSource.getCommonOptions(\n          loggedInUser,\n          messageObject,\n          theme,\n          group,\n          additionalParams\n        )\n      );\n    return optionsList;\n  }\n  getFileMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let optionsList: Array<CometChatMessageOption> = [];\n    if (!isDeletedMessage(messageObject))\n      optionsList.push(\n        ...ChatConfigurator.dataSource.getCommonOptions(\n          loggedInUser,\n          messageObject,\n          theme,\n          group,\n          additionalParams\n        )\n      );\n    return optionsList;\n  }\n  getMessageOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let optionsList: Array<CometChatMessageOption> = [];\n    if (isDeletedMessage(messageObject)) return optionsList;\n    if (messageObject.getCategory() == MessageCategoryConstants.message) {\n      let type: string = messageObject.getType();\n      switch (type) {\n        case MessageTypeConstants.audio:\n          optionsList.push(\n            ...ChatConfigurator.dataSource.getAudioMessageOptions(\n              loggedInUser,\n              messageObject,\n              theme,\n              group,\n              additionalParams\n            )\n          );\n          break;\n        case MessageTypeConstants.video:\n          optionsList.push(\n            ...ChatConfigurator.dataSource.getVideoMessageOptions(\n              loggedInUser,\n              messageObject,\n              theme,\n              group,\n              additionalParams\n            )\n          );\n          break;\n        case MessageTypeConstants.image:\n          optionsList.push(\n            ...ChatConfigurator.dataSource.getImageMessageOptions(\n              loggedInUser,\n              messageObject,\n              theme,\n              group,\n              additionalParams\n            )\n          );\n          break;\n        case MessageTypeConstants.text:\n          optionsList.push(\n            ...ChatConfigurator.dataSource.getTextMessageOptions(\n              loggedInUser,\n              messageObject,\n              theme,\n              group,\n              additionalParams\n            )\n          );\n          break;\n        case MessageTypeConstants.file:\n          optionsList.push(\n            ...ChatConfigurator.dataSource.getFileMessageOptions(\n              loggedInUser,\n              messageObject,\n              theme,\n              group,\n              additionalParams\n            )\n          );\n          break;\n      }\n    } else if (messageObject.getCategory() == MessageCategoryConstants.custom) {\n      optionsList.push(\n        ...ChatConfigurator.dataSource.getCommonOptions(\n          loggedInUser,\n          messageObject,\n          theme,\n          group,\n          additionalParams\n        )\n      );\n    } else if (messageObject.getCategory() == MessageCategoryConstants.interactive) {\n      let type: string = messageObject.getType();\n      //todo: unsupportedBubble\n    }\n    return optionsList;\n  }\n\n  private validateOption(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    optionId: string,\n    group?: CometChat.Group | null,\n    additionalParams?: AdditionalParams\n  ): boolean {\n    if (\n      MessageOptionConstants.replyMessage === optionId &&\n      !additionalParams?.hideReplyOption\n    ) {\n      return true;\n    }\n\n    if (\n      MessageOptionConstants.replyInThread === optionId &&\n      (!messageObject.getParentMessageId() || messageObject.getParentMessageId() === 0) &&\n      !additionalParams?.hideReplyInThreadOption\n    ) {\n      return true;\n    }\n\n    if (\n      MessageOptionConstants.shareMessage === optionId &&\n      (messageObject instanceof CometChat.TextMessage ||\n        messageObject instanceof CometChat.MediaMessage) &&\n      !additionalParams?.hideShareMessageOption\n    ) {\n      return true;\n    }\n\n    if (\n      MessageOptionConstants.copyMessage === optionId &&\n      messageObject instanceof CometChat.TextMessage &&\n      !additionalParams?.hideCopyMessageOption\n    ) {\n      return true;\n    }\n\n    let isSentByMe: boolean = this.isSentByMe(loggedInUser, messageObject);\n\n    if (\n      MessageOptionConstants.messageInformation === optionId &&\n      isSentByMe &&\n      !additionalParams?.hideMessageInfoOption\n    ) {\n      return true;\n    }\n\n    let memberIsNotParticipant: boolean = !!(\n      group &&\n      (group.getOwner() === loggedInUser.getUid() ||\n        group.getScope() !== GroupMemberScope.participant)\n    );\n\n    if (\n      MessageOptionConstants.deleteMessage === optionId &&\n      (isSentByMe || memberIsNotParticipant) &&\n      !additionalParams?.hideDeleteMessageOption\n    ) {\n      return true;\n    }\n\n    if (\n      MessageOptionConstants.editMessage === optionId &&\n      (isSentByMe || memberIsNotParticipant) &&\n      !additionalParams?.hideEditMessageOption\n    ) {\n      return true;\n    }\n\n    if (\n      MessageOptionConstants.sendMessagePrivately === optionId &&\n      group &&\n      loggedInUser.getUid() != messageObject.getSender()?.getUid() &&\n      !additionalParams?.hideMessagePrivatelyOption\n    ) {\n      return true;\n    }\n\n    if (\n      MessageOptionConstants.reportMessage === optionId &&\n      !isSentByMe &&\n      !additionalParams?.hideReportMessageOption\n    ) {\n      return true;\n    }\n   \n\n    if (\n      MessageOptionConstants.markAsUnread === optionId &&\n      !isSentByMe &&\n      !additionalParams?.hideMarkAsUnreadOption\n    ) {\n      return true;\n    }\n\n    return false;\n  }\n\n  getCommonOptions(\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageOption[] {\n    let messageOptionList: CometChatMessageOption[] = [];\n\n    if (isDeletedMessage(messageObject)) return messageOptionList;\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.replyMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getReplyOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.replyInThread,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getReplyInThreadOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.shareMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getShareOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.messageInformation,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getInformationOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.deleteMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getDeleteOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.reportMessage,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getReportOption(theme));\n    }\n\n    if (\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.sendMessagePrivately,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getPrivateMessageOption(theme));\n    }\n\n    if(\n      this.validateOption(\n        loggedInUser,\n        messageObject,\n        MessageOptionConstants.markAsUnread,\n        group,\n        additionalParams\n      )\n    ) {\n      messageOptionList.push(this.getMarkAsUnreadOption(theme));\n    }\n\n    return messageOptionList;\n  }\n\n  /**\n * Returns a localized group action message string for group events (added, kicked, banned, etc.)\n */\n  getActionMessage(message: any): string {\n\n    let actionMessage = \"\";\n\n    if (!message || typeof message !== \"object\") {\n      return \"\";\n    }\n\n    const action =\n      message.action ||\n      message.data?.action ||\n      message.rawMessage?.action;\n\n    const actionBy = message.actionBy || message.rawMessage?.actionBy;\n    const actionOn = message.actionOn || message.rawMessage?.actionOn;\n\n    // Do NOT require actionOn for JOINED/LEFT.\n    const requiresActionOn = action !== \"joined\" && action !== \"left\";\n\n    if (!actionBy || (requiresActionOn && !actionOn)) {\n      return message.message || \"\";\n    }\n\n    // Names (JOINED/LEFT only need byName)\n    const byName = actionBy?.name || \"User\";\n    const onName = requiresActionOn ? (actionOn?.name || \"User\") : \"\";\n\n    const GroupMemberAction = {\n      ADDED: \"added\",\n      JOINED: \"joined\",\n      LEFT: \"left\",\n      KICKED: \"kicked\",\n      BANNED: \"banned\",\n      UNBANNED: \"unbanned\",\n      SCOPE_CHANGE: \"scopeChanged\",\n    } as const;\n\n    switch (action) {\n      case GroupMemberAction.ADDED:\n        // Use template string with placeholders for names\n        actionMessage = t(\"MESSAGE_LIST_ACTION_ADDED\").replace(\"${byName}\", byName).replace(\"${onName}\", onName);\n        // Fallback to simpler format if template is missing\n        if (actionMessage === \"MESSAGE_LIST_ACTION_ADDED\") {\n          actionMessage = `${byName} ${t(\"ADDED\")} ${onName}`;\n        }\n        break;\n\n      case GroupMemberAction.JOINED:\n        // No onName needed\n        actionMessage = t(\"MESSAGE_LIST_ACTION_JOINED\").replace(\"${byName}\", byName);\n        if (actionMessage === \"MESSAGE_LIST_ACTION_JOINED\") {\n          actionMessage = `${byName} ${t(\"JOINED\")}`;\n        }\n        break;\n\n      case GroupMemberAction.LEFT:\n        // No onName needed\n        actionMessage = t(\"MESSAGE_LIST_ACTION_LEFT\").replace(\"${byName}\", byName);\n        if (actionMessage === \"MESSAGE_LIST_ACTION_LEFT\") {\n          actionMessage = `${byName} ${t(\"LEFT\")}`;\n        }\n        break;\n\n      case GroupMemberAction.KICKED:\n        actionMessage = t(\"MESSAGE_LIST_ACTION_KICKED\").replace(\"${byName}\", byName).replace(\"${onName}\", onName);\n        if (actionMessage === \"MESSAGE_LIST_ACTION_KICKED\") {\n          actionMessage = `${byName} ${t(\"KICKED\")} ${onName}`;\n        }\n        break;\n\n      case GroupMemberAction.BANNED:\n        actionMessage = t(\"MESSAGE_LIST_ACTION_BANNED\").replace(\"${byName}\", byName).replace(\"${onName}\", onName);\n        if (actionMessage === \"MESSAGE_LIST_ACTION_BANNED\") {\n          actionMessage = `${byName} ${t(\"BANNED\")} ${onName}`;\n        }\n        break;\n\n      case GroupMemberAction.UNBANNED:\n        actionMessage = t(\"MESSAGE_LIST_ACTION_UNBANNED\").replace(\"${byName}\", byName).replace(\"${onName}\", onName);\n        if (actionMessage === \"MESSAGE_LIST_ACTION_UNBANNED\") {\n          actionMessage = `${byName} ${t(\"UNBANNED\")} ${onName}`;\n        }\n        break;\n\n      case GroupMemberAction.SCOPE_CHANGE: {\n        const newScope =\n          message.newScope ||\n          message.data?.extras?.scope?.new ||\n          message.rawMessage?.data?.extras?.scope?.new ||\n          \"\";\n\n        const translatedRole = newScope ? t(newScope.toUpperCase()) : \"\";\n\n        // Template with three placeholders\n        actionMessage = t(\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\")\n          .replace(\"${byName}\", byName)\n          .replace(\"${onName}\", onName)\n          .replace(\"${role}\", translatedRole);\n\n        if (actionMessage === \"MESSAGE_LIST_ACTION_SCOPE_CHANGED\") {\n          actionMessage = `${byName} ${t(\"MADE\")} ${onName} ${translatedRole}`.trim();\n        }\n        break;\n      }\n\n      default:\n        actionMessage = message.message || \"\";\n        break;\n    }\n\n    return actionMessage;\n  }\n\n  getGroupActionBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element | null {\n    if (isActionMessage(message)) {\n      const messageText = this.getActionMessage(message)\n      return (\n        <CometChatTextBubble\n          text={messageText}\n          textContainerStyle={theme.messageListStyles?.groupActionBubbleStyles?.textContainerStyle}\n          textStyle={theme?.messageListStyles?.groupActionBubbleStyles?.textStyle}\n        />\n      );\n    }\n    return null;\n  }\n\n  getBottomView(\n    message: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ): JSX.Element | null {\n    return null;\n  }\n\n  getReplyView(\n    message: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element | null {\n    const hasQuotedMessage = message.getQuotedMessage();\n    \n    if (!hasQuotedMessage || \n        message instanceof CometChat.Action || \n        message.getDeletedAt()) {\n      return null;\n    }\n\n    // Check if the original message is outgoing to determine styling\n    const loggedInUser = CometChatUIKit.loggedInUser;\n    const isOutgoingMessage = loggedInUser && message.getSender().getUid() === loggedInUser.getUid();\n    \n    const isQuotedMessageDeleted = hasQuotedMessage.getDeletedBy() != null;\n    \n    // Create custom theme with overridden colors for reply view\n    const replyTheme = {\n      ...theme,\n      color: {\n        ...theme.color,\n        textPrimary: isOutgoingMessage ? theme.color.staticWhite : theme.color.textHighlight,\n        textSecondary: isOutgoingMessage ? theme.color.staticWhite : theme.color.textSecondary,\n      }\n    };\n\n    // Handle click to navigate to quoted message\n    const handleReplyClick = () => {\n      if (!isQuotedMessageDeleted) {\n        const messageId = String(hasQuotedMessage.getId());\n        if (additionalParams?.onReplyClick) {\n          additionalParams.onReplyClick(messageId);\n        }\n      }\n    };\n\n    // Mentions style for reply preview — derived from theme tokens so customers can\n    // override via CometChatThemeProvider. Uses same tokens as outgoing bubble mentions.\n    const replyMentionsStyle = isOutgoingMessage\n      ? {\n          textStyle: { color: theme.color.sendBubbleTextHighlight },\n          backgroundColor: 'rgba(255, 255, 255, 0.25)',\n        }\n      : undefined;\n\n    const previewComponent = (\n      <CometChatMessagePreview\n        message={hasQuotedMessage}\n        theme={replyTheme}\n        style={{\n          backgroundColor: isOutgoingMessage ? theme.color.extendedPrimary800 : theme.color.neutral400,\n          borderRadius: 8,\n          borderWidth: 0,\n          borderLeftWidth: 3,\n          borderLeftColor: isOutgoingMessage ? theme.color.staticWhite : theme.color.borderHighlight,\n          margin:2,\n          width:\"98%\"\n        }}\n        showCloseIcon={false}\n        isDeletedMessage={isQuotedMessageDeleted}\n        mentionsStyle={replyMentionsStyle}\n      />\n    );\n\n    // Wrap with TouchableOpacity for click handling\n    if (additionalParams?.onReplyClick && !isQuotedMessageDeleted) {\n      const replyTouchableStyle = { padding: theme.spacing.padding.p0_5 };\n      return (\n        <TouchableOpacity onPress={handleReplyClick} activeOpacity={0.7} style={replyTouchableStyle}>\n          {previewComponent}\n        </TouchableOpacity>\n      );\n    }\n\n    return previewComponent;\n  }\n\n  getDeleteMessageBubble(message: CometChat.BaseMessage, theme: CometChatTheme): JSX.Element {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n\n    const _style =\n      loggedInUser && message.getSender().getUid() === loggedInUser.getUid()\n        ? theme.messageListStyles.outgoingMessageBubbleStyles\n        : theme.messageListStyles.incomingMessageBubbleStyles;\n    return <CometChatDeletedBubble style={_style?.deletedBubbleStyles} />;\n  }\n\n  getVideoMessageBubble(\n    videoUrl: string,\n    thumbnailUrl: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element | null {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    if (isVideoMessage(message)) {\n      const _style =\n        message.getSender().getUid() === loggedInUser!.getUid()\n          ? theme.messageListStyles.outgoingMessageBubbleStyles?.videoBubbleStyles\n          : theme.messageListStyles.incomingMessageBubbleStyles?.videoBubbleStyles;\n      return (\n        <CometChatVideoBubble\n          videoUrl={videoUrl}\n          thumbnailUrl={{ uri: thumbnailUrl }}\n          imageStyle={_style?.imageStyle}\n          playIcon={_style?.playIcon}\n          playIconStyle={_style?.playIconStyle}\n          playIconContainerStyle={_style?.playIconContainerStyle}\n          placeholderImage={_style?.placeholderImage}\n        />\n      );\n    }\n    return null;\n  }\n\n  getTextMessageBubble(\n    messageText: string,\n    message: CometChat.TextMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    let mentionedUsers = message.getMentionedUsers();\n    let textFormatters = [...(additionalParams?.textFormatters || [])];\n    const isMessageSentByLoggedInUser = message.getSender().getUid() === loggedInUser!.getUid();\n    const _style: Partial<CometChatTheme[\"textBubbleStyles\"]> = isMessageSentByLoggedInUser\n      ? (theme.messageListStyles.outgoingMessageBubbleStyles\n          .textBubbleStyles as CometChatTheme[\"textBubbleStyles\"])\n      : (theme.messageListStyles.incomingMessageBubbleStyles\n          .textBubbleStyles as CometChatTheme[\"textBubbleStyles\"]);\n\n    let linksTextFormatter = ChatConfigurator.getDataSource().getUrlsFormatter(loggedInUser!);\n    let mentionsTextFormatter = ChatConfigurator.getDataSource().getMentionsFormatter(\n      loggedInUser!,\n      theme\n    );\n\n    // Create rich text formatter for markdown parsing\n    let richTextFormatter = new CometChatRichTextFormatter(loggedInUser!);\n    richTextFormatter.setMessage(message);\n    richTextFormatter.setId(\"ccDefaultRichTextFormatterId\");\n    // Use same link color as CometChatUrlsFormatter for consistency\n    // Inline code styles differ for sent vs received bubbles per Figma spec\n    richTextFormatter.setStyle({\n      linkStyle: {\n        color: isMessageSentByLoggedInUser ? theme.color.sendBubbleLink : theme.color.receiveBubbleLink,\n        textDecorationLine: 'underline',\n      },\n      inlineCodeStyle: isMessageSentByLoggedInUser\n        ? {\n            fontSize: theme.typography.body.regular.fontSize,\n            fontWeight: '400',\n            lineHeight: ((theme.typography.body.regular.fontSize as number) ?? 14) * 1.2,\n            color: theme.color.sendBubbleTextHighlight,\n          }\n        : {\n            fontSize: theme.typography.body.regular.fontSize,\n            fontWeight: '400',\n            lineHeight: ((theme.typography.body.regular.fontSize as number) ?? 14) * 1.2,\n            color: theme.color.receiveBubbleTextHighlight,\n          },\n      inlineCodeContainerStyle: isMessageSentByLoggedInUser\n        ? {\n            backgroundColor: `${String(theme.color.extendedPrimary50)}33`, // 20% opacity\n            borderRadius: 2,\n            paddingHorizontal: 2,\n            paddingVertical: 0,\n          }\n        : {\n            backgroundColor: theme.color.background3,\n            borderRadius: 2,\n            paddingHorizontal: 2,\n            paddingVertical: 0,\n          },\n      // Code block styles per Figma spec — monospace font in a rounded container\n      codeBlockStyle: {\n        fontFamily: Platform.OS === \"ios\" ? \"Menlo\" : \"monospace\",\n        fontSize: 13,\n        color: isMessageSentByLoggedInUser\n          ? theme.color.sendBubbleText\n          : theme.color.receiveBubbleText,\n      },\n      codeBlockContainerStyle: isMessageSentByLoggedInUser\n        ? {\n            backgroundColor: 'rgba(255,255,255,0.1)',\n            borderRadius: 4,\n            borderWidth: 1,\n            borderColor: 'rgba(255,255,255,0.2)',\n            padding: 12,\n          }\n        : {\n            backgroundColor: theme.color.background2,\n            borderRadius: 4,\n            borderWidth: 1,\n            borderColor: theme.color.borderDefault,\n            padding: 12,\n          },\n      // Blockquote styles per Figma spec — rounded container with left bar\n      blockquoteContainerStyle: isMessageSentByLoggedInUser\n        ? {\n            backgroundColor: 'rgba(255,255,255,0.2)',\n            borderRadius: theme.spacing.radius.r2,\n          }\n        : {\n            backgroundColor: theme.color.background3,\n            borderRadius: theme.spacing.radius.r2,\n          },\n      blockquoteBarStyle: isMessageSentByLoggedInUser\n        ? {\n            backgroundColor: 'rgba(255,255,255,0.6)',\n          }\n        : {\n            backgroundColor: theme.color.primary,\n          },\n    });\n\n    mentionsTextFormatter.setContext(isMessageSentByLoggedInUser ? MentionContext.Outgoing : MentionContext.Incoming);\n    linksTextFormatter.setMessage(message);\n    linksTextFormatter.setId(\"ccDefaultUrlsFormatterId\");\n    linksTextFormatter.setStyle({ linkTextColor: theme.color.receiveBubbleLink });\n    if (isMessageSentByLoggedInUser) {\n      linksTextFormatter.setStyle({ linkTextColor: theme.color.sendBubbleText });\n    }\n\n    if (!additionalParams?.disableMentions && mentionedUsers && mentionedUsers.length) {\n      mentionsTextFormatter.setLoggedInUser(loggedInUser!);\n      mentionsTextFormatter.setMessage(message);\n      mentionsTextFormatter.setId(\"ccDefaultMentionFormatterId\");\n    }\n\n    let finalFormatters: CometChatTextFormatter[] = [];\n\n    let urlFormatterExists = false;\n    let mentionsFormatterExists = false;\n    let richTextFormatterExists = false;\n\n    for (const formatter of textFormatters) {\n      if (formatter instanceof CometChatUrlsFormatter) {\n        urlFormatterExists = true;\n      }\n\n      if (formatter instanceof CometChatMentionsFormatter) {\n        mentionsFormatterExists = true;\n        formatter.setMessage(message);\n        formatter.setTargetElement(MentionsTargetElement.textbubble);\n        formatter.setLoggedInUser(CometChatUIKit.loggedInUser!);\n        formatter.setContext(isMessageSentByLoggedInUser ? \"outgoing\" : \"incoming\");\n      }\n\n      if (formatter instanceof CometChatRichTextFormatter) {\n        richTextFormatterExists = true;\n      }\n\n      formatter.setMessage(message);\n      finalFormatters.push(CommonUtils.clone(formatter));\n      if (urlFormatterExists && mentionsFormatterExists && richTextFormatterExists) {\n        break;\n      }\n    }\n\n    // Add rich text formatter first (to parse markdown before other formatters)\n    if (!richTextFormatterExists) {\n      finalFormatters.unshift(richTextFormatter);\n    }\n\n    if (!urlFormatterExists) {\n      finalFormatters.push(linksTextFormatter);\n    }\n    if (!mentionsFormatterExists) {\n      finalFormatters.push(mentionsTextFormatter);\n    }\n    return (\n      <CometChatTextBubble\n        text={messageText}\n        textStyle={_style?.textStyle}\n        textFormatters={finalFormatters}\n      />\n    );\n  }\n\n  getImageMessageBubble(\n    imageUrl: string,\n    caption: string,\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    if (isImageMessage(message)) {\n      const _style =\n        message.getSender().getUid() === loggedInUser!.getUid()\n          ? theme.messageListStyles.outgoingMessageBubbleStyles?.imageBubbleStyles\n          : theme.messageListStyles.incomingMessageBubbleStyles?.imageBubbleStyles;\n\n      return <CometChatImageBubble imageUrl={{ uri: imageUrl }} style={_style?.imageStyle} />;\n    }\n    return <></>;\n  }\n\n  getAudioMessageBubble(\n    audioUrl: string,\n    title: string,\n    style: any, //ToDoM: remove any\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    if (isAudioMessage(message)) {\n      const _style =\n        message.getSender().getUid() === loggedInUser!.getUid()\n          ? theme.messageListStyles.outgoingMessageBubbleStyles?.audioBubbleStyles\n          : theme.messageListStyles.incomingMessageBubbleStyles?.audioBubbleStyles;\n      return (\n        <CometChatAudioBubble\n          audioUrl={audioUrl}\n          //title={title}\n          playViewContainerStyle={_style?.playViewContainerStyle}\n          playIconStyle={_style?.playIconStyle}\n          playIconContainerStyle={_style?.playIconContainerStyle}\n          waveStyle={_style?.waveStyle}\n          waveContainerStyle={_style?.waveContainerStyle}\n          playProgressTextStyle={_style?.playProgressTextStyle}\n        />\n      );\n    }\n    return <></>;\n  }\n\n  getFileMessageBubble(\n    fileUrl: string,\n    title: string,\n    style: any, //ToDoM: remove any\n    message: CometChat.MediaMessage,\n    theme: CometChatTheme\n  ): JSX.Element {\n    let loggedInUser = CometChatUIKit.loggedInUser;\n    if (isFileMessage(message)) {\n      const metaData = {\n        attachmentObject: message.getAttachment(),\n        timeStamp: message.getSentAt(),\n      };\n\n      let subtitle: string = \"\";\n\n      if (\n        metaData.attachmentObject &&\n        Object.keys(metaData.attachmentObject).length &&\n        metaData.timeStamp\n      ) {\n        const timestamp = metaData.timeStamp * 1000;\n        const date = new Date(timestamp);\n\n        // Format the date as \"15 Oct, 2024\"\n        const formattedDate = date\n          .toLocaleDateString(\"en-GB\", {\n            day: \"2-digit\",\n            month: \"short\",\n            year: \"numeric\",\n          })\n          .replace(/(\\w{3}) (\\d{4})/, \"$1, $2\");\n\n        const attachmentObject = metaData.attachmentObject;\n        let fileSizeInKB, extension, fileType;\n        if (attachmentObject && Object.keys(attachmentObject).length) {\n          fileSizeInKB = Math.round(attachmentObject.getSize() / 1024);\n          extension =\n            attachmentObject.getExtension() && attachmentObject.getExtension().toUpperCase();\n          fileType = extension === \"PDF\" ? \"PDF\" : extension;\n        }\n        subtitle = `${formattedDate} • ${fileSizeInKB ? fileSizeInKB + \" KB\" : \"----\"} • ${\n          fileType ? fileType : \"----\"\n        }`;\n      }\n\n      const _style =\n        message.getSender().getUid() === loggedInUser!.getUid()\n          ? theme.messageListStyles.outgoingMessageBubbleStyles?.fileBubbleStyles\n          : theme.messageListStyles.incomingMessageBubbleStyles?.fileBubbleStyles;\n      return (\n        <CometChatFileBubble\n          fileUrl={fileUrl}\n          title={title}\n          titleStyle={_style?.titleStyle}\n          subtitleStyle={_style?.subtitleStyle}\n          downloadIcon={_style?.downloadIcon}\n          downloadIconStyle={_style?.downloadIconStyle}\n          subtitle={subtitle}\n        />\n      );\n    }\n    return <></>;\n  }\n  getTextMessageContentView(\n    message: CometChat.TextMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): JSX.Element {\n    return ChatConfigurator.dataSource.getTextMessageBubble(\n      message.getText(),\n      message,\n      alignment,\n      theme,\n      additionalParams\n    );\n  }\n  getAudioMessageContentView(\n    message: CometChat.MediaMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element {\n    let attachment = message.getAttachment();\n    return ChatConfigurator.dataSource.getAudioMessageBubble(\n      attachment.getUrl(),\n      attachment.getName(),\n      {},\n      message,\n      theme\n    );\n  }\n  getVideoMessageContentView(\n    message: CometChat.MediaMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element | null {\n    let attachment = message.getAttachment();\n    return ChatConfigurator.dataSource.getVideoMessageBubble(\n      attachment.getUrl(),\n      \"\",\n      message,\n      theme\n    );\n  }\n  getImageMessageContentView(\n    message: CometChat.MediaMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element | null {\n    let attachment = message.getAttachment();\n    let url: string = attachment.getUrl();\n    if (url == undefined) url = message[\"data\"][\"url\"];\n\n    return ChatConfigurator.dataSource.getImageMessageBubble(\n      url,\n      attachment.getName(),\n      message,\n      theme\n    );\n  }\n  getFileMessageContentView(\n    message: CometChat.MediaMessage,\n    alignment: MessageBubbleAlignmentType,\n    theme: CometChatTheme\n  ): JSX.Element {\n    let attachment = message.getAttachment();\n    return ChatConfigurator.dataSource.getFileMessageBubble(\n      attachment.getUrl(),\n      attachment.getName(),\n      {},\n      message,\n      theme\n    );\n  }\n\n  getTextMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.text,\n      category: MessageCategoryConstants.message,\n      ContentView: (message: CometChat.BaseMessage, _alignment: MessageBubbleAlignmentType) => {\n        if (isDeletedMessage(message)) {\n          return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n        } else {\n          return ChatConfigurator.dataSource.getTextMessageContentView(\n            message,\n            _alignment,\n            theme,\n            additionalParams\n          );\n        }\n      },\n      options: (loggedInuser, message, theme, group) =>\n        ChatConfigurator.dataSource.getTextMessageOptions(\n          loggedInuser,\n          message,\n          theme,\n          group,\n          additionalParams\n        ),\n      ReplyView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        const replyView = ChatConfigurator.dataSource.getReplyView?.(message, theme, additionalParams) || null;\n        return replyView;\n      },\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getBottomView(message, alignment);\n      },\n    });\n  }\n\n  getAudioMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.audio,\n      category: MessageCategoryConstants.message,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        if (isDeletedMessage(message)) {\n          return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n        } else\n          return ChatConfigurator.dataSource.getAudioMessageContentView(message, alignment, theme);\n      },\n      options: (loggedInuser, message, theme, group) =>\n        ChatConfigurator.dataSource.getAudioMessageOptions(\n          loggedInuser,\n          message,\n          theme,\n          group,\n          additionalParams\n        ),\n      ReplyView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getReplyView?.(message, theme, additionalParams) || null;\n      },\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getBottomView(message, alignment);\n      },\n    });\n  }\n  getVideoMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.video,\n      category: MessageCategoryConstants.message,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        if (isDeletedMessage(message)) {\n          return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n        } else\n          return ChatConfigurator.dataSource.getVideoMessageContentView(message, alignment, theme);\n      },\n      options: (loggedInuser, message, theme, group) =>\n        ChatConfigurator.dataSource.getVideoMessageOptions(\n          loggedInuser,\n          message,\n          theme,\n          group,\n          additionalParams\n        ),\n      ReplyView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getReplyView?.(message, theme, additionalParams) || null;\n      },\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getBottomView(message, alignment);\n      },\n    });\n  }\n  getImageMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.image,\n      category: MessageCategoryConstants.message,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        if (isDeletedMessage(message)) {\n          return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n        } else\n          return ChatConfigurator.dataSource.getImageMessageContentView(message, alignment, theme);\n      },\n      options: (loggedInuser, message, theme, group) =>\n        ChatConfigurator.dataSource.getImageMessageOptions(\n          loggedInuser,\n          message,\n          theme,\n          group,\n          additionalParams\n        ),\n      ReplyView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getReplyView?.(message, theme, additionalParams) || null;\n      },\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getBottomView(message, alignment);\n      },\n    });\n  }\n  getFileMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.file,\n      category: MessageCategoryConstants.message,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        if (isDeletedMessage(message)) {\n          return ChatConfigurator.dataSource.getDeleteMessageBubble(message, theme);\n        } else\n          return ChatConfigurator.dataSource.getFileMessageContentView(message, alignment, theme);\n      },\n      options: (loggedInuser, message, theme, group) =>\n        ChatConfigurator.dataSource.getFileMessageOptions(\n          loggedInuser,\n          message,\n          theme,\n          group,\n          additionalParams\n        ),\n      ReplyView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getReplyView?.(message, theme, additionalParams) || null;\n      },\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getBottomView(message, alignment);\n      },\n    });\n  }\n\n  getFormMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.form,\n      category: MessageCategoryConstants.interactive,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        const _style =\n          loggedInUser && message.getSender().getUid() === loggedInUser.getUid()\n            ? theme.messageListStyles.outgoingMessageBubbleStyles\n            : theme.messageListStyles.incomingMessageBubbleStyles;\n        return (\n          <CometChatDeletedBubble\n            text={t(\"NOT_SUPPORTED\") ?? \"This message type is not supported\"}\n            style={_style?.deletedBubbleStyles}\n          />\n        );\n      },\n      options: (loggedInuser, message, theme, group) => [],\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return <></>;\n      },\n    });\n  }\n\n  getSchedulerMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.scheduler,\n      category: MessageCategoryConstants.interactive,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        const _style =\n          loggedInUser && message.getSender().getUid() === loggedInUser.getUid()\n            ? theme.messageListStyles.outgoingMessageBubbleStyles\n            : theme.messageListStyles.incomingMessageBubbleStyles;\n        return (\n          <CometChatDeletedBubble\n            text={t(\"NOT_SUPPORTED\") ?? \"This message type is not supported\"}\n            style={_style?.deletedBubbleStyles}\n          />\n        );\n      },\n      options: (loggedInuser, message, theme, group) => [],\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return <></>;\n      },\n    });\n  }\n\n  getCardMessageTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.card,\n      category: MessageCategoryConstants.interactive,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        const loggedInUser = CometChatUIKit.loggedInUser;\n        const _style =\n          loggedInUser && message.getSender().getUid() === loggedInUser.getUid()\n            ? theme.messageListStyles.outgoingMessageBubbleStyles\n            : theme.messageListStyles.incomingMessageBubbleStyles;\n        return (\n          <CometChatDeletedBubble\n            text={t(\"NOT_SUPPORTED\") ?? \"This message type is not supported\"}\n            style={_style?.deletedBubbleStyles}\n          />\n        );\n      },\n      options: (loggedInuser, message, theme, group) => [],\n      BottomView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return <></>;\n      },\n    });\n  }\n\n  getGroupActionTemplate(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate {\n    return new CometChatMessageTemplate({\n      type: MessageTypeConstants.groupMember,\n      category: MessageCategoryConstants.action,\n      ContentView: (message: CometChat.BaseMessage, alignment: MessageBubbleAlignmentType) => {\n        return ChatConfigurator.dataSource.getGroupActionBubble(message, theme);\n      },\n    });\n  }\n\n  getAllMessageTemplates(\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate[] {\n    return [\n      ChatConfigurator.dataSource.getTextMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getAudioMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getVideoMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getFileMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getImageMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getGroupActionTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getGroupActionTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getFormMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getSchedulerMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getCardMessageTemplate(theme, additionalParams),\n      ChatConfigurator.dataSource.getAgentAssistantMessageTemplate(theme, additionalParams),\n    ];\n  }\n\n  getMessageTemplate(\n    messageType: string,\n    MessageCategory: string,\n    theme: CometChatTheme,\n    additionalParams?: AdditionalParams\n  ): CometChatMessageTemplate | null {\n    // let _theme: CometChatTheme = useContext(\"theme\")         ???\n    let template: CometChatMessageTemplate;\n\n    //in case of call message return undefined\n    if (MessageCategory == MessageCategoryConstants.call) return null;\n\n    switch (messageType) {\n      case MessageTypeConstants.text:\n        template = ChatConfigurator.dataSource.getTextMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.audio:\n        template = ChatConfigurator.dataSource.getAudioMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.video:\n        template = ChatConfigurator.dataSource.getVideoMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.groupActions:\n      case MessageTypeConstants.groupMember:\n        template = ChatConfigurator.dataSource.getGroupActionTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.file:\n        template = ChatConfigurator.dataSource.getFileMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.form:\n        template = ChatConfigurator.dataSource.getFormMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.scheduler:\n        template = ChatConfigurator.dataSource.getSchedulerMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.card:\n        template = ChatConfigurator.dataSource.getCardMessageTemplate(theme, additionalParams);\n        break;\n      case MessageTypeConstants.assistant:\n        template = ChatConfigurator.dataSource.getAgentAssistantMessageTemplate(theme, additionalParams);\n        break;\n      default:\n        return null;\n    }\n    return template;\n  }\n\n  getAllMessageTypes(): string[] {\n    return [\n      CometChatMessageTypes.text,\n      CometChatMessageTypes.image,\n      CometChatMessageTypes.audio,\n      CometChatMessageTypes.video,\n      CometChatMessageTypes.file,\n      MessageTypeConstants.groupActions,\n      MessageTypeConstants.groupMember,\n      MessageTypeConstants.form,\n      MessageTypeConstants.card,\n      MessageTypeConstants.scheduler,\n      MessageTypeConstants.assistant\n    ];\n  }\n  getAllMessageCategories(): string[] {\n    return [\n      MessageCategoryConstants.message,\n      MessageCategoryConstants.action,\n      MessageCategoryConstants.interactive,\n      MessageCategoryConstants.agentic\n    ];\n  }\n  getAuxiliaryOptions(\n    user: CometChat.User,\n    group: CometChat.Group,\n    id: Map<string, any>,\n    additionalAuxiliaryParams?: AdditionalAuxiliaryOptionsParams\n  ): JSX.Element[] {\n    return [];\n  }\n  getAuxiliaryHeaderAppbarOptions(\n    user?: CometChat.User,\n    group?: CometChat.Group,\n    additionalAuxiliaryHeaderOptionsParams?: AdditionalAuxiliaryHeaderOptionsParams\n  ): JSX.Element | null {\n    return null;\n  }\n  getId(): string {\n    return \"messageUtils\";\n  }\n  getMessageTypeToSubtitle(messageType: string): string {\n    let subtitle: string = messageType;\n    switch (messageType) {\n      case MessageTypeConstants.text:\n        subtitle = t(\"TEXT\");\n        break;\n      case MessageTypeConstants.image:\n        subtitle = t(\"MESSAGE_IMAGE\");\n        break;\n      case MessageTypeConstants.video:\n        subtitle = t(\"MESSAGE_VIDEO\");\n        break;\n      case MessageTypeConstants.file:\n        subtitle = t(\"MESSAGE_FILE\");\n        break;\n      case MessageTypeConstants.audio:\n        subtitle = t(\"MESSAGE_AUDIO\");\n        break;\n      default:\n        subtitle = messageType;\n        break;\n    }\n    return subtitle;\n  }\n  usersActionList = (\n    theme: CometChatTheme,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ) => {\n    const attachmentOptions: CometChatMessageComposerAction[] = [];\n    if (!additionalAttachmentOptionsParams?.hideCameraOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.takePhoto,\n        title: t(\"CAMERA\"),\n        icon: (\n          <Icon\n            name='photo-camera-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height as DimensionValue\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width as DimensionValue\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle as ViewStyle\n            }\n          />\n        ),\n      });\n    }\n    if (!additionalAttachmentOptionsParams?.hideImageAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.image,\n        title: t(\"ATTACH_IMAGE\"),\n        icon: (\n          <Icon\n            name='photo-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height as DimensionValue\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width as DimensionValue\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle as ViewStyle\n            }\n          />\n        ),\n      });\n    }\n\n    if (!additionalAttachmentOptionsParams?.hideVideoAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.video,\n        title: t(\"ATTACH_VIDEO\"),\n        icon: (\n          <Icon\n            name='videocam-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height as DimensionValue\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width as DimensionValue\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle as ViewStyle\n            }\n          />\n        ),\n      });\n    }\n\n    if (!additionalAttachmentOptionsParams?.hideAudioAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.audio,\n        title: t(\"ATTACH_AUDIO\"),\n        icon: (\n          <Icon\n            name='play-circle-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height as DimensionValue\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width as DimensionValue\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle as ViewStyle\n            }\n          />\n        ),\n      });\n    }\n    if (!additionalAttachmentOptionsParams?.hideFileAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.file,\n        title: t(\"ATTACH_DOCUMENT\"),\n        icon: (\n          <Icon\n            name='description-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height as DimensionValue\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width as DimensionValue\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle as ViewStyle\n            }\n          />\n        ),\n      });\n    }\n    return attachmentOptions;\n  };\n  groupActionList = (\n    theme: CometChatTheme,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ) => {\n    const attachmentOptions: CometChatMessageComposerAction[] = [];\n    if (!additionalAttachmentOptionsParams?.hideCameraOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.takePhoto,\n        title: t(\"CAMERA\"),\n        icon: (\n          <Icon\n            name='photo-camera-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n      });\n    }\n    if (!additionalAttachmentOptionsParams?.hideImageAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.image,\n        title: t(\"ATTACH_IMAGE\"),\n        icon: (\n          <Icon\n            name='photo-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n      });\n    }\n\n    if (!additionalAttachmentOptionsParams?.hideVideoAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.video,\n        title: t(\"ATTACH_VIDEO\"),\n        icon: (\n          <Icon\n            name='videocam-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n      });\n    }\n\n    if (!additionalAttachmentOptionsParams?.hideAudioAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.audio,\n        title: t(\"ATTACH_AUDIO\"),\n        icon: (\n          <Icon\n            name='play-circle-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n      });\n    }\n    if (!additionalAttachmentOptionsParams?.hideFileAttachmentOption) {\n      attachmentOptions.push({\n        id: MessageTypeConstants.file,\n        title: t(\"ATTACH_DOCUMENT\"),\n        icon: (\n          <Icon\n            name='description-fill'\n            color={theme.color.primary}\n            height={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.height\n            }\n            width={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle?.iconStyle\n                ?.width\n            }\n            containerStyle={\n              theme.messageComposerStyles?.attachmentOptionsStyles?.optionsItemStyle\n                ?.iconContainerStyle\n            }\n          />\n        ),\n      });\n    }\n\n    return attachmentOptions;\n  };\n\n  getAttachmentOptions(\n    theme: CometChatTheme,\n    user?: any,\n    group?: any,\n    composerId?: any,\n    additionalAttachmentOptionsParams?: AdditionalAttachmentOptionsParams\n  ): CometChatMessageComposerAction[] {\n    if (user) {\n      return this.usersActionList(theme, additionalAttachmentOptionsParams);\n    } else if (group) {\n      return this.groupActionList(theme, additionalAttachmentOptionsParams);\n    } else {\n      return this.usersActionList(theme, additionalAttachmentOptionsParams);\n    }\n  }\n  getAuxiliaryButtonOptions() {\n    return null;\n  }\n\n  getLastConversationMessage(\n    conversation: CometChat.Conversation,\n    theme?: CometChatTheme\n  ): string | JSX.Element {\n    const lastMessage = conversation.getLastMessage();\n    if (lastMessage && lastMessage.category === 'action') {\n      const actionMsg = this.getActionMessage(lastMessage);\n      if (actionMsg) return actionMsg;\n    }\n    return CometChatConversationUtils.getMessagePreview(lastMessage, theme);\n  }\n\n  getAllTextFormatters(\n    loggedInUser?: CometChat.User,\n    theme?: CometChatTheme\n  ): CometChatTextFormatter[] {\n    return [\n      ChatConfigurator.getDataSource().getMentionsFormatter(loggedInUser, theme),\n      ChatConfigurator.getDataSource().getUrlsFormatter(loggedInUser),\n    ];\n  }\n\n  getMentionsFormatter(\n    loggedInUser?: CometChat.User,\n    theme?: CometChatTheme\n  ): CometChatMentionsFormatter {\n    return new CometChatMentionsFormatter(theme!, loggedInUser);\n  }\n\n  getUrlsFormatter(loggedInUser?: CometChat.User): CometChatUrlsFormatter {\n    return new CometChatUrlsFormatter(loggedInUser);\n  }\n\n  getMessagePreviewSubtitle(message: CometChat.BaseMessage): string {\n    if (message instanceof CometChat.TextMessage) {\n      return message.getText() || \"\";\n    } else if (message instanceof CometChat.MediaMessage) {\n      const data = message.getData() as any;\n      return data?.name || message.getType() || \"\";\n    } else if (message.getType() === \"groupMember\") {\n      return \"Group action\";\n    }\n    return message.getType() || \"\";\n  }\n}\n//for internal use only\nexport const internalMessageDataSource = new MessageDataSource();"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/index.ts",
    "content": "import { ChatConfigurator } from \"./ChatConfigurator\";\nimport { DataSource } from \"./DataSource\";\nimport { DataSourceDecorator } from \"./DataSourceDecorator\";\nimport { ExtensionsDataSource } from \"./ExtensionsDataSource\";\nimport { MessageDataSource } from \"./MessageDataSource\";\nexport {\n  ChatConfigurator,\n  DataSourceDecorator,\n  ExtensionsDataSource,\n  MessageDataSource,\n};\nexport type {\n  DataSource,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/framework/resources/index.ts",
    "content": "import AUDIO from \"./audio-file.png\";\nimport ADD from \"./circle_add.png\";\nimport COPY from \"./copy.png\";\nimport DELETE from \"./delete.png\";\nimport EDIT from \"./edit.png\";\nimport EMOJI from \"./emoji.png\";\nimport FILE from \"./file_upload.png\";\nimport HEART from \"./heart.png\";\nimport IMAGE from \"./image.png\";\nimport INFO from \"./info.png\";\nimport SEND from \"./send_message.png\";\nimport SHARE from \"./share.png\";\nimport VIDEO from \"./video_upload.png\";\n// import FORWARD from \"./forward.png\";\nimport PRIVATE_MESSAGE from \"./messageInPrivate.png\";\nimport MICROPHONE from \"./microphone.png\";\nimport PAUSE from \"./pause.png\";\nimport PLAY from \"./play.png\";\nimport REPLY from \"./reply.png\";\nimport STOP_PLAYER from \"./stop_player.png\";\nimport THREAD from \"./thread.png\";\n\nexport const ICONS = {\n  EDIT,\n  DELETE,\n  COPY,\n  SHARE,\n  INFO,\n  // FORWARD,\n  THREAD,\n  REPLY,\n  HEART,\n  SEND,\n  EMOJI,\n  ADD,\n  AUDIO,\n  IMAGE,\n  VIDEO,\n  FILE,\n  PRIVATE_MESSAGE,\n  PLAY,\n  PAUSE,\n  STOP_PLAYER,\n  MICROPHONE,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/LocalizedDateHelper.ts",
    "content": "import { Language } from \"../resources/CometChatLocalizeNew/type\";\nimport { getCometChatTranslation } from \"../resources/CometChatLocalizeNew/LocalizationManager\";\nimport dayjs from 'dayjs';\nimport 'dayjs/locale/en';      // English\nimport 'dayjs/locale/hi';      // Hindi\nimport 'dayjs/locale/ja';      // Japanese\nimport 'dayjs/locale/ko';      // Korean\nimport 'dayjs/locale/ru';      // Russian\nimport 'dayjs/locale/pt';      // Portuguese\nimport 'dayjs/locale/es';      // Spanish\nimport 'dayjs/locale/fr';      // French\nimport 'dayjs/locale/de';      // German\nimport 'dayjs/locale/it';      // Italian\nimport 'dayjs/locale/tr';      // Turkish\nimport 'dayjs/locale/zh';      // Chinese (Simplified)\nimport 'dayjs/locale/zh-tw';   // Chinese (Traditional)\nimport 'dayjs/locale/ms';      // Malay\nimport 'dayjs/locale/sv';      // Swedish\nimport 'dayjs/locale/lt';      // Lithuanian\nimport 'dayjs/locale/hu';      // Hungarian\nimport 'dayjs/locale/nl';      // Dutch\n// Import other languages as needed\n\n// Dayjs plugins\nimport localizedFormat from 'dayjs/plugin/localizedFormat';\nimport relativeTime from 'dayjs/plugin/relativeTime';\n// Initialize plugins\ndayjs.extend(localizedFormat);\ndayjs.extend(relativeTime);\nimport * as RNLocalize from 'react-native-localize';\n\nconst t = getCometChatTranslation();\n\nexport class LocalizedDateHelper {\n    public static patterns = {\n        timeFormat: \"timeFormat\",\n        dayDateFormat: \"dayDateFormat\",\n        dayWeekDayDateFormat: \"dayWeekDayDateFormat\",\n        dayWeekDayDateTimeFormat: \"dayWeekDayDateTimeFormat\",\n        dayDateTimeFormat: \"dayDateTimeFormat\",\n        callBubble: \"callBubble\",\n        callLogs: \"callLogs\",\n        relativeMinutes: \"relativeMinutes\",\n        conversationDate: \"conversationDate\",\n    } as const;\n\n    /**\n     * Maps language codes to full locale codes and numeral systems\n     */\n    public getLocaleConfig(locale: Language): { localeCode: string, dateFormattingLocale: string, dayJsLocale: string } {\n        // Map language codes to full locale codes\n        const localeMap: Record<string, string> = {\n            'en': 'en',  \n            'en-IN': 'en',      // English (India)\n            'en-US': 'en-US',      // English (United States)\n            'en-GB': 'en-GB',   // English (United Kingdom)\n            'nl': 'nl-NL',      // Dutch\n            'fr': 'fr-FR',      // French\n            'de': 'de-DE',      // German\n            'hi': 'hi-IN',      // Hindi\n            'it': 'it-IT',      // Italian\n            'ja': 'ja-JP',      // Japanese\n            'ko': 'ko-KR',      // Korean\n            'pt': 'pt-PT',      // Portuguese\n            'ru': 'ru-RU',      // Russian\n            'es': 'es-ES',      // Spanish\n            'tr': 'tr-TR',      // Turkish\n            'zh': 'zh-CN',      // Chinese (Simplified)\n            'zh-tw': 'zh-TW',   // Chinese (Traditional)\n            'ms': 'ms-MY',      // Malay\n            'sv': 'sv-SE',      // Swedish\n            'lt': 'lt-LT',      // Lithuanian\n            'hu': 'hu-HU',      // Hungarian\n        };\n\n        // Map to dayjs locale codes (usually simpler than full locale codes)\n        const dayJsLocaleMap: Record<string, string> = {\n            'en-IN': 'en',\n            'en-GB': 'en-gb',\n            'zh-tw': 'zh-tw',\n            // Most other languages use their basic code in dayjs\n        };\n\n        return {\n            localeCode: localeMap[locale] || locale,\n            dateFormattingLocale: LocalizedDateHelper.getFullLocaleForDateFormatting(locale),\n            dayJsLocale: dayJsLocaleMap[locale] || locale\n        };\n    }\n\n    /**\n    * Check if the locale prefers 12-hour clock format\n    */\n\n    private shouldUse12HourClock(locale: Language): boolean {\n    const twelveHourLocales = [\n        'en-US', 'en-GB', 'en',\n        'hi-IN', 'hi',\n        'ms-MY', 'ms'\n    ];\n\n    const { localeCode, dayJsLocale } = this.getLocaleConfig(locale);\n\n    if (twelveHourLocales.includes(localeCode)) {\n        return true;\n    }\n\n    const isKnownToDayjs =\n        Boolean(dayjs.Ls[dayJsLocale]) || Boolean(dayjs.Ls[localeCode]);\n\n    if (!isKnownToDayjs) {\n        return true;\n    }\n    return false;\n}\n\n\n    /**\n * Format time using dayjs\n */\n    private formatTime(date: Date, locale: Language): string {\n        const { dayJsLocale } = this.getLocaleConfig(locale);\n\n        // Apply locale to Day.js\n        dayjs.locale(dayJsLocale);\n\n        // Decide between 12-hour and 24-hour\n        const use12Hour = this.shouldUse12HourClock(locale);\n\n        // Build format string\n        const formatString = use12Hour ? \"h:mm A\" : \"HH:mm\";\n\n        // Return formatted time (with localized AM/PM if available in locale)\n        return dayjs(date).format(formatString);\n   } \n\n    /**\n * Gets the full locale tag needed for date formatting\n * This extends simple language codes to include region information\n */\n    public static getFullLocaleForDateFormatting(language: Language): string {\n        // If we have a language that includes region (like en-US), use it directly\n        if (language.includes('-')) {\n            return language;\n        }\n\n        // DEFAULT MAPPINGS - Always use these explicit mappings instead of device detection\n        const defaultRegionMap: Record<string, string> = {\n            'en': 'en-IN',   // Default English to IN format (DD/MM/YYYY)\n            'en-IN': 'en-IN',\n            'en-US': 'en-US',\n            'en-GB': 'en-GB',\n            'es': 'es-ES',\n            'fr': 'fr-FR',\n            'de': 'de-DE',\n            'pt': 'pt-BR',\n            'it': 'it-IT',\n            'ru': 'ru-RU',\n            'zh': 'zh-CN',\n            'ja': 'ja-JP',\n            'hi': 'hi-IN',\n            'ms': 'ms-MY',\n            'sv': 'sv-SE',\n            'lt': 'lt-LT',\n            'hu': 'hu-HU',\n        };\n\n        // Return the mapped region or construct a default one\n        return defaultRegionMap[language] || `${language}-${language.toUpperCase()}`;\n    }\n    /**\n * Format date specifically for conversation list\n */ \n    private formatConversationDate(date: Date, locale: Language): string {\n        const { dateFormattingLocale, dayJsLocale } = this.getLocaleConfig(locale);\n\n        // Set dayjs locale for weekday names\n        dayjs.locale(dayJsLocale);\n\n        const now = dayjs();\n        const messageDate = dayjs(date);\n        const startOfToday = now.startOf('day');\n\n        // Today - show just the time with AM/PM (maintain existing behavior)\n        if (messageDate.isAfter(startOfToday)) {\n            return this.formatTime(date, locale);\n        }\n\n        // Yesterday (maintain existing behavior)\n        if (messageDate.isAfter(startOfToday.subtract(1, 'day'))) {\n            return t(\"YESTERDAY\");\n        }\n\n\n        // For dates older than current week, use locale-appropriate date format\n        try {\n            // Use Intl.DateTimeFormat with the full locale tag for proper date formatting\n            const options: Intl.DateTimeFormatOptions = {\n                day: 'numeric',\n                month: 'numeric',\n                year: 'numeric' \n            };\n\n            const formatter = new Intl.DateTimeFormat(dateFormattingLocale, options);\n            const formattedDate = formatter.format(date);\n\n            return formattedDate\n              \n              \n        } catch (error) {\n            console.warn('Error formatting date with Intl.DateTimeFormat:', error);\n            return \"\"\n        }\n    }\n\n    /**\n     * Format date for date separators (more detailed than conversation list)\n     */\n    private formatDateSeparator(date: Date, locale: Language): string {\n        const { dayJsLocale } = this.getLocaleConfig(locale);\n\n        // Set dayjs locale\n        dayjs.locale(dayJsLocale);\n\n        const now = dayjs();\n        const messageDate = dayjs(date);\n        const startOfToday = now.startOf('day');\n\n        // Today\n        if (messageDate.isAfter(startOfToday)) {\n            return t(\"TODAY\");\n        }\n\n        // Yesterday\n        if (messageDate.isAfter(startOfToday.subtract(1, 'day'))) {\n            return t(\"YESTERDAY\");\n        }\n\n        // Within current week (show weekday)\n        if (messageDate.isAfter(startOfToday.subtract(6, 'day'))) {\n            // Use dayjs for weekday name\n            return messageDate.format('dddd'); \n        }\n\n        // Older messages, show full date\n        const formattedDate = messageDate.format('D MMM, YYYY');\n        return formattedDate\n    }\n\n    /**\n     * Format date and time together\n     */\n    private formatDateTimeWithSeparator(date: Date, locale: Language): string {\n        const { dayJsLocale } = this.getLocaleConfig(locale);\n\n        // Set dayjs locale\n        dayjs.locale(dayJsLocale);\n\n        const now = dayjs();\n        const messageDate = dayjs(date);\n        const startOfToday = now.startOf('day');\n        const time = this.formatTime(date, locale);\n\n        // Today\n        if (messageDate.isAfter(startOfToday)) {\n            return `${t(\"TODAY\")} ${time}`;\n        }\n\n        // Yesterday\n        if (messageDate.isAfter(startOfToday.subtract(1, 'day'))) {\n            return `${t(\"YESTERDAY\")} ${time}`;\n        }\n\n        // Within current week\n        if (messageDate.isAfter(startOfToday.subtract(6, 'day'))) {\n            return `${messageDate.format('dddd')} ${time}`;\n        }\n\n        // Older messages\n        const formattedDate = messageDate.format('D MMM, YYYY');\n        return `${formattedDate} ${time}`;\n    }\n\n    /**\n     * Public method to get formatted date according to pattern\n     */\n    getFormattedDate(timestamp: number, pattern: string, locale: Language = \"en\"): string {\n        const date = new Date(timestamp);\n\n        switch (pattern) {\n            case LocalizedDateHelper.patterns.timeFormat:\n                return this.formatTime(date, locale);\n            case LocalizedDateHelper.patterns.dayDateFormat:\n            case LocalizedDateHelper.patterns.dayWeekDayDateFormat:\n                return this.formatDateSeparator(date, locale);\n\n            case LocalizedDateHelper.patterns.dayWeekDayDateTimeFormat:\n            case LocalizedDateHelper.patterns.dayDateTimeFormat:\n                return this.formatDateTimeWithSeparator(date, locale);\n\n            case LocalizedDateHelper.patterns.conversationDate:\n                return this.formatConversationDate(date, locale);\n\n            case LocalizedDateHelper.patterns.callBubble:\n            case LocalizedDateHelper.patterns.callLogs: \n                const { dayJsLocale } = this.getLocaleConfig(locale);\n                dayjs.locale(dayJsLocale);\n                const use12Hour = this.shouldUse12HourClock(locale);\n                const formatString = use12Hour ? \"D MMM, h:mm A\" : \"D MMM, HH:mm\";\n                return dayjs(date).format(formatString);\n            default:\n                return this.formatDateSeparator(date, locale);\n        }\n    }\n}\n\nexport const localizedDateHelperInstance = new LocalizedDateHelper();"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/Toast.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, Text, View } from \"react-native\";\n\nexport const Toast = (props: { message: string }) => {\n  return (\n    <View style={ToastStyle.container}>\n      <Text style={{ alignSelf: \"center\" }}>{props.message}</Text>\n    </View>\n  );\n};\n\nconst ToastStyle = StyleSheet.create({\n  container: {\n    position: \"absolute\",\n    backgroundColor: \"rgba(95,95,95,1)\",\n    borderWidth: 1,\n    borderColor: \"rgba(0,0,0,0.8)\",\n    zIndex: 10000,\n    margin: 16,\n    padding: 16,\n    borderRadius: 18,\n    alignSelf: \"center\",\n    justifyContent: \"center\",\n    bottom: 16,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/composerHelpers.ts",
    "content": "/**\n * Shared helper functions for CometChat message composers.\n * These utilities are used by both CometChatMessageComposer and CometChatSingleLineMessageComposer\n * to ensure consistent behavior and maintainability.\n * \n * DESIGN PRINCIPLE: All composer functionality should be extracted into reusable helpers\n * to ensure feature parity and plug-and-play compatibility between composers.\n * \n * @module composerHelpers\n */\n\n//@ts-ignore\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\n\n// ============================================================================\n// AGENTIC USER HELPERS\n// ============================================================================\n\n/**\n * Check if a user is an agentic user (AI agent).\n * Agentic users have the role '@agentic' and require special handling:\n * - Auto-hide certain buttons (attachment, stickers, voice recording)\n * - Disable typing events and mentions\n * - Apply send button delay\n * - Track parent message ID for threaded conversations\n * \n * @param user - The CometChat user object to check\n * @returns True if the user has role '@agentic'\n */\nexport const isAgenticUser = (user?: CometChat.User): boolean => {\n  return user?.getRole?.() === '@agentic';\n};\n\n/**\n * Derive button visibility based on agentic user status and prop value.\n * When user is agentic, buttons are automatically hidden regardless of prop values.\n * \n * @param isAgentic - Whether the user is an agentic user\n * @param propValue - The prop value for hiding the button\n * @returns True if the button should be hidden\n */\nexport const deriveHideButton = (isAgentic: boolean, propValue?: boolean): boolean => {\n  return isAgentic ? true : (propValue ?? false);\n};\n\n/**\n * Derive feature disabled state based on agentic user status and prop value.\n * When user is agentic, features are automatically disabled regardless of prop values.\n * \n * @param isAgentic - Whether the user is an agentic user\n * @param propValue - The prop value for disabling the feature\n * @returns True if the feature should be disabled\n */\nexport const deriveDisableFeature = (isAgentic: boolean, propValue?: boolean): boolean => {\n  return isAgentic ? true : (propValue ?? false);\n};\n\n/**\n * Configuration for agentic user send button delay.\n */\nexport interface AgenticSendDelayConfig {\n  /** Whether the user is agentic */\n  isAgentic: boolean;\n  /** Current delay state setter */\n  setDelayState: (value: boolean) => void;\n  /** Timer ref for cleanup */\n  timerRef: React.MutableRefObject<NodeJS.Timeout | null>;\n  /** Delay duration in milliseconds (default: 1000) */\n  delayMs?: number;\n}\n\n/**\n * Apply send button delay for agentic users.\n * Prevents rapid-fire messages to AI agents by disabling send button for 1 second.\n * \n * @param config - Configuration for the delay\n */\nexport const applyAgenticSendDelay = (config: AgenticSendDelayConfig): void => {\n  const { isAgentic, setDelayState, timerRef, delayMs = 1000 } = config;\n  \n  if (!isAgentic) return;\n  \n  setDelayState(true);\n  \n  if (timerRef.current) {\n    clearTimeout(timerRef.current);\n  }\n  \n  timerRef.current = setTimeout(() => {\n    setDelayState(false);\n  }, delayMs);\n};\n\n/**\n * Configuration for tracking parent message ID for agentic users.\n */\nexport interface AgenticParentMessageConfig {\n  /** Whether the user is agentic */\n  isAgentic: boolean;\n  /** Parent message ID from props (if provided) */\n  parentMessageIdProp?: number;\n  /** Ref to store tracked parent message ID */\n  parentMessageIdRef: React.MutableRefObject<number | null>;\n  /** The sent message object */\n  message: any;\n}\n\n/**\n * Track parent message ID for agentic users.\n * For the first message sent to an agentic user without a parentMessageId prop,\n * stores the message ID as the parent for subsequent messages.\n * \n * @param config - Configuration for parent message tracking\n * @returns The message ID if it was stored, null otherwise\n */\nexport const trackAgenticParentMessageId = (config: AgenticParentMessageConfig): number | null => {\n  const { isAgentic, parentMessageIdProp, parentMessageIdRef, message } = config;\n  \n  if (!isAgentic || parentMessageIdProp || !message?.getId) {\n    return null;\n  }\n  \n  const messageId = typeof message.getId() === 'string' \n    ? Number(message.getId()) \n    : message.getId();\n  \n  if (!isNaN(messageId) && !parentMessageIdRef.current) {\n    parentMessageIdRef.current = messageId;\n    return messageId;\n  }\n  \n  return null;\n};\n\n/**\n * Get the parent message ID to use for a message.\n * Prioritizes prop value, then tracked value for agentic users.\n * \n * @param parentMessageIdProp - Parent message ID from props\n * @param isAgentic - Whether the user is agentic\n * @param parentMessageIdRef - Ref containing tracked parent message ID\n * @returns The parent message ID to use, or null if none\n */\nexport const getParentMessageId = (\n  parentMessageIdProp?: number,\n  isAgentic?: boolean,\n  parentMessageIdRef?: React.MutableRefObject<number | null>\n): number | null => {\n  if (parentMessageIdProp) {\n    return parentMessageIdProp;\n  }\n  if (isAgentic && parentMessageIdRef?.current) {\n    return parentMessageIdRef.current;\n  }\n  return null;\n};\n\n// ============================================================================\n// REPLY MESSAGE HELPERS\n// ============================================================================\n\n/**\n * Interface for reply message state used by composers.\n */\nexport interface ReplyMessageState {\n  message: any;\n  mode: string;\n}\n\n/**\n * Interface for message preview state used by composers (edit mode).\n */\nexport interface MessagePreviewState {\n  message: any;\n  mode: string;\n}\n\n/**\n * Get the reply message ID from a reply message state.\n * \n * @param replyMessage - The reply message state\n * @returns The message ID or null if not available\n */\nexport const getReplyMessageId = (replyMessage: ReplyMessageState | null): number | null => {\n  return replyMessage?.message?.getId?.() ?? null;\n};\n\n/**\n * Check if a reply message is active.\n * \n * @param replyMessage - The reply message state\n * @returns True if there is an active reply message\n */\nexport const hasActiveReply = (replyMessage: ReplyMessageState | null): boolean => {\n  return replyMessage !== null && replyMessage.message !== null;\n};\n\n/**\n * Set quoted message on a CometChat message for reply functionality.\n * Uses SDK's built-in quoted message functionality with error handling.\n * \n * @param message - The message to set the quoted message on (TextMessage or MediaMessage)\n * @param replyMessage - The original message being replied to\n * @param replyMessageId - The ID of the original message\n * @returns True if quoted message was set successfully, false otherwise\n */\nexport const setQuotedMessageSafe = (\n  message: CometChat.TextMessage | CometChat.MediaMessage,\n  replyMessage: any,\n  replyMessageId: number | string\n): boolean => {\n  try {\n    if (typeof (message as any).setQuotedMessage === 'function') {\n      (message as any).setQuotedMessage(replyMessage);\n    }\n    if (typeof (message as any).setQuotedMessageId === 'function') {\n      (message as any).setQuotedMessageId(replyMessageId);\n    }\n    return true;\n  } catch (error) {\n    // Silently handle errors - quoted message is optional\n    return false;\n  }\n};\n\n/**\n * Configuration for handling reply message on send.\n */\nexport interface ReplyMessageSendConfig {\n  /** The message being sent */\n  message: CometChat.TextMessage | CometChat.MediaMessage;\n  /** Current reply message state */\n  replyMessage: ReplyMessageState | null;\n  /** Callback to clear reply state after handling */\n  clearReplyState?: () => void;\n}\n\n/**\n * Handle reply message when sending a message.\n * Sets quoted message if reply is active and optionally clears reply state.\n * \n * @param config - Configuration for reply handling\n * @returns True if quoted message was set, false otherwise\n */\nexport const handleReplyOnSend = (config: ReplyMessageSendConfig): boolean => {\n  const { message, replyMessage, clearReplyState } = config;\n  \n  const replyMessageId = getReplyMessageId(replyMessage);\n  \n  if (replyMessageId && replyMessage?.message) {\n    const success = setQuotedMessageSafe(message, replyMessage.message, replyMessageId);\n    \n    if (clearReplyState) {\n      clearReplyState();\n    }\n    \n    return success;\n  }\n  \n  return false;\n};\n\n// ============================================================================\n// SEND BUTTON STATE HELPERS\n// ============================================================================\n\n/**\n * Configuration for determining send button disabled state.\n */\nexport interface SendButtonStateConfig {\n  /** Current input text */\n  inputText: string;\n  /** Whether streaming is active (for agentic users) */\n  isStreaming?: boolean;\n  /** Whether send button delay is active (for agentic users) */\n  isSendButtonDelayed?: boolean;\n  /** Whether in edit mode */\n  isEditMode?: boolean;\n}\n\n/**\n * Determine if send button should be disabled.\n * Considers input text, streaming state, and delay state.\n * \n * @param config - Configuration for send button state\n * @returns True if send button should be disabled\n */\nexport const isSendButtonDisabled = (config: SendButtonStateConfig): boolean => {\n  const { inputText, isStreaming = false, isSendButtonDelayed = false, isEditMode = false } = config;\n  \n  // In edit mode, only check if text is empty\n  if (isEditMode) {\n    return inputText.trim().length === 0;\n  }\n  \n  return isStreaming || inputText.trim().length === 0 || isSendButtonDelayed;\n};\n\n/**\n * Get send button tint color based on disabled state.\n * \n * @param isDisabled - Whether the button is disabled\n * @param activeTint - Tint color when active\n * @param inactiveTint - Tint color when inactive\n * @returns The appropriate tint color\n */\nexport const getSendButtonTint = (\n  isDisabled: boolean,\n  activeTint: string,\n  inactiveTint: string\n): string => {\n  return isDisabled ? inactiveTint : activeTint;\n};\n\n// ============================================================================\n// MESSAGE CREATION HELPERS\n// ============================================================================\n\n/**\n * Configuration for creating a text message.\n */\nexport interface CreateTextMessageConfig {\n  /** Receiver ID (user UID or group GUID) */\n  receiverId: string;\n  /** Message text */\n  text: string;\n  /** Receiver type ('user' or 'group') */\n  receiverType: string;\n  /** Logged in user */\n  sender: CometChat.User;\n  /** Receiver (user or group) */\n  receiver: CometChat.User | CometChat.Group;\n  /** Message unique ID */\n  muid: string;\n  /** Parent message ID for threaded messages */\n  parentMessageId?: number | null;\n}\n\n/**\n * Create a text message with common configuration.\n * \n * @param config - Configuration for the text message\n * @returns Configured TextMessage object\n */\nexport const createTextMessage = (config: CreateTextMessageConfig): CometChat.TextMessage => {\n  const { receiverId, text, receiverType, sender, receiver, muid, parentMessageId } = config;\n  \n  const textMessage = new CometChat.TextMessage(receiverId, text, receiverType);\n  \n  textMessage.setSender(sender);\n  textMessage.setReceiver(receiver);\n  textMessage.setMuid(muid);\n  \n  if (parentMessageId) {\n    textMessage.setParentMessageId(parentMessageId);\n  }\n  \n  return textMessage;\n};\n\n/**\n * Configuration for creating a media message.\n */\nexport interface CreateMediaMessageConfig {\n  /** Receiver ID (user UID or group GUID) */\n  receiverId: string;\n  /** Media file */\n  file: any;\n  /** Media type (image, video, audio, file) */\n  mediaType: string;\n  /** Receiver type ('user' or 'group') */\n  receiverType: string;\n  /** Logged in user */\n  sender: CometChat.User;\n  /** Receiver (user or group) */\n  receiver: CometChat.User | CometChat.Group;\n  /** Message unique ID */\n  muid: string;\n  /** Parent message ID for threaded messages */\n  parentMessageId?: number | null;\n}\n\n/**\n * Create a media message with common configuration.\n * \n * @param config - Configuration for the media message\n * @returns Configured MediaMessage object\n */\nexport const createMediaMessage = (config: CreateMediaMessageConfig): CometChat.MediaMessage => {\n  const { receiverId, file, mediaType, receiverType, sender, receiver, muid, parentMessageId } = config;\n  \n  const mediaMessage = new CometChat.MediaMessage(receiverId, file, mediaType, receiverType);\n  \n  mediaMessage.setSender(sender);\n  mediaMessage.setReceiver(receiver);\n  mediaMessage.setMuid(muid);\n  \n  if (parentMessageId) {\n    mediaMessage.setParentMessageId(parentMessageId);\n  }\n  \n  return mediaMessage;\n};\n\n// ============================================================================\n// MENTION HELPERS\n// ============================================================================\n\n/**\n * Interface for mention overlap detection.\n */\nexport interface MentionOverlap {\n  key: string;\n  value: any;\n  start: number;\n  end: number;\n}\n\n/**\n * Parse a mention key string into start/end positions.\n * Mention keys are in format \"start_end\".\n * \n * @param key - The mention key string\n * @returns Object with start and end positions, or undefined if invalid\n */\nexport const parseMentionKey = (key: string): { start: number; end: number } | undefined => {\n  const [startStr, endStr] = key.split('_');\n  const start = Number(startStr);\n  const end = Number(endStr);\n  \n  const isValid = Number.isFinite(start) && Number.isFinite(end);\n  \n  if (!isValid && __DEV__) {\n    console.warn(`Invalid mention key: \"${key}\" (expected \"start_end\")`);\n  }\n  \n  return isValid ? { start, end } : undefined;\n};\n\n/**\n * Calculate the deletion range based on selection and deletion length.\n * \n * @param selection - Current selection with start and end positions\n * @param deletionLength - Number of characters being deleted\n * @returns The range of text being deleted\n */\nexport const calcDeletionRange = (\n  selection: { start: number; end: number },\n  deletionLength: number\n): { start: number; end: number } => {\n  return selection.start === selection.end\n    ? { start: Math.max(0, selection.start - deletionLength), end: selection.start }\n    : { start: selection.start, end: selection.end };\n};\n\n/**\n * Collect all mentions that overlap with a deletion range.\n * \n * @param range - The deletion range\n * @param mentionMap - Map of mention keys to mention data\n * @returns Array of overlapping mentions sorted by start position\n */\nexport const collectOverlappingMentions = <T>(\n  range: { start: number; end: number },\n  mentionMap: Map<string, T>\n): MentionOverlap[] => {\n  const overlaps: MentionOverlap[] = [];\n  \n  mentionMap.forEach((value, key) => {\n    const mentionRange = parseMentionKey(key);\n    if (!mentionRange) return;\n    \n    const { start, end } = mentionRange;\n    if (range.start < end && range.end > start) {\n      overlaps.push({ key, value, start, end });\n    }\n  });\n  \n  overlaps.sort((a, b) => a.start - b.start);\n  return overlaps;\n};\n\n/**\n * Shift remaining mention keys after deletion.\n * Updates mention positions to account for removed text.\n * \n * @param map - The mention map to update\n * @param shiftStart - Position after which to shift\n * @param delta - Amount to shift (negative for deletion)\n */\nexport const shiftRemainingMentionKeys = <T>(\n  map: Map<string, T>,\n  shiftStart: number,\n  delta: number\n): void => {\n  if (delta === 0) return;\n  \n  const shifted = new Map<string, T>();\n  \n  map.forEach((val, key) => {\n    const range = parseMentionKey(key);\n    if (!range) return;\n    \n    const { start, end } = range;\n    if (start > shiftStart) {\n      shifted.set(`${start + delta}_${end + delta}`, val);\n    } else {\n      shifted.set(key, val);\n    }\n  });\n  \n  map.clear();\n  shifted.forEach((v, k) => map.set(k, v));\n};\n\n// ============================================================================\n// UTILITY HELPERS\n// ============================================================================\n\n/**\n * Generate a unique message ID using Unix timestamp in milliseconds.\n * \n * @returns Unique message ID string\n */\nexport const generateMessageMuid = (): string => {\n  return String(Date.now());\n};\n\n/**\n * Check if text is empty or contains only whitespace.\n * \n * @param text - The text to check\n * @returns True if text is empty or whitespace only\n */\nexport const isTextEmpty = (text: string): boolean => {\n  return text.trim().length === 0;\n};\n\n/**\n * Safely get message ID from a message object.\n * Handles both string and number ID types.\n * \n * @param message - The message object\n * @returns The message ID as a number, or null if not available\n */\nexport const getMessageIdSafe = (message: any): number | null => {\n  if (!message?.getId) return null;\n  \n  const id = message.getId();\n  const numId = typeof id === 'string' ? Number(id) : id;\n  \n  return isNaN(numId) ? null : numId;\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/dateHelper.ts",
    "content": "import { ValueOf } from \"./types\";\n\n// TODO: Fix the logics - as currently it is inefficient and non-readable\n\nexport class DateHelper {\n  /**\n   * Pattern for Date.\n   * one of\n   * 1. timeFormat: \"hh:mm a\".\n   * 2. dayDateFormat: Today, Yesterday, weekday or \"d MMM, yyyy\".\n   * 3. dayWeekDayDateTimeFormat: Today(time), weekday, Yesterday, or \"dd/mm/yyyy\".\n   */\n\n  public static patterns = {\n    timeFormat: \"timeFormat\",\n    dayDateFormat: \"dayDateFormat\",\n    dayWeekDayDateFormat: \"dayWeekDayDateFormat\",\n    dayWeekDayDateTimeFormat: \"dayWeekDayDateTimeFormat\",\n    dayDateTimeFormat: \"dayDateTimeFormat\",\n    callBubble: \"d MMM, hh:mm aa\",\n    callLogs: \"d MMMM, h:m aa\",\n  } as const;\n\n  private static monthNames = [\n    \"January\",\n    \"February\",\n    \"March\",\n    \"April\",\n    \"May\",\n    \"June\",\n    \"July\",\n    \"August\",\n    \"September\",\n    \"October\",\n    \"November\",\n    \"December\",\n  ] as const;\n\n  private static weekNames = [\n    \"Sunday\",\n    \"Monday\",\n    \"Tuesday\",\n    \"Wednesday\",\n    \"Thursday\",\n    \"Friday\",\n    \"Saturday\",\n  ] as const;\n\n  getWeekOfDay(date: Date) {\n    let weekDay = date.getDay();\n    let week = DateHelper.weekNames[weekDay];\n    return week.substring(0, 3);\n  }\n\n  getMonthOfDay(date: Date, full = false) {\n    let month = date.getMonth();\n    let mnth = DateHelper.monthNames[month];\n    if (full) return mnth;\n    return mnth.substring(0, 3);\n  }\n\n  getDateFormat(date: Date, pattern: ValueOf<typeof DateHelper.patterns>) {\n    if (pattern === DateHelper.patterns.dayDateFormat) {\n      return date.getDate() + \" \" + this.getMonthOfDay(date) + \", \" + date.getFullYear();\n    }\n    let dt: any = date.getDate();\n    if (dt < 10) {\n      dt = \"0\" + dt;\n    }\n    return dt + \"/\" + (date.getMonth() + 1) + \"/\" + date.getFullYear();\n  }\n\n  getTimeFormat(date: Date) {\n    let timeString = date.getHours();\n    let postString = timeString >= 12 ? \"PM\" : \"AM\";\n    if (timeString > 12) {\n      timeString = timeString - 12;\n    }\n    let minutes: any = date.getMinutes();\n    if (minutes < 10) minutes = \"0\" + minutes;\n    return timeString + \":\" + minutes + \" \" + postString;\n  }\n\n  getDate(date: Date, pattern: ValueOf<typeof DateHelper.patterns>) {\n    const today = new Date();\n    if (today.getMonth() === date.getMonth() && today.getFullYear() === date.getFullYear()) {\n      let diff = today.getDate() - date.getDate();\n      if (diff === 0) {\n        if (pattern === DateHelper.patterns.dayWeekDayDateTimeFormat) {\n          return this.getTimeFormat(date);\n        }\n        return \"Today\";\n      } else if (diff === 1) {\n        return \"Yesterday\";\n      } else if (diff < 7 && ([DateHelper.patterns.dayWeekDayDateTimeFormat,DateHelper.patterns.dayWeekDayDateFormat] as ValueOf<typeof DateHelper.patterns>[]).includes(pattern)) {\n        return this.getWeekOfDay(date);\n      } else {\n        return this.getDateFormat(date, pattern);\n      }\n    } else {\n      return this.getDateFormat(date, pattern);\n    }\n  }\n\n  getFormattedDate(timestamp: number, pattern: ValueOf<typeof DateHelper.patterns>) {\n    const date = new Date(timestamp);\n    if (pattern && pattern != null) {\n      let formattedDate = \"\";\n      switch (pattern) {\n        case DateHelper.patterns.timeFormat:\n          formattedDate = this.getTimeFormat(date);\n          break;\n        case DateHelper.patterns.dayDateFormat:\n        case DateHelper.patterns.dayWeekDayDateFormat:\n        case DateHelper.patterns.dayWeekDayDateTimeFormat:\n        case DateHelper.patterns.dayWeekDayDateTimeFormat:\n          formattedDate = this.getDate(date, pattern);\n          break;\n        case DateHelper.patterns.callBubble:\n          formattedDate = `${date.getDate()} ${this.getMonthOfDay(date)}, ${this.getTimeFormat(\n            date\n          )}`;\n        case DateHelper.patterns.callLogs:\n          formattedDate = `${date.getDate()} ${this.getMonthOfDay(\n            date,\n            true\n          )}, ${this.getTimeFormat(date).toLowerCase()}`;\n          break;\n        default:\n          break;\n      }\n      return formattedDate;\n    }\n    return null;\n  }\n}\n\nexport const dateHelperInstance = new DateHelper();\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/functions.js",
    "content": "export const CheckPropertyExists = (obj, propkey) => {\n    return Object.prototype.hasOwnProperty.call(obj, propkey);\n}\n\n/**\n * Returns extention data object or null if extention data not found.\n * @param {object} message - message Object from SDK\n * @param {string} extentionKey extention tobe searched\n * @returns object or null.\n */\n export const getExtensionData = (message, extentionKey) => {\n    if (message?.metadata) {\n        var injectedObject = message.metadata[\"@injected\"];\n        if (injectedObject != null && injectedObject.hasOwnProperty(\"extensions\")) {\n            var extensionsObject = injectedObject[\"extensions\"];\n            if (\n                extensionsObject != null &&\n                extensionsObject.hasOwnProperty(extentionKey)\n            ) {\n                return extensionsObject[extentionKey];\n            }\n        }\n    }\n    return null;\n}\n\nexport const getMetadataByKey = (message, metadataKey) => {\n    if (message.hasOwnProperty(\"metadata\")) {\n        const metadata = message[\"metadata\"];\n        if (metadata.hasOwnProperty(metadataKey)) {\n            return metadata[metadataKey];\n        }\n    }\n\n    return null;\n};\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/helperFunctions.ts",
    "content": "import React from \"react\";\nimport { DeepPartial } from \"./types\";\nimport { useCometChatTranslation } from \"../resources/CometChatLocalizeNew\";\nimport { localizedDateHelperInstance } from \"./LocalizedDateHelper\";\nimport dayjs from \"dayjs\";\n\nexport function deepMerge<\n  T extends Record<string, any>,\n  U extends DeepPartial<T> & Record<string, any>\n>(obj1: T, obj2: U, ...rest: (DeepPartial<T> & Record<string, any>)[]): T & U {\n  // Helper function to determine if a value is a plain object\n  function isObject(value: any): value is object {\n    if (React.isValidElement(value)) return false;\n    return value && typeof value === \"object\" && !Array.isArray(value);\n  }\n\n  // Main deep merge function\n  function merge(\n    target: Record<string, any>,\n    source: Record<string, any>,\n    replace: boolean = false\n  ): Record<string, any> {\n    const output = replace ? target : { ...target };\n\n    for (const key in source) {\n      if (isObject(source[key])) {\n        if (isObject(target[key])) {\n          output[key] = merge(target[key], source[key]);\n        } else {\n          output[key] = merge({}, source[key]);\n        }\n      } else if (source[key] !== undefined) {\n        output[key] = source[key];\n      }\n    }\n\n    return output;\n  }\n\n  // Start merging obj1 and obj2, then recursively merge the rest of the objects\n  let result = merge(obj1, obj2) as T & U;\n  for (const obj of rest) {\n    merge(result, obj, true) as T & U;\n  }\n\n  return result;\n}\n\nexport function deepClone<T>(obj: T, seen = new WeakMap()): T {\n  if (typeof obj !== \"object\" || obj === null) {\n    return obj;\n  }\n\n  if (seen.has(obj)) {\n    return seen.get(obj);\n  }\n\n  let clone: any;\n  if (Array.isArray(obj)) {\n    clone = [];\n    seen.set(obj, clone);\n    for (const item of obj) {\n      clone.push(deepClone(item, seen));\n    }\n  } else {\n    clone = Object.create(null); // Avoid prototype issues\n    seen.set(obj, clone);\n\n    for (const key of Object.keys(obj)) {\n      clone[key] = deepClone((obj as any)[key], seen);\n    }\n  }\n\n  return clone as T;\n}\n\nexport function getLastSeenTime(timestamp: number | null | undefined): string {\n  try {\n    if (timestamp === null || timestamp === undefined) {\n      // Move the hook call inside the function, just like in functions.js\n      return \"\";\n    }\n\n    // Convert to milliseconds if in seconds\n    if (String(timestamp).length === 10) {\n      timestamp *= 1000;\n    }\n\n    // Get user's current language from the translation hook (exactly like functions.js)\n    const { language, t } = useCometChatTranslation();\n\n    // Set the appropriate Day.js locale\n    dayjs.locale(language);\n\n    const now = dayjs();\n    const lastSeenTime = dayjs(timestamp);\n    const diffInMinutes = now.diff(lastSeenTime, \"minute\");\n    const diffInHours = now.diff(lastSeenTime, \"hour\");\n    const diffInDays = now.diff(lastSeenTime, \"day\");\n\n    // For very recent times (< 1 min) → \"a few seconds ago\"\n    if (diffInMinutes < 1) {\n      const relativeTimeString = lastSeenTime.fromNow();\n      // Day.js will give \"a few seconds ago\" localized\n      return `${t(\"LAST_SEEN\")} ${relativeTimeString}`;\n    }\n\n    // For times less than 24 hours → relative (\"X minutes ago\", \"X hours ago\")\n    if (diffInHours < 24) {\n      const relativeTimeString = lastSeenTime.fromNow();\n      return `${t(\"LAST_SEEN\")} ${relativeTimeString}`;\n    }\n\n    // For yesterday with time\n    if (diffInDays === 1) {\n      const timeFormat = localizedDateHelperInstance.getFormattedDate(\n        timestamp,\n        \"timeFormat\",\n        language\n      );\n      return `${t(\"LAST_SEEN\")} ${t(\"YESTERDAY\")} ${timeFormat}`;\n    }\n\n    // For dates within the past week → weekday + time\n    if (diffInDays < 7) {\n      const formattedDate = localizedDateHelperInstance.getFormattedDate(\n        timestamp,\n        \"dayWeekDayDateTimeFormat\",\n        language\n      );\n      return `${t(\"LAST_SEEN\")} ${formattedDate}`;\n    }\n\n    // Older dates → full date + time\n    return `${t(\"LAST_SEEN\")} ${localizedDateHelperInstance.getFormattedDate(\n      timestamp,\n      \"dayDateTimeFormat\",\n      language\n    )}`;\n  } catch (e) {\n    console.log(e);\n    return \"\";\n  }\n}"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/types/index.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport {\n  ColorValue,\n  DimensionValue,\n  ImageSourcePropType,\n  ImageStyle,\n  ImageURISource,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { Typography } from \"../../../theme/default\";\nimport { ActionItemInterface } from \"../../views\";\nimport { JSX } from \"react\";\n\nexport type ImageType = ImageURISource;\n\nexport type CometChatMessageComposerAction = ActionItemInterface & {\n  id?: any;\n  title?: string;\n  icon?: JSX.Element | ImageSourcePropType;\n  CustomView?: (\n    user: CometChat.User,\n    group: CometChat.Group,\n    id: string | number,\n    props: object\n  ) => JSX.Element;\n  onPress?: (user?: CometChat.User, group?: CometChat.Group) => void;\n  style?: Partial<{\n    containerStyle: ViewStyle;\n    iconStyle: ImageStyle;\n    iconContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n  }>;\n};\n\ntype DoNotPartial =\n  | TextStyle\n  | ViewStyle\n  | ImageURISource\n  | ImageStyle\n  | ColorValue\n  | DimensionValue\n  | ImageSourcePropType\n  | JSX.Element;\nexport type DeepPartial<T> = T extends object\n  ? T extends Typography\n    ? {\n        [P in keyof T]?: DeepPartial<T[P]>;\n      }\n    : T extends DoNotPartial\n    ? T\n    : {\n        [P in keyof T]?: DeepPartial<T[P]>;\n      }\n  : T;\n\nexport type ValueOf<T> = T[keyof T];\n\nexport type RequireAtLeastOne<T> = {\n  [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>;\n}[keyof T];\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/useKeyboard.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { Keyboard, KeyboardEvent } from \"react-native\";\n\nexport let isKeyboardVisible = false;\n\nexport const useKeyboard = () => {\n  const [keyboardHeight, setKeyboardHeight] = useState(0);\n\n  useEffect(() => {\n    function onKeyboardDidShow(e: KeyboardEvent) {\n      isKeyboardVisible = true;\n      // NOTE: LayoutAnimation removed to prevent Fabric crashes (SIGABRT)\n      // LayoutAnimation.configureNext() on iOS with Fabric causes crashes when\n      // components are removed during the animation (e.g., message list updates).\n      // The crash happens in RCTComponentViewRegistry.componentViewDescriptorWithTag\n      // when React tries to access a view that was destroyed during animation.\n      setKeyboardHeight(e.endCoordinates.height);\n    }\n\n    function onKeyboardDidHide() {\n      isKeyboardVisible = false;\n      // NOTE: LayoutAnimation removed to prevent Fabric crashes (SIGABRT)\n      setKeyboardHeight(0);\n    }\n\n    const showSubscription = Keyboard.addListener(\"keyboardDidShow\", onKeyboardDidShow);\n    const hideSubscription = Keyboard.addListener(\"keyboardDidHide\", onKeyboardDidHide);\n    return () => {\n      showSubscription.remove();\n      hideSubscription.remove();\n    };\n  }, []);\n\n  return keyboardHeight;\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/helper/useLocalizedDateHook.tsx",
    "content": "import { useMemo } from \"react\";\nimport { useCometChatTranslation } from \"../resources/CometChatLocalizeNew\";\nimport { localizedDateHelperInstance } from \"./../helper/LocalizedDateHelper\";\n\n/**\n * Hook to use localized date formatting with current language\n */\nexport const useLocalizedDate = () => {\n    const { language } = useCometChatTranslation();\n\n    return useMemo(() => ({\n        formatDate: (timestamp: number, pattern: string): string => {\n            return localizedDateHelperInstance.getFormattedDate(timestamp, pattern, language);\n        }\n    }), [language]);\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/Icon.tsx",
    "content": "import React, { JSX } from \"react\";\nimport {\n  ColorValue,\n  DimensionValue,\n  Image,\n  ImageSourcePropType,\n  ImageStyle,\n  StyleProp,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../theme\";\nimport { ICONS } from \"./icon-mapping\";\n\nexport type IconName = keyof typeof ICONS;\n\ntype IconProps = {\n  name?: IconName;\n  color?: ColorValue;\n  size?: DimensionValue;\n  height?: DimensionValue;\n  width?: DimensionValue;\n  containerStyle?: StyleProp<ViewStyle>;\n  imageStyle?: StyleProp<ImageStyle>;\n  icon?: ImageSourcePropType | JSX.Element;\n};\n\nexport const Icon = (props: IconProps) => {\n  const theme = useTheme();\n  const {\n    name,\n    color = theme.color.textSecondary,\n    containerStyle = {},\n    height,\n    width,\n    size = theme.spacing.spacing.s6,\n    imageStyle = { height: theme.spacing.spacing.s6, width: theme.spacing.spacing.s6 },\n    icon = null,\n  } = props;\n  if (React.isValidElement(icon)) {\n    return <View style={[containerStyle]}>{props.icon as JSX.Element}</View>;\n  }\n  if (props.icon) {\n    const ImageComp = () => {\n      if (typeof icon === \"number\") {\n        return <Image source={icon} style={imageStyle} />;\n      } else if (typeof icon === \"string\") {\n        return <Image source={{ uri: icon }} style={imageStyle} />;\n      }\n      return <Image source={icon as ImageSourcePropType} style={imageStyle} />;\n    };\n    return (\n      <View style={[containerStyle]}>\n        <ImageComp />\n      </View>\n    );\n  }\n  if (\"name\" in props && name) {\n    const IconComponent = ICONS[name];\n    if (IconComponent) {\n      return (\n        <View style={[containerStyle]}>\n          <IconComponent\n            color={color as any}\n            height={typeof height === \"number\" ? height : (size as number)}\n            width={typeof width === \"number\" ? width : (size as number)}\n          />\n        </View>\n      );\n    }\n  }\n  return null;\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ListItemCheck.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Path } from \"react-native-svg\";\n\nconst ListItemCheckIcon = ({ height = 16, width = 16, color = \"white\" }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 16 16' fill='none'>\n    <G id='Base_Icon'>\n      <Path\n        id='Vector'\n        d='M6.42713 10.1992L11.974 4.65234C12.0802 4.54818 12.2039 4.49609 12.3453 4.49609C12.4867 4.49609 12.6095 4.54833 12.7137 4.65281C12.8179 4.7574 12.8699 4.88151 12.8699 5.02516C12.8699 5.16891 12.8179 5.29287 12.7137 5.39703L6.79182 11.3242C6.68765 11.4284 6.56609 11.4805 6.42713 11.4805C6.28828 11.4805 6.16677 11.4284 6.0626 11.3242L3.27088 8.5325C3.16671 8.42792 3.11723 8.30365 3.12244 8.15969C3.12765 8.01583 3.18255 7.89182 3.28713 7.78766C3.39161 7.68349 3.51572 7.63141 3.65947 7.63141C3.80322 7.63141 3.92718 7.68349 4.03135 7.78766L6.42713 10.1992Z'\n        fill={color}\n        stroke={color}\n        strokeWidth='0.5'\n      />\n    </G>\n  </Svg>\n);\n\nexport default ListItemCheckIcon;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/StickerBase-icon.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill=\"none\"\n    viewBox=\"0 0 60 61\"\n  >\n    <Path\n      fill=\"#DCDCDC\"\n      d=\"M11.834 52.625q-1.6 0-2.78-1.178-1.178-1.18-1.178-2.78V12.333q0-1.6 1.178-2.78 1.18-1.178 2.78-1.178h36.334q1.6 0 2.78 1.178 1.178 1.18 1.178 2.78v36.334q0 1.6-1.178 2.78-1.18 1.178-2.78 1.178zm0-3.958h36.334V12.333H11.834zM17.157 43h25.774q.59 0 .872-.536.28-.537-.066-1.052l-7.066-9.387a.98.98 0 0 0-.795-.39.98.98 0 0 0-.792.386l-7.125 9.27-4.873-6.588a.98.98 0 0 0-.793-.39.98.98 0 0 0-.792.385l-5.085 6.714q-.415.515-.126 1.052.29.536.867.536\"\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/account-circle-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.023 17.292q1.275-.946 2.778-1.494A9.3 9.3 0 0 1 12 15.25q1.695 0 3.199.548t2.778 1.494a7.9 7.9 0 0 0 1.478-2.373A7.7 7.7 0 0 0 20 12q0-3.325-2.337-5.662Q15.325 4 12 4T6.338 6.338 4 12q0 1.57.545 2.92.545 1.347 1.478 2.372M12 12.75q-1.37 0-2.31-.94T8.75 9.5q0-1.37.94-2.31T12 6.25q1.37 0 2.31.94t.94 2.31q0 1.37-.94 2.31t-2.31.94m0 8.75a9.3 9.3 0 0 1-3.713-.744 9.5 9.5 0 0 1-3.016-2.027 9.5 9.5 0 0 1-2.027-3.016A9.3 9.3 0 0 1 2.5 12q0-1.98.744-3.713a9.5 9.5 0 0 1 2.027-3.016 9.5 9.5 0 0 1 3.016-2.027A9.3 9.3 0 0 1 12 2.5a9.3 9.3 0 0 1 3.713.744 9.5 9.5 0 0 1 3.016 2.027 9.5 9.5 0 0 1 2.027 3.016A9.3 9.3 0 0 1 21.5 12q0 1.98-.744 3.713a9.5 9.5 0 0 1-2.027 3.016 9.5 9.5 0 0 1-3.016 2.027A9.3 9.3 0 0 1 12 21.5'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/account-circle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.743 17.438q1.485-1.047 3-1.609a9.3 9.3 0 0 1 3.256-.562q1.742 0 3.26.562 1.52.563 3.02 1.608 1.046-1.262 1.508-2.601a8.6 8.6 0 0 0 .462-2.835q0-3.497-2.377-5.874T12 3.75 6.126 6.127Q3.75 8.505 3.75 12.001q0 1.496.471 2.835.471 1.34 1.523 2.601m6.255-4.638q-1.44 0-2.428-.989-.987-.989-.987-2.429t.988-2.428 2.43-.987 2.428.989q.987.988.987 2.428t-.989 2.428-2.429.988m.005 9.033a9.5 9.5 0 0 1-3.822-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.124 9.5 9.5 0 0 1-.777-3.824q0-2.026.777-3.817a10 10 0 0 1 2.113-3.125 10 10 0 0 1 3.124-2.11 9.5 9.5 0 0 1 3.824-.776q2.025 0 3.818.777a10 10 0 0 1 3.124 2.113 10 10 0 0 1 2.11 3.125 9.5 9.5 0 0 1 .776 3.814q0 2.027-.777 3.822a10 10 0 0 1-2.112 3.129 10 10 0 0 1-3.126 2.11 9.5 9.5 0 0 1-3.814.776m-.004-1.583q1.342 0 2.586-.387 1.244-.388 2.447-1.296a10.3 10.3 0 0 0-2.455-1.277A7.7 7.7 0 0 0 12 16.85a7.9 7.9 0 0 0-2.584.433q-1.26.434-2.451 1.284 1.204.907 2.447 1.296A8.6 8.6 0 0 0 12 20.25m0-9.033q.795 0 1.315-.519.518-.519.518-1.315t-.518-1.314-1.315-.519-1.314.519-.519 1.314.519 1.315 1.314.519'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/activity.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G clipPath='url(#prefix__a)'>\n      <Path\n        fill={color ?? \"#A1A1A1\"}\n        fillRule='evenodd'\n        d='M9 18.034A8.533 8.533 0 1 1 17.534 9.5 8.543 8.543 0 0 1 9 18.034m-3.034-1.625v-.191H7.64l2.1.799a7 7 0 0 1-.743.028 7.5 7.5 0 0 1-3.032-.636m5.052.297L7.907 15.57v-1.547l2.516-1.353 2.072.777-.044 2.156zm-.96-7.315.006 2.717-2.504 1.348-2.61-1.452-.014-2.9 2.14-1.389zm.407-.543L7.485 7.18l-.17-2.276 2.995-1.556 2.193 1.387v3.066zm4.078.969-1.369-2.17V4.99l1.343-.642c1.044 1.11 1.735 2.461 1.964 3.968l-.2 1.039zm.382.607 1.434-.331.144.282a7.6 7.6 0 0 1-1.009 2.98 7.7 7.7 0 0 1-1.75 2.02l-.577-.147V13.1zM9 1.948q.525 0 1.043.07v.704l-2.859 1.49-2.211-.003-.276-.912A7.48 7.48 0 0 1 9 1.948m-4.887 1.8.235.708-1.368 2.19-1.163.52a7.57 7.57 0 0 1 2.296-3.417m-2.53 4.315 1.499-.668 1.185 1.66.014 3.061h.005l-1.114 1.222-.617.1a7.55 7.55 0 0 1-.971-5.375m1.381 5.976.717-.115 1.635 1.872-.007.294a7.6 7.6 0 0 1-2.345-2.05'\n        clipRule='evenodd'\n      />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-a-photo-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.75 7a.73.73 0 0 1-.535-.215A.73.73 0 0 1 19 6.25V5h-1.25a.73.73 0 0 1-.535-.216A.73.73 0 0 1 17 4.25q0-.32.215-.534a.73.73 0 0 1 .535-.216H19V2.25q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534V3.5h1.25q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 21.75 5H20.5v1.25q0 .318-.216.535A.73.73 0 0 1 19.75 7M11 17.115q1.722 0 2.918-1.197 1.197-1.196 1.197-2.918t-1.197-2.918Q12.722 8.885 11 8.885t-2.918 1.197Q6.885 11.278 6.885 13t1.197 2.918T11 17.115m0-1.5q-1.108.001-1.861-.754-.755-.753-.755-1.861t.755-1.861q.753-.755 1.861-.755t1.861.755q.755.753.755 1.861t-.755 1.861q-.753.755-1.861.755M3.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V7.308q0-.758.525-1.283T3.308 5.5h3.054l1.307-1.417q.249-.27.598-.426A1.8 1.8 0 0 1 9 3.5h5.058q.318 0 .534.216a.73.73 0 0 1 .216.534v1.587q0 .564.395.96.396.396.96.395h.645v.644q0 .565.395.961.396.395.96.395h1.587q.318 0 .534.216a.73.73 0 0 1 .216.534v8.75q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-a-photo.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2.75 20.833a1.52 1.52 0 0 1-1.113-.471 1.52 1.52 0 0 1-.471-1.112V6.854q0-.624.471-1.104t1.112-.48h3.5l1.328-1.555q.212-.27.53-.41.318-.138.68-.138h5.008q.324 0 .556.232.231.233.231.558a.76.76 0 0 1-.231.564.76.76 0 0 1-.556.229H8.787L6.983 6.854H2.749V19.25h16.5v-9.046q0-.323.232-.556a.77.77 0 0 1 .563-.231q.33 0 .56.231.229.232.229.556v9.046q0 .64-.48 1.112-.48.471-1.104.471zM19.25 4.75h-1.288a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.563q0-.33.231-.56a.77.77 0 0 1 .565-.228h1.287V1.854q0-.323.232-.556a.77.77 0 0 1 .563-.231q.33 0 .56.231.229.232.229.556v1.313h1.312q.324 0 .556.232.232.233.232.558a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229h-1.312v1.287q0 .334-.233.565a.76.76 0 0 1-.558.231.76.76 0 0 1-.564-.231.77.77 0 0 1-.229-.565zM10.992 17.3q1.81 0 3.04-1.229t1.229-3.042q0-1.804-1.229-3.025t-3.041-1.22q-1.815 0-3.03 1.22-1.217 1.221-1.217 3.03 0 1.807 1.219 3.037 1.218 1.23 3.029 1.229m-.007-1.583q-1.14 0-1.9-.766-.757-.765-.757-1.913 0-1.145.758-1.91.758-.76 1.899-.761 1.149 0 1.92.762.772.762.772 1.91 0 1.146-.771 1.912-.772.766-1.921.766'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-box-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.25 12.75V16q0 .318.216.534a.73.73 0 0 0 .534.216q.32 0 .534-.216A.73.73 0 0 0 12.75 16v-3.25H16q.318 0 .534-.216A.73.73 0 0 0 16.75 12a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216h-3.25V8a.73.73 0 0 0-.216-.534A.73.73 0 0 0 12 7.25a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534v3.25H8a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 8 12.75zM5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h13.384q.758 0 1.283.525t.525 1.283v13.384q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-box.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.215 12.783v3.338q0 .333.232.565a.77.77 0 0 0 .563.23q.33 0 .56-.23a.77.77 0 0 0 .228-.565v-3.338h3.33a.76.76 0 0 0 .555-.232.77.77 0 0 0 .232-.562.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.229h-3.33V7.87a.76.76 0 0 0-.232-.555.76.76 0 0 0-.558-.232.76.76 0 0 0-.564.232.76.76 0 0 0-.229.556V11.2H7.878a.77.77 0 0 0-.565.233.76.76 0 0 0-.231.558q0 .334.23.563a.77.77 0 0 0 .566.23zM4.732 20.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V4.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h14.533q.64 0 1.112.471.471.472.471 1.112v14.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm0-1.583h14.533V4.733H4.732z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.242-3.709-3.704-5.242T3.5 4.56A1.03 1.03 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M16 8h-2.25a.73.73 0 0 1-.534-.216A.73.73 0 0 1 13 7.25q0-.32.216-.535a.73.73 0 0 1 .534-.215H16V4.25q0-.319.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534V6.5h2.25q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 19.75 8H17.5v2.25q0 .319-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.151 7.85h-2.304a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.563q0-.33.232-.56a.77.77 0 0 1 .564-.228h2.304V3.954q0-.323.232-.556a.77.77 0 0 1 .563-.231q.33 0 .56.231.229.232.229.556v2.313h2.312q.324 0 .556.232.231.233.231.558a.76.76 0 0 1-.231.564.76.76 0 0 1-.556.229h-2.312v2.304q0 .333-.233.565a.76.76 0 0 1-.558.231.76.76 0 0 1-.563-.231.77.77 0 0 1-.23-.565zm3.6 12.983q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.335-.026.616a1 1 0 0 1-.26.477L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-circle-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.25 12.75V16q0 .318.216.534a.73.73 0 0 0 .534.216q.32 0 .534-.216A.73.73 0 0 0 12.75 16v-3.25H16q.318 0 .534-.216A.73.73 0 0 0 16.75 12a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216h-3.25V8a.73.73 0 0 0-.216-.534A.73.73 0 0 0 12 7.25a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534v3.25H8a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 8 12.75zm.752 8.75a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-circle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.25 12.75V16q0 .318.216.534a.73.73 0 0 0 .534.216q.32 0 .534-.216A.73.73 0 0 0 12.75 16v-3.25H16q.318 0 .534-.216A.73.73 0 0 0 16.75 12a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216h-3.25V8a.73.73 0 0 0-.216-.534A.73.73 0 0 0 12 7.25a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534v3.25H8a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 8 12.75zm.752 8.75a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749M12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-comment-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.039 17.5-2.002 2.002q-.427.426-.982.192-.555-.235-.555-.84V4.308q0-.758.525-1.283T4.308 2.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zm5.211-6.75V13q0 .319.216.534a.73.73 0 0 0 .534.216q.32 0 .534-.216A.73.73 0 0 0 12.75 13v-2.25H15q.319 0 .534-.216A.73.73 0 0 0 15.75 10a.73.73 0 0 0-.216-.534A.73.73 0 0 0 15 9.25h-2.25V7a.73.73 0 0 0-.216-.534A.73.73 0 0 0 12 6.25a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534v2.25H9a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 9 10.75z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-comment.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.212 10.783v2.338q0 .333.228.564.228.232.562.232t.564-.232a.77.77 0 0 0 .229-.564v-2.338h2.338q.333 0 .564-.232a.77.77 0 0 0 .232-.562.76.76 0 0 0-.232-.56.77.77 0 0 0-.564-.229h-2.338V6.854a.76.76 0 0 0-.232-.556.77.77 0 0 0-.567-.231.75.75 0 0 0-.56.231.77.77 0 0 0-.224.556V9.2H8.866a.75.75 0 0 0-.56.234.78.78 0 0 0-.227.56q0 .327.227.558a.76.76 0 0 0 .56.231zm-5.15 7.067L3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.25 12.75h-5a.73.73 0 0 1-.534-.216A.73.73 0 0 1 5.5 12q0-.32.216-.534a.73.73 0 0 1 .534-.216h5v-5q0-.319.216-.534A.73.73 0 0 1 12 5.5q.32 0 .534.216a.73.73 0 0 1 .216.534v5h5q.318 0 .534.216A.73.73 0 0 1 18.5 12q0 .32-.216.534a.73.73 0 0 1-.534.216h-5v5q0 .318-.216.534A.73.73 0 0 1 12 18.5a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-reaction-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12.002 21.5a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.017-2.032A9.25 9.25 0 0 1 12 2.5q.831 0 1.626.13.795.129 1.551.405.32.116.505.397.183.283.183.622v.944q0 .729.512 1.24.513.512 1.24.512h.624v1.152q0 .497.367.865.368.368.865.368h.823q.336 0 .612.204a.97.97 0 0 1 .367.544q.125.51.175 1.034T21.5 12q0 1.972-.748 3.705a9.6 9.6 0 0 1-2.03 3.016 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749M12 17.192q1.346 0 2.484-.637a5.2 5.2 0 0 0 1.847-1.73.55.55 0 0 0-.009-.549.5.5 0 0 0-.472-.276h-7.7a.5.5 0 0 0-.472.276.55.55 0 0 0-.009.549 5.2 5.2 0 0 0 1.859 1.73q1.149.637 2.472.637m-3.402-6.384q.546 0 .926-.383.38-.381.38-.927t-.382-.926a1.27 1.27 0 0 0-.928-.38q-.546 0-.926.383-.38.381-.38.927t.382.926q.383.38.928.38m6.808 0q.546 0 .926-.383.38-.381.38-.927t-.383-.926a1.27 1.27 0 0 0-.928-.38q-.545 0-.925.383-.38.381-.38.927t.382.926q.382.38.928.38M20.25 4.75H19a.73.73 0 0 1-.534-.216A.73.73 0 0 1 18.25 4q0-.32.216-.534A.73.73 0 0 1 19 3.25h1.25V2q0-.318.216-.534A.73.73 0 0 1 21 1.25q.32 0 .535.216A.73.73 0 0 1 21.75 2v1.25H23q.318 0 .534.216A.73.73 0 0 1 23.75 4q0 .32-.216.535A.73.73 0 0 1 23 4.75h-1.25V6q0 .319-.216.534A.73.73 0 0 1 21 6.75a.73.73 0 0 1-.535-.216A.73.73 0 0 1 20.25 6z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add-reaction.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.005 21.833a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.124 9.5 9.5 0 0 1-.777-3.822q0-2.037.779-3.832a10 10 0 0 1 2.109-3.124A9.9 9.9 0 0 1 8.18 2.94 9.6 9.6 0 0 1 12 2.167a9.6 9.6 0 0 1 3.574.683q.446.183.554.491a.85.85 0 0 1-.36 1.032q-.261.156-.573.006a8.4 8.4 0 0 0-1.549-.47A8.4 8.4 0 0 0 12 3.75q-3.426 0-5.838 2.406T3.75 11.992t2.415 5.844 5.843 2.414 5.836-2.412T20.249 12q0-.573-.075-1.135a8 8 0 0 0-.229-1.107q-.108-.344.082-.59a.9.9 0 0 1 .476-.32.8.8 0 0 1 .58.067q.295.143.42.568a9.6 9.6 0 0 1 .33 2.517q0 2.025-.774 3.823a9.9 9.9 0 0 1-2.104 3.126 10 10 0 0 1-3.123 2.105 9.5 9.5 0 0 1-3.827.78m8.207-17.045h-1.38a.76.76 0 0 1-.56-.228.77.77 0 0 1-.227-.563.77.77 0 0 1 .227-.564.76.76 0 0 1 .56-.229h1.38v-1.37q0-.334.228-.565a.76.76 0 0 1 .562-.231q.334 0 .564.23a.77.77 0 0 1 .229.565v1.371h1.371q.333 0 .564.232a.77.77 0 0 1 .232.567q0 .335-.232.56a.78.78 0 0 1-.564.225h-1.371v1.379a.75.75 0 0 1-.232.56.78.78 0 0 1-.567.227.76.76 0 0 1-.56-.227.77.77 0 0 1-.224-.56zm-4.665 5.979q.57 0 .969-.399.4-.397.4-.966 0-.57-.398-.969a1.31 1.31 0 0 0-.967-.4q-.568 0-.969.398-.4.399-.4.967 0 .57.399.969.398.4.966.4m-7.1 0q.57 0 .969-.399.4-.397.4-.966 0-.57-.398-.969a1.31 1.31 0 0 0-.967-.4q-.569 0-.968.398-.4.399-.4.967 0 .57.398.969.397.4.966.4m3.542 6.616a5.35 5.35 0 0 0 4.54-2.446.54.54 0 0 0-.018-.562.55.55 0 0 0-.505-.275h-8.01q-.347 0-.511.275a.52.52 0 0 0-.006.556 5.2 5.2 0 0 0 1.93 1.802q1.195.65 2.58.65'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/add.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.167 12.833H5.833a.8.8 0 0 1-.594-.24.8.8 0 0 1-.239-.596q0-.355.24-.593a.8.8 0 0 1 .593-.237h5.334V5.833q0-.354.24-.593.24-.24.596-.24a.8.8 0 0 1 .593.24.8.8 0 0 1 .237.593v5.334h5.334q.354 0 .593.24.24.241.24.596a.8.8 0 0 1-.24.593.8.8 0 0 1-.593.237h-5.334v5.334q0 .354-.24.594a.8.8 0 0 1-.596.24.8.8 0 0 1-.593-.24.8.8 0 0 1-.237-.594z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-chat-history.tsx",
    "content": "import * as React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nfunction SvgComponent({ width = 24, height = 24, color = \"#A1A1A1\", ...props }) {\n  return (\n    <Svg\n      width={width}\n      height={height}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      {...props}\n    >\n      <Path\n        d=\"M11.944 20.85c-2.24 0-4.18-.734-5.82-2.202-1.638-1.468-2.6-3.293-2.882-5.475a.67.67 0 01.167-.566.758.758 0 01.558-.27c.21-.01.396.052.556.188.16.136.26.313.303.53.252 1.775 1.045 3.253 2.379 4.437 1.333 1.183 2.913 1.775 4.738 1.775 2.035 0 3.764-.716 5.186-2.147 1.422-1.43 2.133-3.164 2.133-5.2 0-2.01-.717-3.711-2.15-5.101-1.435-1.39-3.162-2.086-5.183-2.086a6.661 6.661 0 00-3.146.769 8.182 8.182 0 00-2.52 2.048h1.82c.225 0 .414.076.567.23a.76.76 0 01.229.557.77.77 0 01-.23.567.77.77 0 01-.566.23H4.367a.777.777 0 01-.788-.796V4.633c0-.218.075-.403.225-.555a.757.757 0 01.563-.23c.225 0 .413.077.566.23a.76.76 0 01.23.558v1.746a9.106 9.106 0 013.026-2.375 8.422 8.422 0 013.74-.858c1.228 0 2.383.23 3.466.69a9.005 9.005 0 012.835 1.885 8.92 8.92 0 011.913 2.797 8.48 8.48 0 01.703 3.443 8.606 8.606 0 01-.703 3.462 8.993 8.993 0 01-1.91 2.823 9.021 9.021 0 01-2.827 1.902 8.65 8.65 0 01-3.462.698zm.885-9.203l2.72 2.69a.782.782 0 01.242.571c0 .22-.08.409-.241.567a.77.77 0 01-1.118-.005l-2.952-2.936a.77.77 0 01-.235-.572V7.821c0-.219.075-.405.226-.558a.755.755 0 01.562-.23.77.77 0 01.567.23.76.76 0 01.229.558v3.826z\"\n        fill={color}  \n      />\n    </Svg>\n  );\n}\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-conversation-summary-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      fillRule='evenodd'\n      d='M4.537 20.002 6.539 18h13.653q.758 0 1.283-.525T22 16.192V4.808q0-.758-.525-1.283A1.75 1.75 0 0 0 20.192 3H4.808q-.758 0-1.283.525T3 4.808v14.546q0 .605.555.84t.982-.192M15.64 5.513c-.004-.017-.03-.017-.033 0a2.37 2.37 0 0 1-1.845 1.846c-.018.003-.018.029 0 .032.929.19 1.655.916 1.845 1.846.004.017.03.017.033 0 .19-.93.916-1.656 1.845-1.846.018-.003.018-.029 0-.032a2.37 2.37 0 0 1-1.845-1.846m-4.142 2.69c-.056-.27-.442-.27-.498 0a4.23 4.23 0 0 1-3.298 3.298c-.27.056-.27.442 0 .498a4.23 4.23 0 0 1 3.298 3.298c.056.27.442.27.498 0a4.23 4.23 0 0 1 3.298-3.298c.27-.056.27-.442 0-.498a4.23 4.23 0 0 1-3.298-3.298'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-conversation-summary.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      stroke={color}\n      strokeWidth={1.5}\n      d='M4.537 20.002 6.539 18h13.653q.758 0 1.283-.525T22 16.192V4.808q0-.758-.525-1.283A1.75 1.75 0 0 0 20.192 3H4.808q-.758 0-1.283.525T3 4.808v14.546q0 .605.555.84t.982-.192ZM16.013 6.51c-.003-.015-.024-.015-.027 0a1.89 1.89 0 0 1-1.476 1.476c-.014.003-.014.023 0 .026a1.89 1.89 0 0 1 1.476 1.476c.003.015.024.015.027 0a1.89 1.89 0 0 1 1.476-1.476c.014-.003.014-.023 0-.026a1.89 1.89 0 0 1-1.476-1.476Zm-5.314 2.151c-.044-.216-.354-.216-.398 0a3.39 3.39 0 0 1-2.639 2.639c-.216.044-.216.354 0 .398a3.39 3.39 0 0 1 2.639 2.639c.044.216.354.216.398 0a3.39 3.39 0 0 1 2.639-2.639c.216-.044.216-.354 0-.398a3.39 3.39 0 0 1-2.64-2.639Z'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-copy-option.tsx",
    "content": "import * as React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nfunction SvgComponent({ width = 24, height = 24, color = \"#A1A1A1\", ...props }) {\n  return (\n    <Svg\n      width={width}\n      height={height}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      {...props}\n    >\n      <Path\n        d=\"M4.598 14.556c-.356 0-.664-.131-.926-.393a1.267 1.267 0 01-.393-.927V2.125c0-.356.13-.665.393-.927.262-.261.57-.392.926-.392h8.611c.357 0 .665.13.927.392s.393.571.393.927v11.111c0 .356-.131.665-.393.927s-.57.393-.926.393H4.598zm0-1.32h8.611V2.125H4.6v11.111zM1.96 17.194c-.356 0-.665-.13-.927-.392a1.267 1.267 0 01-.393-.927V4.101c0-.18.065-.334.193-.463a.638.638 0 01.47-.194.63.63 0 01.465.194.635.635 0 01.192.463v11.774h9.274a.63.63 0 01.463.194c.129.13.193.284.193.465a.635.635 0 01-.193.47.635.635 0 01-.463.19H1.96z\"\n        fill={color}\n      />\n    </Svg>\n  );\n}\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M9.003 7.406c.11-.541.884-.541.994 0a8.46 8.46 0 0 0 6.597 6.597c.541.11.541.884 0 .995a8.46 8.46 0 0 0-6.597 6.596c-.11.541-.884.541-.994 0a8.46 8.46 0 0 0-6.597-6.597c-.541-.11-.541-.884 0-.995a8.46 8.46 0 0 0 6.597-6.596M18.217 2.027c.007-.036.059-.036.066 0a4.73 4.73 0 0 0 3.69 3.69c.036.007.036.059 0 .066a4.73 4.73 0 0 0-3.69 3.69c-.007.036-.059.036-.066 0a4.73 4.73 0 0 0-3.69-3.69c-.036-.007-.036-.059 0-.066a4.73 4.73 0 0 0 3.69-3.69'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-new-chat.tsx",
    "content": "import * as React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nfunction SvgComponent({ width = 24, height = 24, color = \"#A1A1A1\", ...props }) {\n  return (\n    <Svg\n      width={width}\n      height={height}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      {...props}\n    >\n      <Path\n        d=\"M11.167 12.833H5.833a.805.805 0 01-.594-.24.811.811 0 01-.239-.596.8.8 0 01.24-.593.81.81 0 01.593-.237h5.334V5.833c0-.236.08-.434.24-.594.16-.16.359-.239.596-.239a.8.8 0 01.593.24.81.81 0 01.237.593v5.334h5.334c.236 0 .434.08.593.24.16.16.24.359.24.596a.8.8 0 01-.24.593.81.81 0 01-.593.237h-5.334v5.334c0 .236-.08.434-.24.593a.811.811 0 01-.596.24.8.8 0 01-.593-.24.81.81 0 01-.237-.593v-5.334z\"\n        fill={color}\n      />\n    </Svg>\n  );\n}\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-send-button.tsx",
    "content": "import * as React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nfunction SvgComponent({ width = 20, height = 20, color = \"#000\", ...props }) {\n  return (\n    <Svg\n      width={width}\n      height={height}\n      viewBox=\"0 0 20 20\"\n      fill=\"none\"\n      {...props}\n    >\n      <Path\n        d=\"M10 16.667V3.333m0 0l-5 5m5-5l5 5\"\n        stroke={color}       \n        strokeWidth={1.5}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </Svg>\n  );\n}\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-suggest-reply-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      fillRule='evenodd'\n      d='M3.025 17.975q.525.525 1.283.525h13.654l2.002 2.002q.426.427.981.193t.555-.841V5.308q0-.758-.525-1.283a1.75 1.75 0 0 0-1.283-.525H4.308q-.758 0-1.283.525T2.5 5.308v11.384q0 .758.525 1.283m7.96-2.25q.287.275.684.275a.96.96 0 0 0 .685-.275.99.99 0 0 0 .313-.673h-1.996a.99.99 0 0 0 .313.673m-1.168-1.511a.38.38 0 0 0 .28.112h3.145a.38.38 0 0 0 .28-.112.38.38 0 0 0 .113-.28.38.38 0 0 0-.113-.28.38.38 0 0 0-.28-.114h-3.145a.38.38 0 0 0-.28.113.38.38 0 0 0-.113.28.38.38 0 0 0 .113.28M8.48 11.476q.48.827 1.304 1.338h3.77a3.8 3.8 0 0 0 1.304-1.338q.48-.825.48-1.807 0-1.532-1.068-2.6-1.069-1.07-2.6-1.069-1.534 0-2.601 1.069Q7.999 8.137 8 9.669q0 .982.48 1.807'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai-suggest-reply.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.308 18.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T4.308 3.5h15.384q.758 0 1.283.525t.525 1.283v14.546q0 .607-.555.841t-.982-.193L17.963 18.5zM18.6 17l1.4 1.385V5.308a.3.3 0 0 0-.096-.212.3.3 0 0 0-.212-.096H4.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v11.384q0 .116.096.212a.3.3 0 0 0 .212.096z'\n    />\n    <Path\n      fill='#F5F5F5'\n      d='M11.67 16a.96.96 0 0 1-.686-.275.99.99 0 0 1-.313-.673h1.996a.99.99 0 0 1-.313.673.96.96 0 0 1-.685.275m-1.573-1.674a.38.38 0 0 1-.28-.112.38.38 0 0 1-.113-.28.38.38 0 0 1 .113-.28.38.38 0 0 1 .28-.114h3.145a.38.38 0 0 1 .28.113.38.38 0 0 1 .113.28.38.38 0 0 1-.113.28.38.38 0 0 1-.28.114zm-.313-1.511a3.8 3.8 0 0 1-1.304-1.339A3.53 3.53 0 0 1 8 9.67q0-1.532 1.069-2.6 1.068-1.07 2.6-1.069 1.533 0 2.601 1.069 1.07 1.068 1.069 2.6a3.53 3.53 0 0 1-.48 1.807q-.48.827-1.304 1.338z'\n    />\n    <Path\n      stroke='#000'\n      strokeWidth={1.5}\n      d='m20.494 19.972-2.002-2.002-.22-.22h.02l1.18 1.168 1.12 1.107-.025.003a.2.2 0 0 1-.073-.056ZM9.129 11.099a2.8 2.8 0 0 1-.379-1.43c0-.825.28-1.5.849-2.07.57-.57 1.245-.849 2.07-.849.826 0 1.501.28 2.07.849.57.57.85 1.245.85 2.07 0 .525-.127.997-.38 1.43a3.04 3.04 0 0 1-.88.966H10.01a3.04 3.04 0 0 1-.881-.966ZM4.308 17.75c-.311 0-.547-.1-.753-.305a1 1 0 0 1-.305-.753c0 .31.138.564.315.741.178.179.431.317.743.317ZM3.25 5.308c0-.311.1-.547.305-.753a1 1 0 0 1 .753-.305c-.311 0-.564.138-.742.316a1.04 1.04 0 0 0-.316.742ZM19.692 4.25c.311 0 .546.1.753.305a1 1 0 0 1 .305.753c0-.311-.138-.564-.316-.742a1.04 1.04 0 0 0-.742-.316Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/ai.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      stroke={color}\n      strokeWidth={1.5}\n      d='M9.302 7.865c.1-.487.796-.487.896 0a7.62 7.62 0 0 0 5.936 5.937c.488.1.488.796 0 .896a7.62 7.62 0 0 0-5.936 5.936c-.1.488-.796.488-.896 0a7.62 7.62 0 0 0-5.937-5.936c-.487-.1-.487-.796 0-.896a7.62 7.62 0 0 0 5.937-5.937ZM17.595 3.024c.007-.032.053-.032.06 0a4.26 4.26 0 0 0 3.32 3.321c.033.007.033.053 0 .06a4.26 4.26 0 0 0-3.32 3.32c-.007.033-.053.033-.06 0a4.26 4.26 0 0 0-3.32-3.32c-.033-.007-.033-.053 0-.06a4.26 4.26 0 0 0 3.32-3.32Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/alternate-email-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 21.5q-1.972 0-3.705-.748a9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.03-3.016 9.25 9.25 0 0 1-.749-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.017A9.25 9.25 0 0 1 21.5 12v1.22q0 1.368-.94 2.324t-2.31.956q-.885 0-1.64-.433a3.2 3.2 0 0 1-1.195-1.19 4.6 4.6 0 0 1-1.531 1.198A4.3 4.3 0 0 1 12 16.5q-1.873 0-3.187-1.313Q7.5 13.873 7.5 12t1.313-3.187Q10.128 7.5 12 7.5t3.187 1.313Q16.5 10.128 16.5 12v1.22q0 .736.507 1.258T18.25 15t1.243-.522.507-1.259V12q0-3.35-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20h4.25q.318 0 .535.216a.73.73 0 0 1 .215.534q0 .32-.215.535a.73.73 0 0 1-.535.215zm0-6.5q1.25 0 2.125-.875A2.9 2.9 0 0 0 15 12q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 9q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 12q0 1.25.875 2.125A2.9 2.9 0 0 0 12 15'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/alternate-email.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.001 21.833q-2.041 0-3.836-.775a9.9 9.9 0 0 1-3.123-2.104 9.9 9.9 0 0 1-2.101-3.122 9.6 9.6 0 0 1-.773-3.831q0-2.04.775-3.836a9.9 9.9 0 0 1 2.104-3.124A9.9 9.9 0 0 1 8.17 2.939 9.6 9.6 0 0 1 12 2.167q2.041 0 3.836.775a9.9 9.9 0 0 1 3.124 2.104 9.9 9.9 0 0 1 2.102 3.123 9.6 9.6 0 0 1 .772 3.834v1.33q0 1.39-.977 2.354-.978.963-2.389.963a3.4 3.4 0 0 1-1.661-.423 3.2 3.2 0 0 1-1.222-1.177q-.655.804-1.604 1.202a5.1 5.1 0 0 1-1.976.398q-1.943 0-3.307-1.352t-1.364-3.302T8.698 8.68q1.364-1.365 3.303-1.364 1.94 0 3.303 1.364 1.364 1.365 1.364 3.315v1.337q0 .727.527 1.23.526.504 1.264.504.739 0 1.265-.504.528-.502.527-1.23V12q0-3.452-2.399-5.851t-5.85-2.399q-3.453 0-5.852 2.399T3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399h4.396q.324 0 .556.233a.76.76 0 0 1 .232.558q0 .334-.232.563a.76.76 0 0 1-.556.23zm0-6.766q1.283 0 2.184-.894.9-.894.9-2.171 0-1.302-.9-2.202-.899-.9-2.183-.9t-2.184.898-.9 2.2q0 1.277.899 2.173.9.896 2.183.896'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/animals-&-nature.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G clipPath='url(#prefix__a)'>\n      <Path\n        fill={color ?? \"#A1A1A1\"}\n        d='M5.356 16.284H4.81a4.6 4.6 0 0 1-2.687-1.072A4.56 4.56 0 0 1 .618 12.75 4.99 4.99 0 0 1 2.322 7.9a6.4 6.4 0 0 1-1.34-2.618 3.37 3.37 0 0 1 .847-3.223A3.33 3.33 0 0 1 4.219.967h.055a5.24 5.24 0 0 1 3.315 1.496l.086.077a6.55 6.55 0 0 1 2.85-.077A5.23 5.23 0 0 1 13.836.967h.057a3.33 3.33 0 0 1 2.39 1.092 3.38 3.38 0 0 1 .846 3.23 6.3 6.3 0 0 1-1.345 2.619 4.986 4.986 0 0 1 1.695 4.84 4.57 4.57 0 0 1-1.512 2.465 4.6 4.6 0 0 1-2.691 1.07h-.544a4.8 4.8 0 0 1-3.677 1.75 4.9 4.9 0 0 1-3.7-1.75M3.64 7.962l-.107.32-.268.208c-.083.072-2.23 1.793-1.614 3.967a3.45 3.45 0 0 0 1.121 1.916 3.47 3.47 0 0 0 2.06.841h.964a.537.537 0 0 1 .536.55v.005a3.86 3.86 0 0 0 2.728 1.195 3.8 3.8 0 0 0 2.75-1.248.534.534 0 0 1 .537-.499h.933a3.46 3.46 0 0 0 2.05-.845 3.44 3.44 0 0 0 1.113-1.915c.616-2.176-1.534-3.898-1.627-3.97l-.3-.235-.09-.374c-.043-.183-1.085-4.47-5.158-4.47-4.133 0-5.565 4.368-5.624 4.554zM2.61 2.796c-1.378 1.443-.248 3.36.3 4.119A8.1 8.1 0 0 1 6.529 2.96a3.9 3.9 0 0 0-2.251-.924h-.04a2.26 2.26 0 0 0-1.626.76m9.128.048a7.08 7.08 0 0 1 3.5 4.026c.537-.748 1.633-2.647.272-4.074a2.25 2.25 0 0 0-1.635-.76h-.038a3.73 3.73 0 0 0-2.103.808zm-2.68 12.731a1.06 1.06 0 0 1-.716.269 1.075 1.075 0 0 1-1.096-1.034l.714-.032a.366.366 0 0 0 .382.355.33.33 0 0 0 .25-.092.4.4 0 0 0 .098-.259v-.78a2.2 2.2 0 0 1-1.211-1.457.573.573 0 0 1 .59-.581h1.96a.58.58 0 0 1 .59.581 2.19 2.19 0 0 1-1.202 1.45v.785c.001.095.036.187.1.259a.33.33 0 0 0 .249.092.37.37 0 0 0 .349-.215.4.4 0 0 0 .033-.14l.714.032a1.067 1.067 0 0 1-1.096 1.033 1.05 1.05 0 0 1-.714-.266zm2.485-5.752c0-.591.321-1.069.715-1.069s.715.479.715 1.069-.322 1.069-.715 1.069c-.394 0-.715-.48-.715-1.07m-6.434 0c0-.591.322-1.069.715-1.069.394 0 .715.479.715 1.069s-.321 1.069-.715 1.069-.716-.48-.716-1.07z'\n      />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/archive-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5.385 20.5q-.75 0-1.317-.568-.568-.57-.568-1.317V7.005q0-.32.103-.612.102-.291.309-.537l1.404-1.698q.245-.321.615-.49.369-.168.78-.168H17.27q.412 0 .786.168.374.169.62.49l1.413 1.717q.206.246.309.542t.103.618v11.58q0 .75-.568 1.317-.57.568-1.317.568zM5.39 6.404H18.6l-1.09-1.298a.4.4 0 0 0-.111-.077.3.3 0 0 0-.13-.029H6.721a.3.3 0 0 0-.13.029.4.4 0 0 0-.11.077zM12 10.289a.73.73 0 0 0-.534.215.73.73 0 0 0-.216.535v3.7l-1.323-1.324a.73.73 0 0 0-.522-.212.7.7 0 0 0-.532.213.72.72 0 0 0-.217.526q0 .31.217.527l2.494 2.495q.271.27.633.27.361 0 .633-.27l2.494-2.495a.73.73 0 0 0 .212-.522.7.7 0 0 0-.212-.531.72.72 0 0 0-.527-.218.72.72 0 0 0-.527.217L12.75 14.74v-3.7a.73.73 0 0 0-.216-.535.73.73 0 0 0-.534-.215'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/archive.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.996 9.567a.75.75 0 0 0-.56.231.77.77 0 0 0-.224.556v3.95l-1.45-1.45a.74.74 0 0 0-.535-.212.73.73 0 0 0-.536.222.74.74 0 0 0-.221.54q0 .317.225.533l2.75 2.75q.233.246.553.246a.77.77 0 0 0 .564-.246l2.754-2.754a.73.73 0 0 0 .223-.536.72.72 0 0 0-.22-.539.74.74 0 0 0-.54-.22.73.73 0 0 0-.534.217l-1.45 1.45v-3.95a.76.76 0 0 0-.232-.557.77.77 0 0 0-.567-.231m-7.247-2.1V19.25h14.5V7.467zm.05 13.366a1.58 1.58 0 0 1-1.154-.475 1.55 1.55 0 0 1-.479-1.141V6.458q0-.252.085-.504.086-.251.244-.454l1.267-1.708q.212-.297.542-.46.33-.165.716-.165h11.942q.385 0 .715.164.33.165.552.46L20.512 5.5a1.7 1.7 0 0 1 .32.958v12.756q0 .674-.48 1.146a1.58 1.58 0 0 1-1.146.473zm.363-14.95h13.67l-.89-1.133H6.031z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/arrow-back-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m7.373 12.75 5.17 5.17a.7.7 0 0 1 .22.521.74.74 0 0 1-.236.532.78.78 0 0 1-.527.225.7.7 0 0 1-.527-.225l-6.34-6.34A.83.83 0 0 1 4.877 12q0-.18.058-.336a.8.8 0 0 1 .198-.297l6.34-6.34a.72.72 0 0 1 .515-.213.75.75 0 0 1 .539.213.74.74 0 0 1 .232.534q0 .303-.232.535L7.373 11.25H18.75q.32 0 .535.216A.73.73 0 0 1 19.5 12a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/arrow-back.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m7.193 12.787 5.366 5.367a.8.8 0 0 1 .24.563.75.75 0 0 1-.236.554.79.79 0 0 1-.562.243.74.74 0 0 1-.554-.243L4.73 12.554a.75.75 0 0 1-.185-.256.8.8 0 0 1-.057-.298.8.8 0 0 1 .057-.298.8.8 0 0 1 .185-.265l6.717-6.716a.76.76 0 0 1 .552-.234q.318 0 .564.234a.8.8 0 0 1 .238.564q0 .319-.238.557l-5.37 5.362h11.854a.76.76 0 0 1 .558.23.77.77 0 0 1 .229.566.76.76 0 0 1-.229.562.77.77 0 0 1-.558.225z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/arrow-forward-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M16.627 12.75H5.25a.73.73 0 0 1-.535-.216A.73.73 0 0 1 4.5 12q0-.32.215-.534a.73.73 0 0 1 .535-.216h11.377l-5.17-5.17a.7.7 0 0 1-.22-.521q.003-.3.236-.532A.78.78 0 0 1 12 4.802a.7.7 0 0 1 .527.225l6.34 6.34a.83.83 0 0 1 .256.633.828.828 0 0 1-.256.633l-6.34 6.34a.72.72 0 0 1-.514.213.75.75 0 0 1-.54-.213.74.74 0 0 1-.232-.534q0-.303.232-.535z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/arrow-forward.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.81 12.787H4.964A.777.777 0 0 1 4.168 12a.77.77 0 0 1 .229-.567.77.77 0 0 1 .567-.229H16.81l-5.367-5.367a.767.767 0 0 1 .004-1.117.778.778 0 0 1 1.117 0l6.717 6.717a1 1 0 0 1 .18.265q.06.135.061.298 0 .162-.06.298t-.181.256l-6.717 6.717a.77.77 0 0 1-1.117 0 .75.75 0 0 1 0-1.113z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/attach-file-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17.673 15.644q0 2.436-1.695 4.146t-4.127 1.71q-2.43 0-4.131-1.71-1.7-1.71-1.7-4.146v-8.99q0-1.731 1.202-2.942Q8.422 2.5 10.154 2.5q1.73 0 2.933 1.212 1.202 1.21 1.202 2.942v8.51a2.38 2.38 0 0 1-.707 1.733 2.33 2.33 0 0 1-1.725.718 2.37 2.37 0 0 1-1.734-.713 2.36 2.36 0 0 1-.719-1.739V7.136q0-.318.216-.535a.73.73 0 0 1 .535-.215q.318 0 .534.215a.73.73 0 0 1 .215.535v8.029q0 .402.27.677t.673.275a.9.9 0 0 0 .672-.275.93.93 0 0 0 .27-.677v-8.52q-.015-1.107-.77-1.876Q11.266 4 10.154 4q-1.107 0-1.87.773a2.58 2.58 0 0 0-.764 1.88v8.991q-.015 1.814 1.253 3.085 1.27 1.27 3.082 1.271 1.786 0 3.037-1.271 1.25-1.272 1.281-3.085v-8.51q0-.318.216-.534a.73.73 0 0 1 .535-.215q.318 0 .534.215a.73.73 0 0 1 .215.535z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/attach-file.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.084 15.77q0 2.532-1.768 4.298-1.767 1.765-4.3 1.765T7.71 20.068t-1.775-4.301V6.483q0-1.806 1.266-3.061 1.268-1.255 3.05-1.255 1.787 0 3.05 1.26t1.263 3.056v8.788q0 1.058-.745 1.804a2.46 2.46 0 0 1-1.807.746 2.46 2.46 0 0 1-1.804-.74 2.46 2.46 0 0 1-.744-1.81v-8.3q0-.324.228-.556a.76.76 0 0 1 .562-.232q.335 0 .564.232.23.232.23.556v8.3q0 .4.28.683a.94.94 0 0 0 .694.284.9.9 0 0 0 .686-.284.95.95 0 0 0 .273-.683V6.483q-.004-1.15-.796-1.941-.79-.792-1.937-.792-1.146 0-1.938.793-.792.792-.792 1.94v9.28q-.004 1.878 1.31 3.183t3.192 1.304q1.867 0 3.174-1.304 1.308-1.305 1.307-3.18V6.972q0-.324.232-.556a.77.77 0 0 1 .563-.232q.33 0 .56.232.228.232.229.556z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/audio-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#F16C00'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <Path\n      fill='#fff'\n      d='M21.62 15.974a1.5 1.5 0 0 1-.229.55c-.09.139-.183.231-.275.184-.091 0-.139-.597-.183-.69q-.106-.241-.137-.505c-.046-.367-.184-.691-.415-.828q-.343-.276-.962-.415a3 3 0 0 1-1.24-.55c-.366-.277-.779-.507-1.008-.783-.23-.23-.414-.277-.505-.23-.138.046-.183.184-.183.322v8.555c0 .23-.046.506-.138.782-.091.277-.138.552-.367.827-.23.277-.55.46-.963.645-.414.184-.918.277-1.47.322a3.3 3.3 0 0 1-1.65-.322 3.3 3.3 0 0 1-1.147-.874c-.276-.368-.414-.736-.414-1.197 0-.413.185-.827.597-1.195.367-.367.78-.644 1.24-.782.457-.139.871-.23 1.284-.23s1.055 0 1.376.093c.32.091.551.138.734.184v-8.142c0-.277.091-.505.23-.689.137-.184.366-.276.595-.322q.345-.069.552.138c.138.092 0 .277.182.46q.207.275.552.69c.23.276.504.506.87.735.322.23.598.368.827.507.23.09.46.184.643.276.183.091.413.183.596.321s.414.322.642.599c.23.277.366.505.413.781 0 .277 0 .552-.047.782z'\n    />\n    <Path fill='url(#prefix__b)' d='M26.176 8.708H20.76L26.8 14.75V9.333z' />\n    <Path fill='#F9C499' d='M22.267 9.333h4.534l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__b'\n        x1={22.114}\n        x2={28.156}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bar-chart-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.904 19.5a.88.88 0 0 1-.644-.26.87.87 0 0 1-.26-.644V14.5q0-.384.26-.644a.88.88 0 0 1 .644-.26h1.692q.383 0 .644.26.26.26.26.644v4.096q0 .383-.26.644a.87.87 0 0 1-.644.26zm-5.75 0a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V5.404q0-.383.26-.644a.87.87 0 0 1 .644-.26h1.692q.384 0 .644.26t.26.644v13.192q0 .383-.26.644a.88.88 0 0 1-.644.26zm-5.75 0a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644v-8.284q0-.39.26-.65a.88.88 0 0 1 .644-.258h1.692q.383 0 .644.26.26.26.26.644v8.284a.88.88 0 0 1-.26.65.88.88 0 0 1-.644.258z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bar-chart.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.904 19.5a.88.88 0 0 1-.644-.26.87.87 0 0 1-.26-.644V14.5q0-.384.26-.644a.88.88 0 0 1 .644-.26h1.692q.383 0 .644.26.26.26.26.644v4.096q0 .383-.26.644a.87.87 0 0 1-.644.26zm-5.75 0a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V5.404q0-.383.26-.644a.87.87 0 0 1 .644-.26h1.692q.384 0 .644.26t.26.644v13.192q0 .383-.26.644a.88.88 0 0 1-.644.26zm-5.75 0a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644v-8.284q0-.39.26-.65a.88.88 0 0 1 .644-.258h1.692q.383 0 .644.26.26.26.26.644v8.284a.88.88 0 0 1-.26.65.88.88 0 0 1-.644.258z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/base-icon.tsx",
    "content": "import Svg, { G, Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill=\"none\"\n    viewBox=\"0 0 60 61\"\n  >\n    <G fill=\"#DCDCDC\">\n      <Path d=\"M10 45.167V15.833a5.333 5.333 0 0 1 5.333-5.333h29.334A5.333 5.333 0 0 1 50 15.833v16h-8a9.333 9.333 0 0 0-9.333 9.334V50.5H15.333A5.333 5.333 0 0 1 10 45.167\" />\n      <Path d=\"M49.924 34.5H42a6.667 6.667 0 0 0-6.667 6.667V50.5h.105z\" />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bedtime-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12.1 21.5a9.3 9.3 0 0 1-3.74-.757 9.7 9.7 0 0 1-3.046-2.056 9.7 9.7 0 0 1-2.057-3.047A9.3 9.3 0 0 1 2.5 11.9q0-2.883 1.56-5.266t4.219-3.482a.91.91 0 0 1 .895.07.87.87 0 0 1 .426.769q.03 2.085.815 3.977a10.3 10.3 0 0 0 2.254 3.363q1.47 1.47 3.353 2.25 1.882.78 3.968.81.554.003.814.422.26.42.06.908-1.13 2.645-3.506 4.212T12.1 21.5'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bedtime.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12 20.954q-3.738 0-6.347-2.608Q3.045 15.737 3.045 12q0-3.337 2.054-5.763 2.055-2.425 5.296-3.016.476-.092.798.004t.494.352q.162.248.137.621-.025.372-.242.852-.253.58-.387 1.19a6 6 0 0 0-.133 1.26q0 2.265 1.586 3.851 1.585 1.586 3.851 1.586.645 0 1.25-.127a6.6 6.6 0 0 0 1.167-.36q.52-.22.908-.22.387 0 .63.166a.86.86 0 0 1 .324.51q.084.334-.016.832-.596 3.083-3.01 5.15-2.415 2.066-5.753 2.066m.003-1.583q2.555 0 4.509-1.556t2.57-3.77a7.6 7.6 0 0 1-2.58.476q-2.924 0-4.974-2.05t-2.05-4.974q0-.564.111-1.21t.38-1.42Q7.642 5.6 6.136 7.574 4.629 9.55 4.628 12.003q0 3.065 2.154 5.216t5.221 2.152'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/block-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12.002 21.5a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749M12 20a7.8 7.8 0 0 0 2.71-.476 7.9 7.9 0 0 0 2.382-1.378L5.854 6.908A8.1 8.1 0 0 0 4.48 9.29 7.8 7.8 0 0 0 4 12q0 3.35 2.325 5.675T12 20m6.146-2.908a7.9 7.9 0 0 0 1.378-2.381A7.8 7.8 0 0 0 20 12q0-3.35-2.325-5.675T12 4q-1.41 0-2.717.471a7.6 7.6 0 0 0-2.375 1.383z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/block.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.002 21.833a9.6 9.6 0 0 1-3.834-.774 10 10 0 0 1-3.123-2.105 9.9 9.9 0 0 1-2.104-3.12 9.6 9.6 0 0 1-.773-3.833q0-2.043.775-3.835a10 10 0 0 1 2.104-3.122 9.9 9.9 0 0 1 3.12-2.104A9.6 9.6 0 0 1 12 2.166q2.043 0 3.835.775a10 10 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.105 3.12 9.6 9.6 0 0 1 .772 3.833q0 2.043-.774 3.835a10 10 0 0 1-2.104 3.122 9.9 9.9 0 0 1-3.12 2.105 9.6 9.6 0 0 1-3.834.772m0-1.583q1.469 0 2.816-.488a8 8 0 0 0 2.45-1.412L5.655 6.733a8.6 8.6 0 0 0-1.408 2.46A8 8 0 0 0 3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399m6.33-2.983a8.8 8.8 0 0 0 1.407-2.449A7.9 7.9 0 0 0 20.25 12q0-3.452-2.399-5.851t-5.85-2.399q-1.464 0-2.807.496a8.4 8.4 0 0 0-2.46 1.42z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bookmark-add-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17 7h-1.25a.73.73 0 0 1-.534-.216A.73.73 0 0 1 15 6.25q0-.32.216-.535a.73.73 0 0 1 .534-.215H17V4.25q0-.319.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534V5.5h1.25q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 19.75 7H18.5v1.25q0 .319-.216.534A.73.73 0 0 1 17.75 9a.73.73 0 0 1-.535-.216A.73.73 0 0 1 17 8.25zm-5 10.462-3.97 1.703q-.903.387-1.716-.145-.814-.532-.814-1.506V5.308q0-.746.531-1.277A1.74 1.74 0 0 1 7.308 3.5h5.346a.66.66 0 0 1 .61.357.78.78 0 0 1 .032.743 4 4 0 0 0-.228.79Q13 5.786 13 6.25q0 1.77 1.147 3.093T17 10.93q.184.03.33.038t.285.008a.93.93 0 0 1 .626.248.77.77 0 0 1 .259.59v5.7q0 .975-.814 1.506-.813.533-1.717.146z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bookmark-add.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m12.004 17.867-4.659 1.987q-.796.342-1.504-.125-.708-.468-.708-1.333V4.65q0-.64.471-1.112a1.52 1.52 0 0 1 1.112-.471h6.03q.322 0 .555.233.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229H6.716V18.4L12 16.147l5.283 2.253v-7.046q0-.323.232-.555a.77.77 0 0 1 .563-.232q.33 0 .56.232.228.232.228.555v7.042q0 .865-.708 1.332t-1.496.126zM12 4.65H6.716h6.817zm5.283 2.167h-1.37a.77.77 0 0 1-.565-.232.77.77 0 0 1-.232-.563q0-.33.232-.56a.77.77 0 0 1 .564-.229h1.37V3.854q0-.323.233-.555a.77.77 0 0 1 .563-.232q.33 0 .56.232.228.232.228.555v1.38h1.38q.322 0 .555.232a.76.76 0 0 1 .232.558q0 .334-.232.564a.76.76 0 0 1-.556.229h-1.379v1.37q0 .334-.232.565a.76.76 0 0 1-.558.231.76.76 0 0 1-.564-.231.77.77 0 0 1-.23-.564z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bookmark-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 17.462-3.97 1.703q-.903.387-1.716-.148-.814-.534-.814-1.503V5.308q0-.758.525-1.283T7.308 3.5h9.384q.758 0 1.283.525t.525 1.283v12.206q0 .969-.814 1.503-.813.535-1.717.148z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/bookmark.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m12.004 17.867-4.659 1.987q-.796.342-1.504-.125-.708-.468-.708-1.333V4.65q0-.64.471-1.112a1.52 1.52 0 0 1 1.112-.471h10.567q.64 0 1.112.471.471.471.471 1.112v13.746q0 .865-.708 1.332t-1.496.126zM12 16.147l5.283 2.253V4.65H6.716V18.4zM12 4.65H6.716h10.567z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/calendar-add-on-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17.25 18.75H15a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535A.73.73 0 0 1 15 17.25h2.25V15q0-.319.216-.534A.73.73 0 0 1 18 14.25q.32 0 .535.216a.73.73 0 0 1 .215.534v2.25H21q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215h-2.25V21q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534zm-11.942.75q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T5.308 4.5h1.384V3.154q0-.326.222-.548a.75.75 0 0 1 .549-.221q.327 0 .548.22.22.221.22.549V4.5h5.577V3.135q0-.318.216-.535a.73.73 0 0 1 .534-.215q.319 0 .534.215a.73.73 0 0 1 .216.535V4.5h1.384q.758 0 1.283.525t.525 1.283v5.096q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534v-1.096H5v7.384q0 .116.096.212a.3.3 0 0 0 .212.096h6.086q.319 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/calendar-add-on.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.38 18.954h-2.213a.76.76 0 0 1-.56-.228.77.77 0 0 1-.227-.562.77.77 0 0 1 .227-.564.76.76 0 0 1 .56-.23h2.213v-2.203q0-.334.227-.565a.76.76 0 0 1 .563-.231q.334 0 .564.231.229.23.229.565v2.204h2.204q.333 0 .565.232a.77.77 0 0 1 .23.566.75.75 0 0 1-.23.56.78.78 0 0 1-.565.225h-2.204v2.213a.75.75 0 0 1-.232.56.78.78 0 0 1-.567.227.76.76 0 0 1-.56-.227.77.77 0 0 1-.224-.56zm-12.646.88q-.64 0-1.112-.472a1.52 1.52 0 0 1-.472-1.112V5.383q0-.64.472-1.112A1.52 1.52 0 0 1 4.734 3.8h1.5v-.846q0-.337.24-.58a.8.8 0 0 1 .585-.24q.35 0 .596.24a.78.78 0 0 1 .245.58V3.8h6.2v-.846q0-.337.241-.58a.8.8 0 0 1 .584-.24q.351 0 .596.24a.78.78 0 0 1 .246.58V3.8h1.5q.64 0 1.112.471.471.472.471 1.112v6.138q0 .333-.232.564a.76.76 0 0 1-.558.232.76.76 0 0 1-.564-.232.77.77 0 0 1-.229-.564V9.867H4.734v8.383h6.846q.323 0 .555.233a.76.76 0 0 1 .232.558q0 .334-.232.563a.76.76 0 0 1-.555.23zm0-11.55h12.533v-2.9H4.734z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/calendar-today-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5.308 21.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T5.308 4.5h1.384V3.154q0-.329.22-.55a.75.75 0 0 1 .55-.22q.328 0 .549.22.22.221.22.55V4.5h7.577V3.135q0-.32.215-.535a.73.73 0 0 1 .535-.215q.319 0 .534.215a.73.73 0 0 1 .216.535V4.5h1.384q.758 0 1.283.525t.525 1.283v13.384q0 .758-.525 1.283t-1.283.525zm0-1.5h13.384a.3.3 0 0 0 .212-.096.3.3 0 0 0 .096-.212v-9.384H5v9.384q0 .116.096.212a.3.3 0 0 0 .212.096'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/calendar-today.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.732 21.833q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.383q0-.64.472-1.112A1.52 1.52 0 0 1 4.732 3.8h1.5v-.846q0-.337.24-.58a.8.8 0 0 1 .585-.24q.35 0 .596.24a.78.78 0 0 1 .245.58V3.8h8.2v-.846q0-.337.241-.58a.8.8 0 0 1 .584-.24q.351 0 .596.24a.78.78 0 0 1 .246.58V3.8h1.5q.64 0 1.112.471.471.472.471 1.112V20.25q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm0-1.583h14.533V9.867H4.732zm0-11.967h14.533v-2.9H4.732z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-end-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 8.25q2.864 0 5.649 1.149a14.15 14.15 0 0 1 4.876 3.332q.308.31.314.724a.95.95 0 0 1-.305.714l-1.905 1.856q-.305.294-.681.329a1 1 0 0 1-.696-.2l-2.516-1.912a1.3 1.3 0 0 1-.367-.417 1.1 1.1 0 0 1-.12-.517v-2.822a15 15 0 0 0-2.113-.552A12 12 0 0 0 12 9.75q-1.107 0-2.137.184-1.029.186-2.113.553v2.82q0 .289-.12.518-.118.229-.367.417l-2.515 1.912a1 1 0 0 1-.696.2 1.1 1.1 0 0 1-.681-.329l-1.906-1.856a.95.95 0 0 1-.305-.714 1 1 0 0 1 .315-.724 14 14 0 0 1 4.868-3.332Q9.136 8.25 12 8.25'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-end.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.517 16.3 1.2 14.001a.74.74 0 0 1-.234-.551.84.84 0 0 1 .233-.567 13.8 13.8 0 0 1 4.917-3.587A14.7 14.7 0 0 1 12 8.066q3.045 0 5.88 1.23 2.833 1.23 4.92 3.587.229.255.233.567a.74.74 0 0 1-.235.551L20.5 16.3a.86.86 0 0 1-.573.24.86.86 0 0 1-.595-.158L16.5 14.254a.8.8 0 0 1-.237-.275.8.8 0 0 1-.08-.358v-3.27a11.3 11.3 0 0 0-2.077-.534 13.4 13.4 0 0 0-2.09-.167q-1.05 0-2.11.167-1.059.165-2.073.533v3.267a.8.8 0 0 1-.077.352.74.74 0 0 1-.24.281l-2.852 2.137q-.286.21-.598.183a.84.84 0 0 1-.55-.27m2.65-5.3q-.887.442-1.715 1.063-.826.62-1.602 1.32l1.317 1.35 2-1.5zm11.6-.05v2.183l2.066 1.584 1.334-1.317a12 12 0 0 0-1.619-1.367 17 17 0 0 0-1.781-1.083'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-log-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.44 21.5q-2.827 0-5.68-1.314t-5.242-3.709-3.703-5.242T2.5 5.56A1.03 1.03 0 0 1 3.55 4.5h3.262q.378 0 .668.247.289.247.368.61L8.421 8.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M12.75 4a.73.73 0 0 1-.534-.216A.73.73 0 0 1 12 3.25q0-.32.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 20.75 4zm0 3.692a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.319.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215zm0 3.692a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.216h8q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .319-.216.534a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-log.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.75 21.833q-2.893 0-5.863-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.457.31-.768.31-.31.773-.31h3.567q.35 0 .598.235T7.749 5l.666 3.194q.043.335-.026.616a1 1 0 0 1-.26.477L5.704 11.75q.603 1.041 1.304 1.953.702.91 1.546 1.735a17 17 0 0 0 1.818 1.626A16 16 0 0 0 12.4 18.4l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M4.92 10.283l1.9-1.916-.537-2.617H3.77q.059 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521zm-.842-15.4a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.234.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm0 3.5a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h8q.324 0 .556.233a.76.76 0 0 1 .232.558.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-made-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17 8.054 6.083 18.973a.72.72 0 0 1-.522.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.217-.527L15.947 7H9.751a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.535a.73.73 0 0 1 .535-.215h7.846q.383 0 .644.26.26.26.26.644v7.846q0 .319-.216.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-made.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.245 7.854 5.83 19.284a.73.73 0 0 1-.554.235.8.8 0 0 1-.559-.244.75.75 0 0 1 .002-1.106L16.133 6.75H9.766a.75.75 0 0 1-.56-.234.78.78 0 0 1-.227-.56q0-.327.227-.558a.76.76 0 0 1 .56-.231h8.267q.333 0 .565.232a.76.76 0 0 1 .23.555v8.267q0 .333-.231.565a.77.77 0 0 1-.567.23.75.75 0 0 1-.56-.23.78.78 0 0 1-.225-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-missed-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5 10.021v4.181a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V8.356q0-.387.259-.645a.88.88 0 0 1 .645-.259h5.846q.32 0 .534.215a.73.73 0 0 1 .216.535.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215H6.054l5.773 5.773a.3.3 0 0 0 .221.087.3.3 0 0 0 .221-.087l6.66-6.66a.7.7 0 0 1 .522-.22q.3.003.532.236.217.232.225.527a.7.7 0 0 1-.225.527l-6.654 6.654q-.27.27-.608.401a1.9 1.9 0 0 1-.673.131q-.337 0-.673-.13a1.75 1.75 0 0 1-.608-.402z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-missed-outgoing-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m19 10.021-5.767 5.768q-.272.27-.608.401a1.8 1.8 0 0 1-.673.131q-.336 0-.673-.13a1.75 1.75 0 0 1-.608-.402L4.017 9.135a.72.72 0 0 1-.212-.515.75.75 0 0 1 .212-.54.74.74 0 0 1 .535-.232q.302 0 .535.233l6.644 6.644a.3.3 0 0 0 .221.087q.134 0 .221-.087l5.773-5.773H13.75a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.32.216-.535a.73.73 0 0 1 .534-.215h5.846q.387 0 .645.259a.88.88 0 0 1 .259.645v5.846a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-missed-outgoing.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m19.25 9.942-6.224 6.224a1.5 1.5 0 0 1-.52.345q-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.522-.346L3.702 9.06a.78.78 0 0 1-.237-.552.78.78 0 0 1 .222-.565.82.82 0 0 1 1.151.003l7.08 7.087L18.2 8.75H13.93a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.562q0-.33.231-.56a.77.77 0 0 1 .565-.23h6.116q.324 0 .556.233.231.232.232.555v6.234q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.23-.565z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-missed.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.751 9.942v4.246q0 .333-.233.564a.76.76 0 0 1-.557.231.76.76 0 0 1-.564-.23.77.77 0 0 1-.229-.565V7.954a.76.76 0 0 1 .231-.555.77.77 0 0 1 .565-.232h6.117q.323 0 .555.233.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.555.23H5.8l6.284 6.282 7.11-7.11a.72.72 0 0 1 .557-.227.86.86 0 0 1 .568.25.8.8 0 0 1 .219.562.77.77 0 0 1-.238.55l-7.107 7.107q-.238.24-.52.346-.28.105-.587.106-.306 0-.59-.106a1.5 1.5 0 0 1-.521-.345z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-received-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.404 18.5a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V9.75q0-.318.216-.534A.73.73 0 0 1 6.25 9q.32 0 .535.216A.73.73 0 0 1 7 9.75v6.196L17.92 5.027a.72.72 0 0 1 .521-.212.7.7 0 0 1 .532.212q.217.217.217.527a.72.72 0 0 1-.217.527L8.053 17h6.197q.319 0 .534.215a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call-received.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.967 18.833a.76.76 0 0 1-.56-.231.78.78 0 0 1-.227-.565V9.771q0-.325.227-.557a.76.76 0 0 1 .563-.23q.334 0 .564.23.23.232.229.557v6.366L18.18 4.717a.7.7 0 0 1 .545-.236.8.8 0 0 1 .559.24.76.76 0 0 1 .241.554q0 .312-.241.546L7.867 17.25h6.367q.333 0 .564.232a.76.76 0 0 1 .232.558q0 .334-.232.564a.77.77 0 0 1-.564.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.751 20.833q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/cancel-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 13.054 3.073 3.073q.208.208.522.213a.7.7 0 0 0 .532-.213.72.72 0 0 0 .217-.527.72.72 0 0 0-.217-.527L13.054 12l3.073-3.073a.73.73 0 0 0 .213-.522.7.7 0 0 0-.213-.532.72.72 0 0 0-.527-.217.72.72 0 0 0-.527.217L12 10.946 8.927 7.873a.73.73 0 0 0-.522-.213.7.7 0 0 0-.532.213.72.72 0 0 0-.217.527q0 .31.217.527L10.946 12l-3.073 3.073a.73.73 0 0 0-.213.522.7.7 0 0 0 .213.532q.217.217.527.217a.72.72 0 0 0 .527-.217zm.002 8.446a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/cancel.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m12 13.1 3.116 3.117a.75.75 0 0 0 .55.229.75.75 0 0 0 .55-.23.75.75 0 0 0 .229-.55.75.75 0 0 0-.229-.55L13.099 12l3.117-3.117a.75.75 0 0 0 .229-.55.75.75 0 0 0-.229-.55.75.75 0 0 0-.55-.229.75.75 0 0 0-.55.23L11.999 10.9 8.883 7.783a.75.75 0 0 0-.55-.229.75.75 0 0 0-.55.23.75.75 0 0 0-.23.55.75.75 0 0 0 .23.55L10.899 12l-3.116 3.117a.75.75 0 0 0-.23.55.75.75 0 0 0 .23.55.75.75 0 0 0 .55.229.75.75 0 0 0 .55-.23zm0 8.733a9.5 9.5 0 0 1-3.819-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.128 9.5 9.5 0 0 1-.777-3.814q0-2.04.777-3.836a9.9 9.9 0 0 1 2.113-3.124 10 10 0 0 1 3.128-2.102 9.5 9.5 0 0 1 3.814-.772q2.041 0 3.836.775a9.9 9.9 0 0 1 3.125 2.104 9.9 9.9 0 0 1 2.1 3.122 9.6 9.6 0 0 1 .773 3.831q0 2.024-.773 3.82a9.95 9.95 0 0 1-2.104 3.127 10 10 0 0 1-3.123 2.11 9.5 9.5 0 0 1-3.832.777M12 20.25q3.438 0 5.844-2.414t2.406-5.835q0-3.438-2.405-5.845Q15.437 3.75 12 3.75q-3.421 0-5.836 2.406T3.749 12q0 3.42 2.414 5.836Q8.578 20.25 12 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/change-circle-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.45 15.629a5.4 5.4 0 0 1-1.647-.223 3.24 3.24 0 0 1-1.384-.825 3.7 3.7 0 0 1-.796-1.178 3.55 3.55 0 0 1-.277-1.388q0-.288.035-.555a2.5 2.5 0 0 1 .128-.53.68.68 0 0 0-.012-.479.568.568 0 0 0-.773-.329.55.55 0 0 0-.31.343q-.126.375-.193.755a4.719 4.719 0 0 0 .306 2.634q.373.885 1.054 1.565a4.16 4.16 0 0 0 1.717 1.075 6.8 6.8 0 0 0 2.037.343l-.695.694a.59.59 0 0 0-.181.414q-.005.238.181.424a.58.58 0 0 0 .42.187.58.58 0 0 0 .419-.187l1.561-1.561a.87.87 0 0 0 .272-.633.87.87 0 0 0-.272-.633l-1.561-1.561a.59.59 0 0 0-.414-.182.57.57 0 0 0-.425.182.58.58 0 0 0-.186.419.58.58 0 0 0 .186.42zm-.906-7.273q.85 0 1.655.238.805.24 1.392.825.519.52.796 1.178.277.66.277 1.388 0 .288-.035.555a2.5 2.5 0 0 1-.129.53.7.7 0 0 0 .012.487.58.58 0 0 0 .311.341.58.58 0 0 0 .462 0 .53.53 0 0 0 .311-.338 5 5 0 0 0 .192-.768q.068-.391.068-.792 0-.972-.358-1.855a4.7 4.7 0 0 0-1.054-1.58 4.2 4.2 0 0 0-1.733-1.067 6.6 6.6 0 0 0-2.036-.32l.71-.709a.61.61 0 0 0 .166-.414.57.57 0 0 0-.182-.424.58.58 0 0 0-.419-.187.58.58 0 0 0-.42.187L9.97 7.192a.87.87 0 0 0-.272.633q0 .362.271.633l1.562 1.561a.59.59 0 0 0 .414.182q.238.005.424-.182a.58.58 0 0 0 .187-.419.58.58 0 0 0-.187-.42zM12 21.5a9.2 9.2 0 0 1-3.703-.749 9.6 9.6 0 0 1-3.016-2.032 9.6 9.6 0 0 1-2.032-3.016A9.2 9.2 0 0 1 2.5 12q0-1.97.749-3.703a9.6 9.6 0 0 1 2.032-3.016 9.6 9.6 0 0 1 3.016-2.032A9.2 9.2 0 0 1 12 2.5q1.97 0 3.703.749a9.6 9.6 0 0 1 3.016 2.032 9.6 9.6 0 0 1 2.032 3.016q.75 1.734.749 3.703 0 1.97-.749 3.703a9.6 9.6 0 0 1-2.032 3.016 9.6 9.6 0 0 1-3.016 2.032q-1.734.75-3.703.749'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/change-circle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.45 15.629a5.4 5.4 0 0 1-1.647-.223 3.24 3.24 0 0 1-1.384-.825 3.7 3.7 0 0 1-.796-1.178 3.55 3.55 0 0 1-.277-1.388q0-.288.035-.555a2.5 2.5 0 0 1 .128-.53.68.68 0 0 0-.012-.479.568.568 0 0 0-.773-.329.55.55 0 0 0-.31.343q-.126.375-.193.755a4.719 4.719 0 0 0 .306 2.634q.373.885 1.054 1.565a4.16 4.16 0 0 0 1.717 1.075 6.8 6.8 0 0 0 2.037.343l-.695.694a.59.59 0 0 0-.181.414q-.005.238.181.424a.58.58 0 0 0 .42.187.58.58 0 0 0 .419-.187l1.561-1.561a.87.87 0 0 0 .272-.633.87.87 0 0 0-.272-.633l-1.561-1.561a.59.59 0 0 0-.414-.182.57.57 0 0 0-.425.182.58.58 0 0 0-.186.419.58.58 0 0 0 .186.42zm-.906-7.273q.85 0 1.655.238.805.24 1.392.825.519.52.796 1.178.277.66.277 1.388 0 .288-.035.555a2.5 2.5 0 0 1-.129.53.7.7 0 0 0 .012.487.58.58 0 0 0 .311.341.58.58 0 0 0 .462 0 .53.53 0 0 0 .311-.338 5 5 0 0 0 .192-.768q.068-.391.068-.792 0-.972-.358-1.855a4.7 4.7 0 0 0-1.054-1.58 4.2 4.2 0 0 0-1.733-1.067 6.6 6.6 0 0 0-2.036-.32l.71-.709a.61.61 0 0 0 .166-.414.57.57 0 0 0-.182-.424.58.58 0 0 0-.419-.187.58.58 0 0 0-.42.187L9.97 7.192a.87.87 0 0 0-.272.633q0 .362.271.633l1.562 1.561a.59.59 0 0 0 .414.182q.238.005.424-.182a.58.58 0 0 0 .187-.419.58.58 0 0 0-.187-.42zM12 21.5a9.2 9.2 0 0 1-3.703-.749 9.6 9.6 0 0 1-3.016-2.032 9.6 9.6 0 0 1-2.032-3.016A9.2 9.2 0 0 1 2.5 12q0-1.97.749-3.703a9.6 9.6 0 0 1 2.032-3.016 9.6 9.6 0 0 1 3.016-2.032A9.2 9.2 0 0 1 12 2.5q1.97 0 3.703.749a9.6 9.6 0 0 1 3.016 2.032 9.6 9.6 0 0 1 2.032 3.016q.75 1.734.749 3.703 0 1.97-.749 3.703a9.6 9.6 0 0 1-2.032 3.016 9.6 9.6 0 0 1-3.016 2.032q-1.734.75-3.703.749m0-1.5q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chat-bot-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      fillRule='evenodd'\n      d='M5.896 18.7 3.35 21.25q-.375.375-.863.175-.486-.2-.487-.73V4.584q0-.64.471-1.112A1.52 1.52 0 0 1 3.583 3h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm5.189-9.581a1.269 1.269 0 1 1-2.538 0 1.269 1.269 0 0 1 2.538 0m4.23 0a1.269 1.269 0 1 1-2.539 0 1.269 1.269 0 0 1 2.538 0m.719 3.387a4.702 4.702 0 0 1-8.112.039.634.634 0 0 1 1.091-.647 3.433 3.433 0 0 0 5.923-.028.634.634 0 1 1 1.098.636'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chat-bot.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.253 8.269a1.269 1.269 0 1 1-2.538 0 1.269 1.269 0 0 1 2.538 0M15.482 8.269a1.269 1.269 0 1 1-2.538 0 1.269 1.269 0 0 1 2.538 0M16.202 11.656a4.7 4.7 0 0 1-8.112.039.634.634 0 0 1 1.091-.647 3.433 3.433 0 0 0 5.923-.028.635.635 0 0 1 1.098.636'\n    />\n    <Path\n      fill={color}\n      fillRule='evenodd'\n      d='m3.518 20.4 2.546-2.55h14.187a1.52 1.52 0 0 0 1.112-.471q.471-.472.471-1.112V3.733q0-.64-.47-1.112a1.52 1.52 0 0 0-1.113-.471h-16.5a1.52 1.52 0 0 0-1.112.471 1.52 1.52 0 0 0-.471 1.112v16.113q0 .528.487.729.488.2.863-.175m16.733-4.133H5.397l-1.646 1.741V3.733h16.5z'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chat-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.039 17.5-2.002 2.002q-.427.426-.982.192-.555-.235-.555-.84V4.308q0-.758.525-1.283T4.308 2.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zM7 13.75h6q.319 0 .534-.216A.73.73 0 0 0 13.75 13a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 13.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 10a.73.73 0 0 0-.216-.534A.73.73 0 0 0 17 9.25H7a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 7 10.75m0-3h10q.318 0 .534-.216A.73.73 0 0 0 17.75 7a.73.73 0 0 0-.216-.535A.73.73 0 0 0 17 6.25H7a.73.73 0 0 0-.534.216A.73.73 0 0 0 6.25 7q0 .32.216.535A.73.73 0 0 0 7 7.75'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chat.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.062 17.85 3.516 20.4q-.375.375-.862.175-.488-.2-.488-.73V3.734q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 2.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm-.667-1.583h14.854V3.733H3.75v14.275zm1.5-2.334h6.15a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.229h-6.15a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.557q0 .335.232.564a.77.77 0 0 0 .564.23m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.23H6.895a.77.77 0 0 0-.564.234.76.76 0 0 0-.232.557q0 .335.232.564.231.23.564.229m0-3.133h10.217a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.563.76.76 0 0 0-.232-.56.76.76 0 0 0-.555-.229H6.895a.77.77 0 0 0-.564.233.76.76 0 0 0-.232.558q0 .334.232.564.231.229.564.229'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/check-circle-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.58 14.146-2.322-2.323a.73.73 0 0 0-.522-.213.7.7 0 0 0-.532.213.72.72 0 0 0-.217.527q0 .31.217.527l2.744 2.744q.27.27.633.271.36 0 .633-.27l5.563-5.564a.73.73 0 0 0 .212-.522.7.7 0 0 0-.212-.532.72.72 0 0 0-.527-.217.72.72 0 0 0-.527.217zm1.422 7.354a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/check-circle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.58 14.146-2.322-2.323a.73.73 0 0 0-.522-.213.7.7 0 0 0-.532.213.72.72 0 0 0-.217.527q0 .31.217.527l2.744 2.744q.27.27.633.271.36 0 .633-.27l5.563-5.564a.73.73 0 0 0 .212-.522.7.7 0 0 0-.212-.532.72.72 0 0 0-.527-.217.72.72 0 0 0-.527.217zm1.422 7.354a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749M12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/check-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m9.551 15.516 8.639-8.639a.73.73 0 0 1 .522-.228q.299-.005.532.228a.74.74 0 0 1 .232.535q0 .302-.232.534l-9.06 9.075a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.27l-4.175-4.176a.71.71 0 0 1-.22-.53.75.75 0 0 1 .236-.539.74.74 0 0 1 .534-.233q.303 0 .535.233z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/check.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m9.483 15.517 8.875-8.875a.82.82 0 0 1 1.184 0q.25.252.25.596 0 .345-.25.595l-9.475 9.484a.8.8 0 0 1-.584.25.8.8 0 0 1-.583-.25L4.433 12.85a.77.77 0 0 1-.237-.596.86.86 0 0 1 .263-.596.82.82 0 0 1 .596-.25q.345 0 .595.25z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chevron-left-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m10.453 12 4.074 4.073q.207.209.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217l-4.495-4.494A.83.83 0 0 1 8.723 12q0-.18.057-.336a.8.8 0 0 1 .198-.297l4.495-4.494a.73.73 0 0 1 .522-.213.7.7 0 0 1 .532.213q.217.217.217.527a.72.72 0 0 1-.217.527z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chevron-left.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.333 11.983 4.234 4.234a.71.71 0 0 1 .22.543.82.82 0 0 1-.237.557.75.75 0 0 1-.558.243.75.75 0 0 1-.563-.24l-4.783-4.783a.75.75 0 0 1-.185-.256.8.8 0 0 1-.057-.298q0-.162.057-.298a.8.8 0 0 1 .185-.264l4.8-4.8a.76.76 0 0 1 .562-.232q.33.003.559.244.228.242.229.565 0 .323-.23.552z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chevron-right-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12.947 12 8.874 7.927a.73.73 0 0 1-.213-.522.7.7 0 0 1 .213-.532.72.72 0 0 1 .526-.217q.31 0 .527.217l4.495 4.494a.83.83 0 0 1 .255.633.833.833 0 0 1-.256.633l-4.493 4.494a.73.73 0 0 1-.523.213.7.7 0 0 1-.531-.213.72.72 0 0 1-.218-.527q0-.31.217-.527z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/chevron-right.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M13.066 11.983 8.833 7.751a.72.72 0 0 1-.221-.545.82.82 0 0 1 .237-.556.75.75 0 0 1 .558-.244q.33-.002.571.232l4.784 4.783q.12.129.18.264.061.135.061.298t-.06.299a.9.9 0 0 1-.183.258l-4.797 4.796a.74.74 0 0 1-.564.233.77.77 0 0 1-.55-.252.8.8 0 0 1-.23-.565q0-.323.23-.552z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/close-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 13.054-5.072 5.073a.73.73 0 0 1-.523.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.218-.527L10.947 12 5.874 6.927a.73.73 0 0 1-.213-.522.7.7 0 0 1 .213-.532.72.72 0 0 1 .527-.217q.308 0 .527.217L12 10.946l5.073-5.073a.73.73 0 0 1 .522-.212.7.7 0 0 1 .532.212q.216.217.217.527a.72.72 0 0 1-.217.527L13.054 12l5.074 5.073q.207.209.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/close.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 13.117 6.895 18.22a.763.763 0 0 1-1.117-.004.766.766 0 0 1 .004-1.1l5.1-5.117-5.1-5.117a.74.74 0 0 1-.229-.544q0-.315.23-.556a.74.74 0 0 1 .55-.244.78.78 0 0 1 .562.232l5.104 5.112 5.112-5.112a.75.75 0 0 1 .555-.232.78.78 0 0 1 .562.244.76.76 0 0 1 .223.556.77.77 0 0 1-.235.544L13.116 12l5.1 5.117a.74.74 0 0 1 .229.543q0 .314-.23.557a.74.74 0 0 1-.55.243.74.74 0 0 1-.553-.24z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/code-blocks.tsx",
    "content": "import Svg, { G, Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G transform='translate(3.5, 3.5)'>\n      <Path\n        fill={color}\n        d='M4.96925 8.5L6.61725 6.852C6.76592 6.70317 6.84192 6.52908 6.84525 6.32975C6.84842 6.13042 6.77242 5.95317 6.61725 5.798C6.46208 5.643 6.28392 5.5655 6.08275 5.5655C5.88142 5.5655 5.70317 5.643 5.548 5.798L3.47875 7.86725C3.38525 7.96092 3.31925 8.05967 3.28075 8.1635C3.24225 8.26733 3.223 8.3795 3.223 8.5C3.223 8.6205 3.24225 8.73267 3.28075 8.8365C3.31925 8.94033 3.38525 9.03908 3.47875 9.13275L5.55775 11.2115C5.70642 11.3602 5.883 11.4362 6.0875 11.4395C6.292 11.4427 6.47183 11.3667 6.627 11.2115C6.782 11.0563 6.8595 10.8807 6.8595 10.6845C6.8595 10.4885 6.782 10.3129 6.627 10.1578L4.96925 8.5ZM12.0307 8.5L10.373 10.1578C10.2243 10.3064 10.1484 10.4804 10.1453 10.6798C10.1421 10.8791 10.218 11.0563 10.373 11.2115C10.5282 11.3667 10.7064 11.4443 10.9078 11.4443C11.1089 11.4443 11.2871 11.3667 11.4423 11.2115L13.5212 9.13275C13.6147 9.03908 13.6808 8.94033 13.7193 8.8365C13.7578 8.73267 13.777 8.6205 13.777 8.5C13.777 8.3795 13.7578 8.26733 13.7193 8.1635C13.6808 8.05967 13.6147 7.96092 13.5212 7.86725L11.4423 5.7885C11.3679 5.71417 11.2843 5.65842 11.1913 5.62125C11.0984 5.58408 11.0055 5.5655 10.9125 5.5655C10.8195 5.5655 10.7249 5.58408 10.6288 5.62125C10.5328 5.65842 10.4475 5.71417 10.373 5.7885C10.218 5.94367 10.1405 6.11933 10.1405 6.3155C10.1405 6.5115 10.218 6.68708 10.373 6.84225L12.0307 8.5ZM1.80775 17C1.30258 17 0.875 16.825 0.525 16.475C0.175 16.125 0 15.6974 0 15.1923V1.80775C0 1.30258 0.175 0.875 0.525 0.525C0.875 0.175 1.30258 0 1.80775 0H15.1923C15.6974 0 16.125 0.175 16.475 0.525C16.825 0.875 17 1.30258 17 1.80775V15.1923C17 15.6974 16.825 16.125 16.475 16.475C16.125 16.825 15.6974 17 15.1923 17H1.80775ZM1.80775 15.5H15.1923C15.2692 15.5 15.3398 15.4679 15.4038 15.4038C15.4679 15.3398 15.5 15.2692 15.5 15.1923V1.80775C15.5 1.73075 15.4679 1.66025 15.4038 1.59625C15.3398 1.53208 15.2692 1.5 15.1923 1.5H1.80775C1.73075 1.5 1.66025 1.53208 1.59625 1.59625C1.53208 1.66025 1.5 1.73075 1.5 1.80775V15.1923C1.5 15.2692 1.53208 15.3398 1.59625 15.4038C1.66025 15.4679 1.73075 15.5 1.80775 15.5Z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/code.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M9.4 16.6 4.8 12l4.6-4.6L8 6l-6 6 6 6zm5.2 0 4.6-4.6-4.6-4.6L16 6l6 6-6 6z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/collaborative-document-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      fillRule='evenodd'\n      d='M2 4.147A3.147 3.147 0 0 1 5.147 1H15.85a3.147 3.147 0 0 1 3.147 3.147V9.54a.63.63 0 0 1-.179.44l-4.067 4.16-5.817 5.571a.63.63 0 0 1-.436.175h-3.35A3.15 3.15 0 0 1 2 16.737zm7.24-.195a.826.826 0 0 0 0 1.652h5.98a.826.826 0 0 0 0-1.652zM5.775 7.415a.826.826 0 1 0 0 1.652h9.443a.826.826 0 0 0 0-1.652zm0 3.461a.826.826 0 1 0 0 1.652h7.554a.826.826 0 1 0 0-1.652z'\n      clipRule='evenodd'\n    />\n    <Path\n      fill={color}\n      d='M21.128 11.334a1.26 1.26 0 0 0-1.78 0l-7.935 7.934a.33.33 0 0 0-.094.187l-.367 2.57a.33.33 0 0 0 .374.375l2.57-.367a.33.33 0 0 0 .188-.094l7.934-7.934a1.26 1.26 0 0 0 0-1.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/collaborative-document-icon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G fill={color ?? \"#6852D6\"}>\n      <Path d='M19.468 4h.772a1.92 1.92 0 0 1 1.86 1.441l-5.408 5.408a.24.24 0 0 1-.136.068l-1.98.283a.24.24 0 0 1-.272-.272l.283-1.98a.24.24 0 0 1 .068-.135z' />\n      <Path d='m17.347 4-3.753 3.752a1.74 1.74 0 0 0-.492.984l-.283 1.98a1.74 1.74 0 0 0 1.97 1.969l1.979-.283a1.74 1.74 0 0 0 .984-.492l4.408-4.408V17.92c0 1.06-.86 1.92-1.92 1.92H3.92A1.92 1.92 0 0 1 2 17.92v-3.89a.75.75 0 0 0 .403-.117l.183-.117c1-.637 3.135-1.996 3.746-2.345.102-.058.298-.127.542-.153.24-.026.464-.004.628.056.151.056.229.132.272.228.05.109.103.354-.02.83-.24.928-.487 1.997-.415 2.892.038.461.165.946.483 1.35.331.42.804.67 1.375.766 1.137.19 2.18-.482 2.91-1.116.768-.668 1.408-1.487 1.773-1.974a.75.75 0 1 0-1.2-.9c-.354.473-.915 1.184-1.557 1.741-.68.59-1.257.84-1.68.77-.27-.046-.382-.139-.443-.216-.072-.092-.142-.255-.165-.543-.05-.604.123-1.434.371-2.394.177-.684.17-1.304-.065-1.825-.242-.534-.674-.853-1.123-1.017a2.9 2.9 0 0 0-1.306-.14c-.402.044-.806.161-1.124.343-.604.345-2.53 1.569-3.588 2.242V5.92C2 4.86 2.86 4 3.92 4z' />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/collaborative-document.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill='#F9F8FD'\n      stroke={color}\n      strokeWidth={1.5}\n      d='M18.625 11.11c.086-.176.132-.37.132-.57V5.78a3.53 3.53 0 0 0-3.53-3.53H5.78A3.53 3.53 0 0 0 2.25 5.78v11.115c0 1.95 1.58 3.53 3.529 3.53h2.958c.336 0 .66-.13.903-.364l-.473-.493.473.493.835-.8-.313 2.198a1.042 1.042 0 0 0 1.178 1.178l2.27-.324c.223-.032.43-.135.59-.295l7.005-7.005a1.86 1.86 0 0 0 0-2.633l-.786-.786a1.86 1.86 0 0 0-1.794-.483Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/collaborative-whiteboard-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.468 4h.772a1.92 1.92 0 0 1 1.86 1.441l-5.408 5.408a.24.24 0 0 1-.136.068l-1.98.283a.24.24 0 0 1-.272-.272l.283-1.98a.24.24 0 0 1 .068-.135z'\n    />\n    <Path\n      fill={color}\n      d='m17.347 4-3.753 3.752a1.74 1.74 0 0 0-.492.984l-.283 1.98a1.74 1.74 0 0 0 1.97 1.969l1.979-.283a1.74 1.74 0 0 0 .984-.492l4.408-4.408V17.92c0 1.06-.86 1.92-1.92 1.92H3.92A1.92 1.92 0 0 1 2 17.92v-3.89a.75.75 0 0 0 .403-.117l.183-.117c1-.637 3.135-1.996 3.746-2.345.102-.058.298-.127.542-.153.24-.026.464-.004.628.056.151.056.229.132.272.228.05.109.103.354-.02.83-.24.928-.487 1.997-.415 2.892.038.461.165.946.483 1.35.331.42.804.67 1.375.766 1.137.19 2.18-.482 2.91-1.116.768-.668 1.408-1.487 1.773-1.974a.75.75 0 1 0-1.2-.9c-.354.473-.915 1.184-1.557 1.741-.68.59-1.257.84-1.68.77-.27-.046-.382-.139-.443-.216-.072-.092-.142-.255-.165-.543-.05-.604.123-1.434.371-2.394.177-.684.17-1.304-.065-1.825-.242-.534-.674-.853-1.123-1.017a2.9 2.9 0 0 0-1.306-.14c-.402.044-.806.161-1.124.343-.604.345-2.53 1.569-3.588 2.242V5.92C2 4.86 2.86 4 3.92 4z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/collaborative-whiteboard-icon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G fill={color ?? \"#6852D6\"}>\n      <Path\n        fillRule='evenodd'\n        d='M2 4.147A3.147 3.147 0 0 1 5.147 1H15.85a3.147 3.147 0 0 1 3.147 3.147V9.54a.63.63 0 0 1-.179.44l-4.067 4.16-5.817 5.571a.63.63 0 0 1-.436.175h-3.35A3.15 3.15 0 0 1 2 16.737zm7.24-.195a.826.826 0 0 0 0 1.652h5.98a.826.826 0 0 0 0-1.652zM5.775 7.415a.826.826 0 1 0 0 1.652h9.443a.826.826 0 0 0 0-1.652zm0 3.461a.826.826 0 1 0 0 1.652h7.554a.826.826 0 1 0 0-1.652z'\n        clipRule='evenodd'\n      />\n      <Path d='M21.128 11.334a1.26 1.26 0 0 0-1.78 0l-7.935 7.934a.33.33 0 0 0-.094.187l-.367 2.57a.33.33 0 0 0 .374.375l2.57-.367a.33.33 0 0 0 .188-.094l7.934-7.934a1.26 1.26 0 0 0 0-1.78z' />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/collaborative-whiteboard.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      stroke={color}\n      strokeWidth={1.339}\n      d='M18.596 4.33H4.714A2.384 2.384 0 0 0 2.33 6.714V17.43a2.384 2.384 0 0 0 2.384 2.384h14.572a2.384 2.384 0 0 0 2.384-2.384V6.51l.02-.02-.095-.37a2.385 2.385 0 0 0-2.31-1.79zM3 13.286l.36-.23 1.055-.67-1.232.783-.02.012-.163.105Zm13.591-1.697.473.473zm-5.125 3.992c-.601.495-1.277.85-1.93.74.362.06.724-.025 1.05-.172.29-.13.584-.324.88-.568Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/content-copy-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M9.058 17.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V4.308q0-.758.525-1.283T9.058 2.5h8.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zm-3.5 3.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V7.058q0-.32.216-.535a.73.73 0 0 1 .534-.215q.32 0 .535.215a.73.73 0 0 1 .215.535v12.134q0 .116.096.212a.3.3 0 0 0 .212.096h9.134q.32 0 .535.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.535.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/content-copy.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M7.918 18.667a1.52 1.52 0 0 1-1.112-.472 1.52 1.52 0 0 1-.472-1.112V3.75q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h10.333q.64 0 1.112.471.471.471.471 1.112v13.333q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.472zm0-1.584h10.333V3.75H7.918zm-3.167 4.75a1.52 1.52 0 0 1-1.112-.471 1.52 1.52 0 0 1-.471-1.112V6.12q0-.323.232-.555a.77.77 0 0 1 .562-.232q.33 0 .56.232t.23.556V20.25H15.88q.324 0 .556.233a.76.76 0 0 1 .232.558q0 .334-.232.563a.76.76 0 0 1-.556.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/crop-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M17.25 22v-3.25H7.058q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.75H2a.73.73 0 0 1-.534-.216A.73.73 0 0 1 1.25 6q0-.32.216-.535A.73.73 0 0 1 2 5.25h3.25V2q0-.318.216-.534A.73.73 0 0 1 6 1.25q.32 0 .535.216A.73.73 0 0 1 6.75 2v14.942q0 .116.096.212a.3.3 0 0 0 .212.096H22q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215h-3.25V22q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534m0-6.25V7.058a.3.3 0 0 0-.096-.212.3.3 0 0 0-.212-.096H8.25v-1.5h8.692q.758 0 1.283.525t.525 1.283v8.692z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/crop.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.234 22.054V18.8H6.78a1.52 1.52 0 0 1-1.112-.471 1.52 1.52 0 0 1-.472-1.112V6.754H1.934a.76.76 0 0 1-.56-.228.77.77 0 0 1-.228-.562.77.77 0 0 1 .228-.564.76.76 0 0 1 .56-.23h3.262V1.92q0-.322.228-.555a.76.76 0 0 1 .563-.232q.334 0 .564.232t.229.556v15.296h15.287q.334 0 .565.233.231.232.231.557a.76.76 0 0 1-.231.564.77.77 0 0 1-.565.23h-3.25v3.253q0 .333-.232.565a.76.76 0 0 1-.558.231.76.76 0 0 1-.564-.231.77.77 0 0 1-.229-.565m0-6.42v-8.88h-8.87V5.171h8.87q.64 0 1.112.476.471.475.471 1.107v8.88z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/delete-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M7.308 20.5a1.74 1.74 0 0 1-1.277-.531 1.74 1.74 0 0 1-.531-1.277V6h-.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535A.73.73 0 0 1 5.25 4.5H9q0-.368.259-.626a.85.85 0 0 1 .625-.259h4.232q.367 0 .625.259A.85.85 0 0 1 15 4.5h3.75q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.534A.73.73 0 0 1 18.75 6h-.25v12.692q0 .746-.531 1.277a1.74 1.74 0 0 1-1.277.531zm2.846-3.5q.319 0 .534-.215a.73.73 0 0 0 .216-.535v-7.5a.73.73 0 0 0-.216-.535.73.73 0 0 0-.535-.215.73.73 0 0 0-.534.215.73.73 0 0 0-.215.535v7.5q0 .318.216.535a.73.73 0 0 0 .534.215m3.693 0q.318 0 .534-.215a.73.73 0 0 0 .215-.535v-7.5a.73.73 0 0 0-.216-.535.73.73 0 0 0-.534-.215.73.73 0 0 0-.534.215.73.73 0 0 0-.216.535v7.5q0 .318.216.535a.73.73 0 0 0 .535.215'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/delete.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.734 20.833q-.655 0-1.12-.464a1.53 1.53 0 0 1-.464-1.119V5.55h-.204a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.562q0-.33.232-.56a.77.77 0 0 1 .564-.23h3.89v-.012a.76.76 0 0 1 .23-.558.77.77 0 0 1 .564-.23h4.766q.324 0 .556.232.231.232.232.556v.013h3.9q.32 0 .552.232a.76.76 0 0 1 .231.558.76.76 0 0 1-.232.564.76.76 0 0 1-.555.229h-.213v13.7q0 .655-.464 1.119-.465.464-1.12.464zM17.284 5.55H6.734v13.7h10.55zm-7.29 11.62a.77.77 0 0 0 .559-.226.76.76 0 0 0 .23-.56V8.4a.77.77 0 0 0-.232-.565.76.76 0 0 0-.558-.23.76.76 0 0 0-.563.23.77.77 0 0 0-.23.565v7.983q0 .335.234.56a.78.78 0 0 0 .56.228m4.034 0a.77.77 0 0 0 .558-.226.76.76 0 0 0 .231-.56V8.4a.77.77 0 0 0-.233-.565.76.76 0 0 0-.557-.23.76.76 0 0 0-.564.23.77.77 0 0 0-.23.565v7.983q0 .335.235.56a.78.78 0 0 0 .56.228'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/description-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M9 17.75h6q.319 0 .534-.216A.73.73 0 0 0 15.75 17a.73.73 0 0 0-.216-.535.73.73 0 0 0-.534-.215H9a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.535A.73.73 0 0 0 9 17.75m0-4h6q.319 0 .534-.216A.73.73 0 0 0 15.75 13a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216H9a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534A.73.73 0 0 0 9 13.75M6.308 21.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V4.308q0-.758.525-1.283T6.308 2.5h7.194q.366 0 .697.14a1.8 1.8 0 0 1 .578.387l4.196 4.196a1.8 1.8 0 0 1 .527 1.275v11.194q0 .758-.525 1.283t-1.283.525zM13.5 7.6q0 .383.259.641a.87.87 0 0 0 .641.259H18L13.5 4z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/description.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M8.85 17.7h6.313a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.562.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.23H8.85a.75.75 0 0 0-.56.235.78.78 0 0 0-.227.56q0 .327.227.558a.76.76 0 0 0 .56.231m0-4.133h6.313a.76.76 0 0 0 .556-.232.77.77 0 0 0 .231-.563.76.76 0 0 0-.231-.56.76.76 0 0 0-.556-.229H8.85a.75.75 0 0 0-.56.234.78.78 0 0 0-.227.561q0 .327.227.558a.76.76 0 0 0 .56.23m-3.116 8.266q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V3.75q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h7.958q.323 0 .616.125.293.124.516.34l4.548 4.545q.225.223.351.517.127.294.127.619V20.25q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm7.833-14.229V3.75H5.734v16.5h12.533V8.4h-3.904a.77.77 0 0 1-.565-.231.77.77 0 0 1-.23-.565'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/document-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#4876F9'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path\n        fill='#fff'\n        d='m13.978 22.666 2.008-7.36h.037l1.996 7.36h1.121l2.525-8.833h-1.133l-1.934 7.41h-.05l-1.995-7.41h-1.096l-2.008 7.41h-.05l-1.934-7.41h-1.133l2.525 8.833z'\n      />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#B5C8FC' d='M22.266 9.333h4.533l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/documents.tsx",
    "content": "import * as React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nconst DocumentIcon = (props: any) => (\n  <Svg\n    width={16}\n    height={16}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    {...props}\n  >\n    <Path\n      d=\"M12.6667 3.33333V12.6667H3.33333V3.33333H12.6667ZM12.6667 2H3.33333C2.6 2 2 2.6 2 3.33333V12.6667C2 13.4 2.6 14 3.33333 14H12.6667C13.4 14 14 13.4 14 12.6667V3.33333C14 2.6 13.4 2 12.6667 2Z\"\n      fill=\"#A1A1A1\"\n    />\n    <Path\n      d=\"M9.33333 11.3333H4.66667V10H9.33333V11.3333ZM11.3333 8.66667H4.66667V7.33333H11.3333V8.66667ZM11.3333 6H4.66667V4.66667H11.3333V6Z\"\n      fill=\"#A1A1A1\"\n    />\n  </Svg>\n);\n\nexport default DocumentIcon;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/done-all-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M1.923 12.877a.7.7 0 0 1-.22-.522q.002-.3.236-.532a.78.78 0 0 1 .527-.225.7.7 0 0 1 .526.225l3.724 3.723.25-.25q.146.07.306.172.16.103.317.259.232.232.225.527a.76.76 0 0 1-.24.527l-.241.24a.92.92 0 0 1-.633.264.84.84 0 0 1-.633-.264zm10.427 2.654 8.673-8.673a.7.7 0 0 1 .522-.22q.3.002.532.235.217.232.225.527a.7.7 0 0 1-.225.527l-9.094 9.094a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.27l-4.144-4.145a.72.72 0 0 1-.212-.514.75.75 0 0 1 .212-.54.74.74 0 0 1 .535-.232q.302 0 .534.232zm4.062-7.589-3.881 3.881a.72.72 0 0 1-.515.213.75.75 0 0 1-.539-.213.73.73 0 0 1-.233-.534q0-.303.233-.535l3.88-3.881a.72.72 0 0 1 .515-.213.75.75 0 0 1 .54.213.74.74 0 0 1 .232.535q0 .301-.232.534'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/done-all.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2.101 12.933a.77.77 0 0 1-.237-.591.86.86 0 0 1 .262-.592.84.84 0 0 1 .592-.246.8.8 0 0 1 .592.246l3.833 3.85.3.3.3.3a.78.78 0 0 1 .246.583.82.82 0 0 1-.254.584l-.017.016a.83.83 0 0 1-.583.246.79.79 0 0 1-.584-.246zm9.75 2.659 8.884-8.884a.8.8 0 0 1 .591-.246.82.82 0 0 1 .592.255.84.84 0 0 1 .246.591.8.8 0 0 1-.246.592l-9.483 9.483a.8.8 0 0 1-.584.25.8.8 0 0 1-.583-.25l-4.45-4.45a.77.77 0 0 1-.233-.587.86.86 0 0 1 .25-.596.81.81 0 0 1 .596-.25q.346 0 .595.25zm5.342-7.684-4.758 4.759a.76.76 0 0 1-.58.233.85.85 0 0 1-.587-.25.81.81 0 0 1-.25-.596q0-.346.25-.596l4.742-4.741a.8.8 0 0 1 .587-.242q.346 0 .596.242.25.25.25.595 0 .347-.25.596'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/download-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 15.412a.83.83 0 0 1-.633-.256l-3.11-3.11a.7.7 0 0 1-.22-.522.77.77 0 0 1 .22-.532.77.77 0 0 1 .535-.24q.303-.008.535.225L11.25 12.9V5.25q0-.32.216-.535A.73.73 0 0 1 12 4.5q.32 0 .534.215a.73.73 0 0 1 .216.535v7.65l1.923-1.923a.71.71 0 0 1 .53-.22q.306.003.54.235.217.233.224.527a.7.7 0 0 1-.225.527l-3.11 3.11a.83.83 0 0 1-.632.255M6.308 19.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283v-1.961q0-.32.215-.535a.73.73 0 0 1 .535-.215q.32 0 .535.215a.73.73 0 0 1 .215.535v1.961q0 .116.096.212a.3.3 0 0 0 .212.096h11.384a.3.3 0 0 0 .212-.096.3.3 0 0 0 .096-.212v-1.961q0-.32.215-.535a.73.73 0 0 1 .535-.215q.32 0 .535.215a.73.73 0 0 1 .215.535v1.961q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/download.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 15.713a.8.8 0 0 1-.296-.056.75.75 0 0 1-.256-.186l-3.633-3.633a.73.73 0 0 1-.227-.557.79.79 0 0 1 .795-.787.76.76 0 0 1 .561.231l2.27 2.28v-8.05q0-.326.229-.557a.76.76 0 0 1 .562-.231q.335 0 .564.231.23.231.229.556v8.05l2.28-2.279a.71.71 0 0 1 .55-.223q.32.01.56.236a.78.78 0 0 1 .22.562.76.76 0 0 1-.227.554l-3.616 3.617a.8.8 0 0 1-.267.185.8.8 0 0 1-.298.057m-6.248 4.12a1.52 1.52 0 0 1-1.112-.47 1.52 1.52 0 0 1-.471-1.113V15.7q0-.333.232-.564a.77.77 0 0 1 .567-.231q.325 0 .555.23.23.232.23.565v2.55h12.5V15.7q0-.333.231-.564a.77.77 0 0 1 .567-.231q.326 0 .555.23a.77.77 0 0 1 .23.565v2.55q0 .642-.471 1.113a1.52 1.52 0 0 1-1.113.47z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/edit-calendar-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5.308 21.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T5.308 4.5h1.384V3.154q0-.326.222-.548a.75.75 0 0 1 .549-.221q.327 0 .548.22.22.221.22.549V4.5h7.577V3.135q0-.318.215-.535a.73.73 0 0 1 .535-.215q.319 0 .534.215a.73.73 0 0 1 .216.535V4.5h1.384q.758 0 1.283.525t.525 1.283v4.313q0 .319-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534v-.313H5v9.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.865q.319 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535a.73.73 0 0 1-.534.215zm8.808-.904v-1.361q0-.177.065-.343a.9.9 0 0 1 .206-.305l5.157-5.133a1.2 1.2 0 0 1 .835-.338 1.22 1.22 0 0 1 .857.353l.925.935q.162.186.25.404.09.218.089.434 0 .217-.076.442a1.06 1.06 0 0 1-.262.412l-5.133 5.133a.9.9 0 0 1-.648.271h-1.362a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644m6.267-4.388.925-.966-.925-.934-.95.95z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/edit-calendar.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.734 21.833q-.64 0-1.112-.47a1.52 1.52 0 0 1-.472-1.113V5.383q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.47h1.5v-.847q0-.337.24-.58a.8.8 0 0 1 .585-.24q.35 0 .596.24a.78.78 0 0 1 .245.58V3.8h8.2v-.846q0-.337.241-.58a.8.8 0 0 1 .584-.24q.351 0 .596.24a.78.78 0 0 1 .246.58V3.8h1.5q.64 0 1.112.471.471.472.471 1.112v4.509a.75.75 0 0 1-.234.56.78.78 0 0 1-.56.227.77.77 0 0 1-.558-.228.76.76 0 0 1-.23-.564v-.02H4.733V20.25h6.812q.324 0 .556.233a.76.76 0 0 1 .232.558q0 .334-.232.563a.76.76 0 0 1-.556.23zm0-13.55h14.533v-2.9H4.734zm9.183 12.755v-1.9q0-.146.056-.287a.9.9 0 0 1 .177-.272l5.269-5.246q.223-.214.492-.315.27-.102.538-.101a1.438 1.438 0 0 1 1.047.433l.925.93q.21.217.312.483.1.267.1.537t-.105.549-.325.49L17.171 21.6a.9.9 0 0 1-.27.177.8.8 0 0 1-.288.056h-1.9a.77.77 0 0 1-.565-.23.77.77 0 0 1-.23-.566m1.463-.667h.946l3.062-3.083-.448-.475-.473-.45-3.087 3.058zm3.558-3.558-.475-.45.925.925z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/edit-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M4.404 20.5a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644v-1.733q0-.365.14-.697a1.8 1.8 0 0 1 .387-.578L16.691 3.932q.226-.207.5-.319a1.5 1.5 0 0 1 .575-.112q.3 0 .583.107.282.106.499.34l1.221 1.236q.233.217.332.5.099.282.099.565 0 .301-.103.576t-.328.501L7.412 19.973a1.8 1.8 0 0 1-1.275.527zM17.552 7.694 19 6.256 17.744 5l-1.438 1.448z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/edit-square-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M10.404 14.5a.88.88 0 0 1-.645-.259.88.88 0 0 1-.259-.645v-1.78q0-.362.14-.696a1.8 1.8 0 0 1 .387-.58l8.533-8.532q.232-.233.511-.34.279-.105.567-.106.295 0 .566.106.27.107.494.33l1.256 1.252q.217.232.334.514.116.28.116.569t-.1.56q-.098.27-.331.503l-8.561 8.562a1.9 1.9 0 0 1-.58.394 1.7 1.7 0 0 1-.696.148zm9.129-8.777 1.386-1.38-1.256-1.285-1.405 1.39zM5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h6.47q.313 0 .518.16a1.02 1.02 0 0 1 .385.905.82.82 0 0 1-.254.472L7.835 9.629a1.8 1.8 0 0 0-.387.58q-.14.333-.14.695v3.98q0 .749.53 1.279t1.278.53h3.917q.36 0 .695-.141.333-.141.58-.387l4.656-4.655a.82.82 0 0 1 .472-.254 1.016 1.016 0 0 1 .904.384q.16.207.16.518v6.534q0 .758-.525 1.283t-1.283.525z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/edit-square.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h6.852q.375 0 .562.236a.8.8 0 0 1 .188.514.8.8 0 0 1-.196.515Q12.52 5 12.144 5H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h13.384a.3.3 0 0 0 .212-.096.3.3 0 0 0 .096-.212v-6.9q0-.375.235-.562a.8.8 0 0 1 .515-.188.8.8 0 0 1 .515.188q.235.187.235.562v6.9q0 .758-.525 1.283t-1.283.525zM9.5 13.596v-1.78q0-.362.14-.696a1.8 1.8 0 0 1 .387-.58l8.533-8.532q.232-.233.511-.34.279-.105.567-.106.295 0 .566.106.27.107.494.33l1.256 1.252q.217.232.334.514.116.28.116.569t-.1.56q-.098.27-.331.503l-8.561 8.562a1.9 1.9 0 0 1-.58.394 1.7 1.7 0 0 1-.696.148h-1.732a.88.88 0 0 1-.645-.259.88.88 0 0 1-.259-.645M11 13h1.246l6.233-6.233-.623-.623-.668-.642L11 11.691z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/edit.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.751 19.25h1.142L17.035 8.13l-1.146-1.147L4.75 18.113zm-.787 1.583a.77.77 0 0 1-.567-.229.77.77 0 0 1-.229-.567v-1.914q0-.315.127-.613.126-.3.348-.518L17.018 3.629a1.62 1.62 0 0 1 1.103-.446q.3 0 .592.119.293.118.534.335l1.142 1.146q.225.23.335.519a1.62 1.62 0 0 1-.001 1.184 1.4 1.4 0 0 1-.334.51L7.009 20.358a1.6 1.6 0 0 1-1.133.475zM16.459 7.558l-.57-.575 1.146 1.146z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/empty-search.tsx",
    "content": "import React from \"react\";\nimport Svg, { G, Path, Defs, ClipPath, Rect } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ width = 120, height = 120, ...props }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 120 120\"\n    fill=\"none\"\n    {...props}\n  >\n    <G clipPath=\"url(#clip0_15027_349483)\">\n      <Path\n        opacity={0.2}\n        d=\"M105 45.2598H97.5V64.0098C97.5 74.3635 89.1037 82.7598 78.75 82.7598H45V90.2598C45 98.5435 51.7162 105.26 60 105.26H82.5L98.85 118.872C100.44 120.196 102.806 119.982 104.13 118.392C104.692 117.717 105 116.866 105 115.985V105.26C113.284 105.26 120 98.5435 120 90.2598V60.2598C120 51.976 113.284 45.2598 105 45.2598Z\"\n        fill=\"#DCDCDC\"\n      />\n      <Path\n        opacity={0.2}\n        d=\"M75 0.259766H15C6.71625 0.259766 0 6.97602 0 15.2598V60.2598C0 68.5435 6.71625 75.2598 15 75.2598V89.6223C15 91.6923 16.68 93.3723 18.75 93.3723C19.6425 93.3723 20.5088 93.0535 21.1875 92.4723L41.25 75.2598H75C83.2838 75.2598 90 68.5435 90 60.2598V15.2598C90 6.97602 83.2838 0.259766 75 0.259766ZM27.7875 43.0473C27.4575 43.411 27.0788 43.726 26.6625 43.9848C26.2575 44.2585 25.8337 44.4985 25.3875 44.6973C24.9225 44.8698 24.4462 45.0085 23.9625 45.1098C23.4825 45.2148 22.9912 45.2635 22.5 45.2598C18.3563 45.2598 15 41.9035 15 37.7598C14.9213 33.6198 18.2138 30.196 22.3538 30.1173C22.8937 30.106 23.4338 30.1548 23.9625 30.2598C24.4462 30.361 24.9225 30.4998 25.3875 30.6723C25.8412 30.8598 26.2688 31.096 26.6625 31.3848C27.0713 31.6435 27.4462 31.9435 27.7875 32.2848C29.2425 33.7323 30.0413 35.7085 30 37.7598C29.9925 39.7473 29.1975 41.6485 27.7875 43.0473ZM50.2875 43.0473C48.8887 44.4573 46.9875 45.2523 45 45.2598C44.5125 45.316 44.025 45.316 43.5375 45.2598C43.0537 45.1585 42.5775 45.0198 42.1125 44.8473C41.6662 44.6485 41.2425 44.4085 40.8375 44.1348C40.4175 43.8648 40.0275 43.5535 39.675 43.1973C38.2425 41.7535 37.4588 39.7923 37.5 37.7598C37.4437 37.2723 37.4437 36.7848 37.5 36.2973C37.6012 35.8135 37.74 35.3373 37.9125 34.8723C38.1 34.4185 38.3363 33.991 38.625 33.5973C38.8913 33.1848 39.195 32.7948 39.525 32.4348C39.885 32.1048 40.275 31.801 40.6875 31.5348C41.0813 31.246 41.5087 31.0098 41.9625 30.8223C42.4725 30.5898 42.9975 30.4023 43.5375 30.2598C47.5987 29.4498 51.5475 32.0898 52.3575 36.151C52.4625 36.6798 52.5112 37.2198 52.5 37.7598C52.5 39.7398 51.72 41.641 50.325 43.0473H50.2875ZM72.7875 43.0473C72.4575 43.411 72.0788 43.726 71.6625 43.9848C71.2575 44.2585 70.8338 44.4985 70.3875 44.6973C69.9225 44.8698 69.4463 45.0085 68.9625 45.1098C68.4825 45.2148 67.9912 45.2635 67.5 45.2598C63.3563 45.2598 60 41.9035 60 37.7598C59.9212 33.6198 63.2138 30.196 67.3538 30.1173C67.8938 30.106 68.4337 30.1548 68.9625 30.2598C69.4463 30.361 69.9225 30.4998 70.3875 30.6723C70.8413 30.8598 71.2688 31.096 71.6625 31.3848C72.0713 31.6435 72.4463 31.9435 72.7875 32.2848C74.2425 33.7323 75.0413 35.7085 75 37.7598C74.9925 39.7473 74.1975 41.6485 72.7875 43.0473Z\"\n        fill=\"#DCDCDC\"\n      />\n      <Path\n        d=\"M47.7349 18.3633C41.9551 18.3602 36.3043 20.0713 31.497 23.2801C26.6898 26.4889 22.9422 31.0513 20.7283 36.3902C18.5143 41.7291 17.9334 47.6047 19.0591 53.2738C20.1847 58.9429 22.9664 64.1508 27.0522 68.2389C31.138 72.3269 36.3445 75.1114 42.013 76.2401C47.6815 77.3688 53.5574 76.7911 58.8975 74.58C64.2376 72.3688 68.802 68.6237 72.0134 63.8182C75.2248 59.0127 76.9389 53.3628 76.9389 47.583C76.9389 39.8362 73.8626 32.4064 68.3862 26.9271C62.9099 21.4478 55.4817 18.3674 47.7349 18.3633Z\"\n        fill=\"white\"\n        stroke=\"#A1A1A1\"\n        strokeWidth={1.57011}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <Path\n        d=\"M68.5233 68.7793L76.311 76.567\"\n        stroke=\"#A1A1A1\"\n        strokeWidth={1.57011}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <Path\n        d=\"M75.4307 72.784L73.0113 75.3149C71.9267 76.4494 71.9672 78.2483 73.1017 79.3329L94.0982 99.4046C95.2328 100.489 97.0317 100.449 98.1163 99.3142L100.536 96.7832C101.62 95.6487 101.58 93.8498 100.445 92.7652L79.4487 72.6935C78.3142 71.6089 76.5153 71.6494 75.4307 72.784Z\"\n        fill=\"#A1A1A1\"\n      />\n    </G>\n    <Defs>\n      <ClipPath id=\"clip0_15027_349483\">\n        <Rect width={120} height={120} fill=\"white\" />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/empty-state.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 190 123' fill='none'>\n    <Path\n      d='M10.5 11.9987C10.5 6.3838 15.0518 1.83203 20.6667 1.83203H159.333C164.948 1.83203 169.5 6.3838 169.5 11.9987V27.9987C169.5 33.6136 164.948 38.1654 159.333 38.1654H20.6667C15.0518 38.1654 10.5 33.6136 10.5 27.9987V11.9987Z'\n      fill='#FAFAFA'\n    />\n    <Path\n      d='M10.5 11.9987C10.5 6.3838 15.0518 1.83203 20.6667 1.83203H159.333C164.948 1.83203 169.5 6.3838 169.5 11.9987V27.9987C169.5 33.6136 164.948 38.1654 159.333 38.1654H20.6667C15.0518 38.1654 10.5 33.6136 10.5 27.9987V11.9987Z'\n      stroke='#F5F5F5'\n    />\n    <Path\n      opacity={0.6}\n      d='M34.0003 33.3327C41.3641 33.3327 47.3337 27.3631 47.3337 19.9993C47.3337 12.6356 41.3641 6.66602 34.0003 6.66602C26.6365 6.66602 20.667 12.6356 20.667 19.9993C20.667 27.3631 26.6365 33.3327 34.0003 33.3327Z'\n      fill='#A1A1A1'\n    />\n    <Path\n      d='M32.2454 22.6816C32.2454 22.5462 32.2246 22.4247 32.1829 22.3171C32.1447 22.2059 32.0718 22.1053 31.9642 22.015C31.8566 21.9212 31.7055 21.8292 31.5111 21.7389C31.3166 21.6487 31.0649 21.5549 30.7559 21.4577C30.4121 21.3466 30.0857 21.2216 29.7767 21.0827C29.4711 20.9438 29.2003 20.7823 28.9642 20.5983C28.7316 20.4108 28.5475 20.1938 28.4121 19.9473C28.2802 19.7007 28.2142 19.4143 28.2142 19.0879C28.2142 18.7719 28.2836 18.4855 28.4225 18.2285C28.5614 17.9681 28.7559 17.7459 29.0059 17.5618C29.2559 17.3743 29.551 17.2303 29.8913 17.1296C30.235 17.0289 30.6118 16.9785 31.0215 16.9785C31.5805 16.9785 32.0684 17.0792 32.485 17.2806C32.9017 17.482 33.2246 17.758 33.4538 18.1087C33.6864 18.4594 33.8027 18.8605 33.8027 19.3118H32.2507C32.2507 19.0896 32.2038 18.8952 32.11 18.7285C32.0197 18.5584 31.8809 18.4247 31.6934 18.3275C31.5093 18.2303 31.2767 18.1816 30.9954 18.1816C30.7246 18.1816 30.4989 18.2233 30.3184 18.3066C30.1378 18.3865 30.0024 18.4959 29.9121 18.6348C29.8218 18.7702 29.7767 18.923 29.7767 19.0931C29.7767 19.2216 29.8079 19.3379 29.8704 19.4421C29.9364 19.5462 30.0336 19.6434 30.1621 19.7337C30.2906 19.824 30.4486 19.9091 30.6361 19.9889C30.8236 20.0688 31.0406 20.1469 31.2871 20.2233C31.7003 20.3483 32.0632 20.4889 32.3757 20.6452C32.6916 20.8014 32.9555 20.9768 33.1673 21.1712C33.3791 21.3657 33.5388 21.5862 33.6465 21.8327C33.7541 22.0792 33.8079 22.3587 33.8079 22.6712C33.8079 23.0011 33.7437 23.2962 33.6152 23.5566C33.4868 23.8171 33.301 24.0375 33.0579 24.2181C32.8149 24.3987 32.525 24.5358 32.1882 24.6296C31.8513 24.7233 31.4746 24.7702 31.0579 24.7702C30.6829 24.7702 30.3132 24.7216 29.9486 24.6243C29.584 24.5237 29.2524 24.3726 28.9538 24.1712C28.6586 23.9698 28.4225 23.7129 28.2454 23.4004C28.0684 23.0879 27.9798 22.7181 27.9798 22.291H29.5475C29.5475 22.5271 29.584 22.7268 29.6569 22.89C29.7298 23.0532 29.8322 23.1851 29.9642 23.2858C30.0996 23.3865 30.2593 23.4594 30.4434 23.5046C30.6309 23.5497 30.8357 23.5723 31.0579 23.5723C31.3288 23.5723 31.551 23.5341 31.7246 23.4577C31.9017 23.3813 32.0319 23.2754 32.1152 23.14C32.202 23.0046 32.2454 22.8518 32.2454 22.6816ZM36.4329 17.0827V24.666H34.8704V17.0827H36.4329ZM39.4538 20.3223V21.541H36.0059V20.3223H39.4538ZM39.8184 17.0827V18.3066H36.0059V17.0827H39.8184Z'\n      fill='white'\n    />\n    <Path\n      d='M96.6667 9.33203H62C59.7909 9.33203 58 11.1229 58 13.332C58 15.5412 59.7909 17.332 62 17.332H96.6667C98.8758 17.332 100.667 15.5412 100.667 13.332C100.667 11.1229 98.8758 9.33203 96.6667 9.33203Z'\n      fill='#DCDCDC'\n    />\n    <Path\n      d='M153.253 22.666H64.08C60.7221 22.666 58 24.4569 58 26.666C58 28.8752 60.7221 30.666 64.08 30.666H153.253C156.611 30.666 159.333 28.8752 159.333 26.666C159.333 24.4569 156.611 22.666 153.253 22.666Z'\n      fill='#E8E8E8'\n    />\n    <Path\n      d='M30.5 53.9987C30.5 48.3838 35.0518 43.832 40.6667 43.832H179.333C184.948 43.832 189.5 48.3838 189.5 53.9987V69.9987C189.5 75.6136 184.948 80.1654 179.333 80.1654H40.6667C35.0518 80.1654 30.5 75.6136 30.5 69.9987V53.9987Z'\n      fill='#FAFAFA'\n    />\n    <Path\n      d='M30.5 53.9987C30.5 48.3838 35.0518 43.832 40.6667 43.832H179.333C184.948 43.832 189.5 48.3838 189.5 53.9987V69.9987C189.5 75.6136 184.948 80.1654 179.333 80.1654H40.6667C35.0518 80.1654 30.5 75.6136 30.5 69.9987V53.9987Z'\n      stroke='#F5F5F5'\n    />\n    <Path\n      opacity={0.6}\n      d='M54.0003 75.3327C61.3641 75.3327 67.3337 69.3631 67.3337 61.9993C67.3337 54.6356 61.3641 48.666 54.0003 48.666C46.6365 48.666 40.667 54.6356 40.667 61.9993C40.667 69.3631 46.6365 75.3327 54.0003 75.3327Z'\n      fill='#A1A1A1'\n    />\n    <Path\n      d='M49.9954 65.0827L51.7819 59.0827H53.5215L50.8809 66.666H49.735L49.9954 65.0827ZM48.3496 59.0827L50.1309 65.0827L50.4017 66.666H49.2454L46.6204 59.0827H48.3496ZM60.4017 59.0827V66.666H58.8392L55.7923 61.5827V66.666H54.2298V59.0827H55.7923L58.8444 64.1712V59.0827H60.4017Z'\n      fill='white'\n    />\n    <Path\n      d='M116.667 51.332H82C79.7909 51.332 78 53.1229 78 55.332C78 57.5412 79.7909 59.332 82 59.332H116.667C118.876 59.332 120.667 57.5412 120.667 55.332C120.667 53.1229 118.876 51.332 116.667 51.332Z'\n      fill='#DCDCDC'\n    />\n    <Path\n      d='M173.253 64.666H84.08C80.7221 64.666 78 66.4569 78 68.666C78 70.8752 80.7221 72.666 84.08 72.666H173.253C176.611 72.666 179.333 70.8752 179.333 68.666C179.333 66.4569 176.611 64.666 173.253 64.666Z'\n      fill='#E8E8E8'\n    />\n    <Path\n      d='M0.5 95.9987C0.5 90.3838 5.05177 85.832 10.6667 85.832H149.333C154.948 85.832 159.5 90.3838 159.5 95.9987V111.999C159.5 117.614 154.948 122.165 149.333 122.165H10.6667C5.05178 122.165 0.5 117.614 0.5 111.999V95.9987Z'\n      fill='#FAFAFA'\n    />\n    <Path\n      d='M0.5 95.9987C0.5 90.3838 5.05177 85.832 10.6667 85.832H149.333C154.948 85.832 159.5 90.3838 159.5 95.9987V111.999C159.5 117.614 154.948 122.165 149.333 122.165H10.6667C5.05178 122.165 0.5 117.614 0.5 111.999V95.9987Z'\n      stroke='#F5F5F5'\n    />\n    <Path\n      opacity={0.6}\n      d='M24.0003 117.333C31.3641 117.333 37.3337 111.363 37.3337 103.999C37.3337 96.6356 31.3641 90.666 24.0003 90.666C16.6365 90.666 10.667 96.6356 10.667 103.999C10.667 111.363 16.6365 117.333 24.0003 117.333Z'\n      fill='#A1A1A1'\n    />\n    <Path\n      d='M16.7663 101.083H18.0892L20.0371 106.65L21.985 101.083H23.3079L20.5684 108.666H19.5059L16.7663 101.083ZM16.0527 101.083H17.3704L17.61 106.51V108.666H16.0527V101.083ZM22.7038 101.083H24.0267V108.666H22.4642V106.51L22.7038 101.083ZM29.3444 106.682C29.3444 106.546 29.3236 106.425 29.2819 106.317C29.2437 106.206 29.1708 106.105 29.0632 106.015C28.9555 105.921 28.8045 105.829 28.61 105.739C28.4156 105.649 28.1638 105.555 27.8548 105.458C27.5111 105.347 27.1847 105.222 26.8757 105.083C26.5701 104.944 26.2993 104.782 26.0632 104.598C25.8305 104.411 25.6465 104.194 25.5111 103.947C25.3791 103.701 25.3132 103.414 25.3132 103.088C25.3132 102.772 25.3826 102.485 25.5215 102.229C25.6604 101.968 25.8548 101.746 26.1048 101.562C26.3548 101.374 26.65 101.23 26.9902 101.13C27.334 101.029 27.7107 100.979 28.1204 100.979C28.6795 100.979 29.1673 101.079 29.584 101.281C30.0007 101.482 30.3236 101.758 30.5527 102.109C30.7854 102.459 30.9017 102.86 30.9017 103.312H29.3496C29.3496 103.09 29.3027 102.895 29.209 102.729C29.1187 102.558 28.9798 102.425 28.7923 102.327C28.6083 102.23 28.3757 102.182 28.0944 102.182C27.8236 102.182 27.5979 102.223 27.4173 102.307C27.2368 102.387 27.1013 102.496 27.0111 102.635C26.9208 102.77 26.8757 102.923 26.8757 103.093C26.8757 103.222 26.9069 103.338 26.9694 103.442C27.0354 103.546 27.1326 103.643 27.2611 103.734C27.3895 103.824 27.5475 103.909 27.735 103.989C27.9225 104.069 28.1395 104.147 28.3861 104.223C28.7993 104.348 29.1621 104.489 29.4746 104.645C29.7906 104.801 30.0545 104.977 30.2663 105.171C30.4781 105.366 30.6378 105.586 30.7454 105.833C30.8531 106.079 30.9069 106.359 30.9069 106.671C30.9069 107.001 30.8427 107.296 30.7142 107.557C30.5857 107.817 30.4 108.038 30.1569 108.218C29.9138 108.399 29.6239 108.536 29.2871 108.63C28.9503 108.723 28.5736 108.77 28.1569 108.77C27.7819 108.77 27.4121 108.722 27.0475 108.624C26.6829 108.524 26.3513 108.373 26.0527 108.171C25.7576 107.97 25.5215 107.713 25.3444 107.4C25.1673 107.088 25.0788 106.718 25.0788 106.291H26.6465C26.6465 106.527 26.6829 106.727 26.7559 106.89C26.8288 107.053 26.9312 107.185 27.0632 107.286C27.1986 107.387 27.3583 107.459 27.5423 107.505C27.7298 107.55 27.9347 107.572 28.1569 107.572C28.4277 107.572 28.65 107.534 28.8236 107.458C29.0007 107.381 29.1309 107.275 29.2142 107.14C29.301 107.005 29.3444 106.852 29.3444 106.682Z'\n      fill='white'\n    />\n    <Path\n      d='M86.6667 93.332H52C49.7909 93.332 48 95.1229 48 97.332C48 99.5412 49.7909 101.332 52 101.332H86.6667C88.8758 101.332 90.6667 99.5412 90.6667 97.332C90.6667 95.1229 88.8758 93.332 86.6667 93.332Z'\n      fill='#DCDCDC'\n    />\n    <Path\n      d='M143.253 106.666H54.08C50.7221 106.666 48 108.457 48 110.666C48 112.875 50.7221 114.666 54.08 114.666H143.253C146.611 114.666 149.333 112.875 149.333 110.666C149.333 108.457 146.611 106.666 143.253 106.666Z'\n      fill='#E8E8E8'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/error-fill.tsx",
    "content": "import Svg, { G, Mask, Path, Defs, ClipPath } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G clipPath='url(#prefix__a)'>\n      <Mask\n        id='prefix__b'\n        width={24}\n        height={24}\n        x={0}\n        y={0}\n        maskUnits='userSpaceOnUse'\n        style={{\n          maskType: \"alpha\",\n        }}\n      >\n        <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n      </Mask>\n      <G mask='url(#prefix__b)'>\n        <Path fill='#fff' d='M2.5 12a9.5 9.5 0 1 1 19 0 9.5 9.5 0 0 1-19 0' />\n        <Path\n          fill={color}\n          d='M12 16.73q.343 0 .575-.232a.78.78 0 0 0 .233-.575.78.78 0 0 0-.232-.575.78.78 0 0 0-.576-.232.78.78 0 0 0-.575.232.78.78 0 0 0-.233.575q0 .343.232.575a.78.78 0 0 0 .576.233m0-3.653q.32 0 .534-.216a.73.73 0 0 0 .216-.534v-4.5a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534v4.5q0 .319.216.534a.73.73 0 0 0 .534.216m.002 8.423a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n        />\n      </G>\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M0 0h24v24H0z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/error-state.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Circle, ClipPath, Defs, G, Mask, Path, Rect } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 120 120' fill='none'>\n    <G clipPath='url(#clip0_5082_126538)'>\n      <Mask\n        id='mask0_5082_126538'\n        style={{\n          maskType: \"alpha\",\n        }}\n        maskUnits='userSpaceOnUse'\n        x={0}\n        y={0}\n        width={120}\n        height={120}\n      >\n        <Path\n          d='M120 60C120 68.9108 118.054 77.3757 114.559 84.9811C105.089 105.649 84.2189 120 60 120C35.7811 120 14.9108 105.649 5.44054 84.9811C1.94595 77.3757 0 68.9108 0 60C0 26.8622 26.8622 0 60 0C93.1378 0 120 26.8622 120 60Z'\n          fill='#F4F7FC'\n        />\n      </Mask>\n      <G mask='url(#mask0_5082_126538)'>\n        <Path d='M2.625 84L22.5 79.875L39 86.25L24 94.5L2.625 84Z' fill='#DCDCDC' />\n        <Path d='M117.375 84L97.5 79.875L81 86.25L96 94.5L117.375 84Z' fill='#DCDCDC' />\n        <Path opacity={0.6} d='M39 86.25H81L96 94.5H24L39 86.25Z' fill='#A1A1A1' />\n        <Path\n          d='M45.0804 50.5793C51.8396 46.6769 60.4825 48.9927 64.3849 55.7519C68.2873 62.5111 65.9714 71.154 59.2123 75.0564L51.2636 79.6455L49.1302 89.2054C48.8672 90.3839 47.2935 90.625 46.6897 89.5793L43.0182 83.22C37.4847 83.994 31.782 81.4235 28.8166 76.2873C24.9142 69.5281 27.23 60.8852 33.9892 56.9828L45.0804 50.5793Z'\n          fill='#E8E8E8'\n        />\n        <Circle\n          cx={2.2081}\n          cy={2.2081}\n          r={2.2081}\n          transform='matrix(-0.866025 0.5 0.5 0.866025 54.8672 58.7012)'\n          fill='#FAFAFA'\n        />\n        <Circle\n          cx={2.2081}\n          cy={2.2081}\n          r={2.2081}\n          transform='matrix(-0.866025 0.5 0.5 0.866025 47.2178 63.1191)'\n          fill='#FAFAFA'\n        />\n        <Circle\n          cx={2.2081}\n          cy={2.2081}\n          r={2.2081}\n          transform='matrix(-0.866025 0.5 0.5 0.866025 39.5693 67.5312)'\n          fill='#FAFAFA'\n        />\n        <Path\n          d='M68.732 23.6854C58.7161 21.0017 48.4209 26.9456 45.7372 36.9615C43.0534 46.9774 48.9973 57.2726 59.0132 59.9563L70.7933 63.1128L76.8183 74.647C77.561 76.0689 79.6634 75.8372 80.0786 74.2876L82.6037 64.864C89.9703 63.9541 96.4041 58.6946 98.4433 51.0841C101.127 41.0681 95.1832 30.773 85.1673 28.0892L68.732 23.6854Z'\n          fill='#DCDCDC'\n        />\n        <Circle\n          cx={61.0383}\n          cy={41.0617}\n          r={2.93362}\n          transform='rotate(15 61.0383 41.0617)'\n          fill='#FAFAFA'\n        />\n        <Circle\n          cx={72.3732}\n          cy={44.0968}\n          r={2.93362}\n          transform='rotate(15 72.3732 44.0968)'\n          fill='#FAFAFA'\n        />\n        <Circle\n          cx={83.7082}\n          cy={47.1379}\n          r={2.93362}\n          transform='rotate(15 83.7082 47.1379)'\n          fill='#FAFAFA'\n        />\n        <Path\n          fillRule='evenodd'\n          clipRule='evenodd'\n          d='M41.6949 32.7834C42.2991 32.6386 42.6716 32.0314 42.5268 31.4272C42.382 30.823 41.7748 30.4505 41.1706 30.5953L38.2532 31.2944C37.6489 31.4392 37.2765 32.0464 37.4213 32.6506C37.5661 33.2548 38.1733 33.6273 38.7775 33.4825L41.6949 32.7834ZM52.5119 19.0915C52.5107 19.7128 52.006 20.2155 51.3847 20.2143C50.7634 20.2131 50.2607 19.7084 50.2619 19.0871L50.2678 16.0871C50.269 15.4658 50.7736 14.9631 51.395 14.9643C52.0163 14.9655 52.519 15.4702 52.5178 16.0915L52.5119 19.0915ZM43.1167 26.035C42.9756 26.6401 42.3706 27.0162 41.7656 26.8751L38.844 26.1938C38.2389 26.0526 37.8627 25.4477 38.0038 24.8427C38.1449 24.2376 38.7499 23.8614 39.3549 24.0025L42.2766 24.6839C42.8816 24.825 43.2578 25.4299 43.1167 26.035ZM46.7833 20.528C47.0604 21.0841 46.8343 21.7595 46.2782 22.0367C45.7221 22.3138 45.0467 22.0877 44.7695 21.5316L43.4313 18.8466C43.1542 18.2905 43.3803 17.6151 43.9364 17.3379C44.4925 17.0608 45.1679 17.2869 45.4451 17.843L46.7833 20.528Z'\n          fill='#DCDCDC'\n        />\n        <Rect x={24} y={94.5} width={72} height={63} fill='#DCDCDC' />\n      </G>\n    </G>\n    <Defs>\n      <ClipPath id='clip0_5082_126538'>\n        <Rect width={120} height={120} rx={16} fill='white' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/error.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.998 16.917q.37 0 .612-.239a.82.82 0 0 0 .241-.608q0-.37-.239-.612a.82.82 0 0 0-.608-.241q-.37 0-.611.239a.82.82 0 0 0-.242.608q0 .37.239.611.24.242.608.242m.065-3.8q.33 0 .559-.232a.77.77 0 0 0 .23-.564V7.754a.76.76 0 0 0-.234-.555.76.76 0 0 0-.557-.232.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.567q0 .333.232.565a.77.77 0 0 0 .563.23m-.056 8.716a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/event-available-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.95 15.816 3.677-3.677a.75.75 0 0 1 .537-.228.73.73 0 0 1 .547.228.74.74 0 0 1 .233.542.74.74 0 0 1-.233.542l-4.128 4.129a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.271L8.29 15.323a.75.75 0 0 1-.229-.537.73.73 0 0 1 .229-.547.74.74 0 0 1 .542-.233q.31 0 .542.232zM5.308 21.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T5.308 4.5h1.384V3.154q0-.329.22-.55a.75.75 0 0 1 .55-.22q.328 0 .549.22.22.221.22.55V4.5h7.577V3.135q0-.32.215-.535a.73.73 0 0 1 .535-.215q.319 0 .534.215a.73.73 0 0 1 .216.535V4.5h1.384q.758 0 1.283.525t.525 1.283v13.384q0 .758-.525 1.283t-1.283.525zm0-1.5h13.384a.3.3 0 0 0 .212-.096.3.3 0 0 0 .096-.212v-9.384H5v9.384q0 .116.096.212a.3.3 0 0 0 .212.096'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/event-available.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.95 15.816 3.677-3.677a.75.75 0 0 1 .537-.228.73.73 0 0 1 .547.228.74.74 0 0 1 .233.542.74.74 0 0 1-.233.542l-4.128 4.129a.87.87 0 0 1-.633.271.87.87 0 0 1-.633-.271L8.29 15.323a.75.75 0 0 1-.229-.537.73.73 0 0 1 .229-.547.74.74 0 0 1 .542-.233q.31 0 .542.232zM5.308 21.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T5.308 4.5h1.384V3.154q0-.329.22-.55a.75.75 0 0 1 .55-.22q.328 0 .549.22.22.221.22.55V4.5h7.577V3.135q0-.32.215-.535a.73.73 0 0 1 .535-.215q.319 0 .534.215a.73.73 0 0 1 .216.535V4.5h1.384q.758 0 1.283.525t.525 1.283v13.384q0 .758-.525 1.283t-1.283.525zm0-1.5h13.384a.3.3 0 0 0 .212-.096.3.3 0 0 0 .096-.212v-9.384H5v9.384q0 .116.096.212a.3.3 0 0 0 .212.096M5 8.808h14v-2.5a.3.3 0 0 0-.096-.212.3.3 0 0 0-.212-.096H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/event-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M14.692 18a2.23 2.23 0 0 1-1.635-.672 2.23 2.23 0 0 1-.672-1.636q0-.963.672-1.635a2.23 2.23 0 0 1 1.635-.672q.964 0 1.636.672T17 15.692t-.672 1.636a2.23 2.23 0 0 1-1.636.672m-9.384 3.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T5.308 4.5h1.384V3.154q0-.329.22-.55a.75.75 0 0 1 .55-.22q.328 0 .549.22.22.221.22.55V4.5h7.577V3.135q0-.32.215-.535a.73.73 0 0 1 .535-.215q.319 0 .534.215a.73.73 0 0 1 .216.535V4.5h1.384q.758 0 1.283.525t.525 1.283v13.384q0 .758-.525 1.283t-1.283.525zm0-1.5h13.384a.3.3 0 0 0 .212-.096.3.3 0 0 0 .096-.212v-9.384H5v9.384q0 .116.096.212a.3.3 0 0 0 .212.096'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/event.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M14.78 18.267q-1.014 0-1.706-.694-.69-.694-.69-1.71 0-1.014.694-1.705.693-.69 1.709-.691 1.015 0 1.706.694.69.693.69 1.709t-.693 1.706-1.71.69M4.735 21.833q-.64 0-1.112-.47a1.52 1.52 0 0 1-.472-1.113V5.383q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.47h1.5v-.847q0-.337.24-.58a.8.8 0 0 1 .585-.24q.35 0 .596.24a.78.78 0 0 1 .245.58V3.8h8.2v-.846q0-.337.241-.58a.8.8 0 0 1 .584-.24q.351 0 .596.24a.78.78 0 0 1 .246.58V3.8h1.5q.64 0 1.112.471.471.472.471 1.112V20.25q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm0-1.583h14.533V9.867H4.734zm0-11.967h14.533v-2.9H4.734z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/favorite-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.99 19.71q-.321 0-.645-.116a1.5 1.5 0 0 1-.57-.361l-1.437-1.306a66 66 0 0 1-4.748-4.764Q2.5 10.823 2.5 8.15q0-2.129 1.436-3.565T7.5 3.15q1.21 0 2.39.558T12 5.519q.93-1.254 2.11-1.811a5.54 5.54 0 0 1 2.39-.558q2.13 0 3.564 1.435Q21.5 6.022 21.5 8.15q0 2.701-2.125 5.068a64 64 0 0 1-4.738 4.724l-1.422 1.29a1.5 1.5 0 0 1-.575.362q-.329.115-.65.115'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/favorite.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 20.28q-.291 0-.573-.097a1.26 1.26 0 0 1-.502-.32l-1.354-1.25q-2.983-2.747-5.194-5.267t-2.21-5.333q0-2.235 1.502-3.732 1.5-1.498 3.72-1.498 1.266 0 2.476.594t2.135 1.873q1.026-1.28 2.176-1.873a5.25 5.25 0 0 1 2.439-.594q2.218 0 3.718 1.498t1.5 3.732q0 2.812-2.208 5.335t-5.179 5.256l-1.367 1.263q-.225.222-.504.317a1.8 1.8 0 0 1-.575.095m-.71-13.316q-.645-1.16-1.68-1.879-1.035-.718-2.232-.718-1.576 0-2.602 1.027-1.026 1.028-1.026 2.624 0 1.385.944 2.923a19.7 19.7 0 0 0 2.263 2.983 37 37 0 0 0 2.723 2.695q1.404 1.251 2.32 2.081l2.321-2.077a37 37 0 0 0 2.721-2.71 20.3 20.3 0 0 0 2.262-2.994q.946-1.54.946-2.908 0-1.59-1.034-2.617t-2.593-1.027q-1.215 0-2.248.71-1.034.711-1.703 1.89a.8.8 0 0 1-.306.31.86.86 0 0 1-.78 0 .67.67 0 0 1-.295-.313'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/filter-list-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.029 17.5a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.932q.32 0 .535.216a.73.73 0 0 1 .215.534q0 .32-.215.535a.73.73 0 0 1-.535.215zm-3.875-4.75a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h9.683q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.534a.73.73 0 0 1-.534.216zM4.25 8a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535A.73.73 0 0 1 4.25 6.5h15.5q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 19.75 8z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/filter-list-off-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.277 21.4 2.584 4.723a.74.74 0 0 1-.232-.534q0-.303.232-.535a.74.74 0 0 1 .535-.233q.302 0 .534.233l16.693 16.692q.232.232.232.527a.73.73 0 0 1-.232.527.74.74 0 0 1-.535.233.74.74 0 0 1-.534-.233m-3.383-8.65a.75.75 0 0 1-.542-.214.7.7 0 0 1-.224-.532q0-.298.215-.526a.72.72 0 0 1 .55-.228h.943q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .32-.215.534a.73.73 0 0 1-.535.216zM11.144 8a.76.76 0 0 1-.542-.215.7.7 0 0 1-.224-.53q0-.3.215-.527a.72.72 0 0 1 .55-.228h8.607q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.535A.73.73 0 0 1 19.75 8zm-.116 9.5a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.933q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .32-.215.535a.73.73 0 0 1-.535.215zm-3.875-4.75a.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534q0-.32.215-.534a.73.73 0 0 1 .534-.216h3.458v1.5zM4.25 8a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.535A.73.73 0 0 1 4.25 6.5H5.86V8z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/filter-list-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.509 21.75 2.226 4.48a.78.78 0 0 1-.238-.566.77.77 0 0 1 .239-.564.798.798 0 0 1 1.137 0l17.283 17.283a.76.76 0 0 1 .237.559q0 .321-.238.558a.8.8 0 0 1-1.137 0m-4.146-8.962a.76.76 0 0 1-.56-.234.76.76 0 0 1-.232-.552q0-.302.213-.55t.58-.248h1.72q.333 0 .565.232a.77.77 0 0 1 .231.567.75.75 0 0 1-.231.56.78.78 0 0 1-.565.224zm-4.962-4.955a.75.75 0 0 1-.56-.237.77.77 0 0 1-.232-.556q0-.303.217-.546a.73.73 0 0 1 .575-.244h9.646q.325 0 .556.232a.76.76 0 0 1 .231.558q0 .334-.231.564a.76.76 0 0 1-.556.23zm.45 9.917a.75.75 0 0 1-.56-.232.78.78 0 0 1-.228-.567.76.76 0 0 1 .227-.555.76.76 0 0 1 .56-.23h2.3q.334 0 .565.233.232.231.232.558a.76.76 0 0 1-.232.564.77.77 0 0 1-.564.229zm-3.938-4.962a.77.77 0 0 1-.564-.229.76.76 0 0 1-.231-.562q0-.334.23-.564a.77.77 0 0 1 .565-.229h3.621v1.583zm-2.95-4.955a.77.77 0 0 1-.564-.232.77.77 0 0 1-.231-.567q0-.325.23-.555a.77.77 0 0 1 .565-.229H5.58v1.583z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/filter-list.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M10.851 17.75a.75.75 0 0 1-.56-.232.78.78 0 0 1-.227-.567.76.76 0 0 1 .227-.555.76.76 0 0 1 .56-.23h2.3q.333 0 .565.233.231.231.231.558a.76.76 0 0 1-.231.564.77.77 0 0 1-.565.229zm-3.937-4.963a.77.77 0 0 1-.565-.228.76.76 0 0 1-.231-.562q0-.335.231-.564a.77.77 0 0 1 .565-.229h10.17q.334 0 .565.232a.77.77 0 0 1 .232.567q0 .335-.232.56a.78.78 0 0 1-.565.224zm-2.95-4.954a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.567q0-.326.231-.555a.77.77 0 0 1 .565-.23h16.083q.325 0 .556.233a.76.76 0 0 1 .231.558q0 .334-.23.564a.76.76 0 0 1-.557.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/flags.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <Path\n      fill={color ?? \"#A1A1A1\"}\n      fillRule='evenodd'\n      d='M3.667 16.967H2.601V2.347h1.066c.303.352.686.638 1.119.818s.902.257 1.37.226a5.5 5.5 0 0 0 2.41-.72 6.4 6.4 0 0 1 2.568-.638c.873.008 1.73.234 2.489.657l.71.46v8.359l-.71-.46a5.2 5.2 0 0 0-2.49-.662c-.771.077-1.52.3-2.206.657a6.8 6.8 0 0 1-2.773.657 3.59 3.59 0 0 1-2.487-.735v6.001m1.12-6.495a3 3 0 0 1-1.12-.807v-5.91c.731.485 1.61.71 2.49.636a6.8 6.8 0 0 0 2.77-.657 6 6 0 0 1 2.207-.657c.754.021 1.489.234 2.133.617l.002 6.128a5.3 5.3 0 0 0-2.135-.479 6.4 6.4 0 0 0-2.569.637 5.5 5.5 0 0 1-2.408.72 3.1 3.1 0 0 1-1.37-.228'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/flip-camera-ios-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M4.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V7.308q0-.758.525-1.283T4.308 5.5h3.054l1.307-1.417q.246-.271.595-.427.35-.156.736-.156h4q.387 0 .736.156.348.156.595.427L16.639 5.5h3.053q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zm11.296-7.466q-.131 1.499-1.135 2.56T12 16.653q-.21 0-.42-.03-.209-.03-.418-.08a.8.8 0 0 0-.378.01.55.55 0 0 0-.307.19.53.53 0 0 0-.118.504.51.51 0 0 0 .333.383q.32.125.65.17.328.045.658.045 1.976 0 3.338-1.414 1.362-1.415 1.458-3.428l.485.44a.6.6 0 0 0 .424.157.56.56 0 0 0 .399-.167.62.62 0 0 0 .194-.426.56.56 0 0 0-.179-.427l-1.236-1.237a.86.86 0 0 0-.633-.255.86.86 0 0 0-.633.255l-1.236 1.237a.58.58 0 0 0-.187.419.58.58 0 0 0 .187.42.58.58 0 0 0 .419.186.58.58 0 0 0 .42-.187zm-8.4-.038-.485-.44a.6.6 0 0 0-.424-.157.56.56 0 0 0-.399.167.62.62 0 0 0-.194.426.56.56 0 0 0 .179.427l1.236 1.237a.86.86 0 0 0 .633.255.86.86 0 0 0 .633-.255l1.236-1.237A.58.58 0 0 0 9.806 13a.58.58 0 0 0-.187-.42.58.58 0 0 0-.419-.186.58.58 0 0 0-.42.187l-.384.385q.131-1.499 1.135-2.56 1.003-1.06 2.469-1.06.21 0 .42.03.208.03.409.08a.74.74 0 0 0 .381 0 .54.54 0 0 0 .313-.2.54.54 0 0 0 .123-.504.5.5 0 0 0-.329-.383 5 5 0 0 0-.653-.16A4 4 0 0 0 12 8.154q-1.976 0-3.338 1.414-1.362 1.415-1.458 3.428'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/flip-camera-ios.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M15.604 13.035q-.131 1.497-1.135 2.558T12 16.653q-.21 0-.42-.029-.209-.03-.418-.08a.8.8 0 0 0-.378.01.55.55 0 0 0-.307.19.53.53 0 0 0-.118.504.51.51 0 0 0 .333.383q.32.125.65.17.328.045.658.045 1.976 0 3.338-1.414 1.362-1.415 1.458-3.428l.485.44a.6.6 0 0 0 .424.157.56.56 0 0 0 .399-.167.62.62 0 0 0 .194-.426.56.56 0 0 0-.179-.427l-1.236-1.237a.86.86 0 0 0-.633-.255.86.86 0 0 0-.633.255l-1.236 1.237a.58.58 0 0 0-.187.419.58.58 0 0 0 .187.42.58.58 0 0 0 .419.186.58.58 0 0 0 .42-.187zm-8.4-.039-.485-.44a.6.6 0 0 0-.424-.157.56.56 0 0 0-.399.167.62.62 0 0 0-.194.426.56.56 0 0 0 .179.427l1.236 1.237a.86.86 0 0 0 .633.255.86.86 0 0 0 .633-.255l1.236-1.237A.58.58 0 0 0 9.806 13a.58.58 0 0 0-.187-.42.58.58 0 0 0-.419-.186.58.58 0 0 0-.42.187l-.384.385q.131-1.499 1.135-2.56 1.003-1.06 2.469-1.06.21 0 .42.03.208.03.409.08a.74.74 0 0 0 .381 0 .54.54 0 0 0 .313-.2.54.54 0 0 0 .123-.504.5.5 0 0 0-.329-.383 5 5 0 0 0-.653-.16A4 4 0 0 0 12 8.154q-1.976 0-3.338 1.414-1.362 1.415-1.458 3.428M4.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V7.308q0-.758.525-1.283T4.308 5.5h3.054l1.307-1.417q.246-.271.595-.427.35-.156.736-.156h4q.387 0 .736.156.348.156.595.427L16.639 5.5h3.053q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zm0-1.5h15.384a.3.3 0 0 0 .221-.087.3.3 0 0 0 .087-.22V7.307a.3.3 0 0 0-.087-.221.3.3 0 0 0-.22-.087h-3.724l-1.835-2H9.866L8.03 7H4.308a.3.3 0 0 0-.221.087.3.3 0 0 0-.087.22v11.385a.3.3 0 0 0 .087.221.3.3 0 0 0 .22.087'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/food-&-drink.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G clipPath='url(#prefix__a)'>\n      <Path\n        fill={color ?? \"#A1A1A1\"}\n        d='M8.596 18.034h-.782l-5.986-.017L.467 4.857h6.262L7.6 1.35a.51.51 0 0 1 .623-.364l.064.02 3.46 1.182-.327.961-2.97-1.014-.678 2.723h1.603l-.32 3.24c.427-.022.862-.032 1.275-.032 1.66-.051 3.318.165 4.91.64 1.352.473 2.037 1.175 2.037 2.074a.53.53 0 0 1-.19.434l-.009.011q-.106.112-.189.243l.025.011c.025.011.048.022.05.043.016.187-.588.407-.593.409l1.162.672v.006q-.216.226-.491.373a1.22 1.22 0 0 1 .48 1.081 1.43 1.43 0 0 1-.798 1.272c.34.478.55 1.036.607 1.62 0 .546-1.56 1.068-1.899 1.068H8.596zm-3.12-2.512a.74.74 0 0 0-.661.486.61.61 0 0 0 .086.677c.292.21.648.312 1.007.287q.111.002.183-.006h8.97c.279-.016.543-.13.747-.32a.51.51 0 0 0 .129-.586.91.91 0 0 0-.813-.524H5.476zm-3.89-9.596 1.1 11.039h.647a3.35 3.35 0 0 1 .674-1.587c-.642-.402-.901-.748-.901-1.206a1.6 1.6 0 0 1 .567-1.302c-.389-.134-.523-.342-.52-.48 0-.107.082-.19.219-.23a.7.7 0 0 1 .359-.018h.015a.5.5 0 0 0 .098.014c.078 0 .145-.043.23-.152l-.093-.091-.043-.042c-.389-.379-.604-.587-.604-1.09 0-1.373 1.677-2.299 4.722-2.607l.25-2.25h-6.72zm2.722 6.767 6.057 2.091 6.317-2.09zM10.333 9.1c-1.782 0-4.917.33-5.751 1.211a.65.65 0 0 0-.201.49c.021.345 1.175.748 1.41.823h9.085c.056-.02 1.376-.493 1.39-.824a.67.67 0 0 0-.214-.491c-.814-.834-3.834-1.209-5.72-1.209'\n      />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-bold.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 -960 960 960'>\n    <Path\n      fill={color}\n      d='M293.54-215v-530H488q61.15 0 110.58 38.08Q648-668.85 648-604.38q0 44.84-21.66 73.11-21.65 28.27-46.65 41.04 30.77 10.61 58.77 41.96 28 31.35 28 84.42 0 76.69-56.54 112.77T496-215H293.54Zm86-79.69h113.23q47.23 0 66.77-26.23t19.54-50.31q0-24.08-19.54-50.31-19.54-26.23-68.62-26.23H379.54v153.08Zm0-230.31h103.77q36.46 0 57.81-20.85 21.34-20.84 21.34-49.92 0-30.92-22.57-50.54-22.58-19.61-55.35-19.61h-105V-525Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-italic.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 -960 960 960'>\n    <Path\n      fill={color}\n      d='M215.77-215v-72.31h152.69l129.62-385.38H345.39V-745h366.15v72.31H571.15L441.54-287.31h140.38V-215H215.77Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-list-bulleted.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5m0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5m0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5M7 19h14v-2H7zm0-6h14v-2H7zm0-8v2h14V5z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-list-numbered.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2 17h2v.5H3v1h1v.5H2v1h3v-4H2zm1-9h1V4H2v1h1zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2zm5-6v2h14V5zm0 14h14v-2H7zm0-6h14v-2H7z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-quote.tsx",
    "content": "import Svg, { G, Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G transform='translate(3.5, 3.5)'>\n      <Path\n        fill={color}\n        d='M0.75 17C0.5375 17 0.359417 16.9281 0.21575 16.7843C0.0719168 16.6404 0 16.4622 0 16.2498C0 16.0371 0.0719168 15.859 0.21575 15.7155C0.359417 15.5718 0.5375 15.5 0.75 15.5H16.25C16.4625 15.5 16.6406 15.5719 16.7843 15.7158C16.9281 15.8596 17 16.0378 17 16.2502C17 16.4629 16.9281 16.641 16.7843 16.7845C16.6406 16.9282 16.4625 17 16.25 17H0.75ZM0.75 13.125C0.5375 13.125 0.359417 13.0531 0.21575 12.9093C0.0719168 12.7654 0 12.5872 0 12.3748C0 12.1621 0.0719168 11.984 0.21575 11.8405C0.359417 11.6968 0.5375 11.625 0.75 11.625H10.25C10.4625 11.625 10.6406 11.6969 10.7843 11.8408C10.9281 11.9846 11 12.1628 11 12.3753C11 12.5879 10.9281 12.766 10.7843 12.9095C10.6406 13.0532 10.4625 13.125 10.25 13.125H0.75ZM0.75 9.25C0.5375 9.25 0.359417 9.17808 0.21575 9.03425C0.0719168 8.89042 0 8.71225 0 8.49975C0 8.28708 0.0719168 8.109 0.21575 7.9655C0.359417 7.82183 0.5375 7.75 0.75 7.75H16.25C16.4625 7.75 16.6406 7.82192 16.7843 7.96575C16.9281 8.10958 17 8.28775 17 8.50025C17 8.71292 16.9281 8.891 16.7843 9.0345C16.6406 9.17817 16.4625 9.25 16.25 9.25H0.75ZM0.75 5.375C0.5375 5.375 0.359417 5.30308 0.21575 5.15925C0.0719168 5.01542 0 4.83725 0 4.62475C0 4.41208 0.0719168 4.234 0.21575 4.0905C0.359417 3.94683 0.5375 3.875 0.75 3.875H10.25C10.4625 3.875 10.6406 3.94692 10.7843 4.09075C10.9281 4.23458 11 4.41275 11 4.62525C11 4.83792 10.9281 5.016 10.7843 5.1595C10.6406 5.30317 10.4625 5.375 10.25 5.375H0.75ZM0.75 1.5C0.5375 1.5 0.359417 1.42808 0.21575 1.28425C0.0719168 1.14042 0 0.96225 0 0.74975C0 0.537083 0.0719168 0.359 0.21575 0.2155C0.359417 0.0718332 0.5375 0 0.75 0H16.25C16.4625 0 16.6406 0.0719168 16.7843 0.21575C16.9281 0.359583 17 0.53775 17 0.75025C17 0.962917 16.9281 1.141 16.7843 1.2845C16.6406 1.42817 16.4625 1.5 16.25 1.5H0.75Z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-strikethrough.tsx",
    "content": "import Svg, { G, Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G transform='translate(2, 4)'>\n      <Path\n        fill={color}\n        d='M9.9 15.496C8.90133 15.496 7.97858 15.2313 7.13175 14.7018C6.28492 14.1723 5.61925 13.4633 5.13475 12.575C5.02958 12.3788 5.01675 12.1733 5.09625 11.9585C5.17575 11.7438 5.31867 11.5839 5.525 11.4788C5.74167 11.3634 5.96758 11.3455 6.20275 11.425C6.43808 11.5045 6.62175 11.6526 6.75375 11.8693C7.08592 12.4461 7.53342 12.9038 8.09625 13.2423C8.65892 13.5808 9.27042 13.75 9.93075 13.75C10.7603 13.75 11.4875 13.5058 12.1125 13.0173C12.7375 12.5288 13.05 11.8961 13.05 11.1193C13.05 11.0103 13.0404 10.9013 13.0212 10.7923C13.0019 10.6833 12.9634 10.5807 12.9057 10.4845C12.8866 10.2473 12.9552 10.0409 13.1115 9.86525C13.268 9.68958 13.4648 9.59217 13.702 9.573C13.9392 9.55367 14.1497 9.62225 14.3335 9.77875C14.5175 9.93508 14.6192 10.1318 14.6385 10.369C14.709 10.5088 14.7554 10.6598 14.7778 10.822C14.8003 10.9842 14.8115 11.1448 14.8115 11.3038C14.8115 12.5371 14.3179 13.5442 13.3307 14.325C12.3436 15.1057 11.2 15.496 9.9 15.496ZM0.75 7.55375C0.537167 7.55375 0.359 7.48192 0.2155 7.33825C0.0718334 7.19475 0 7.01658 0 6.80375C0 6.59092 0.0718334 6.41275 0.2155 6.26925C0.359 6.12558 0.537167 6.05375 0.75 6.05375H18.75C18.9628 6.05375 19.141 6.12558 19.2845 6.26925C19.4282 6.41275 19.5 6.59092 19.5 6.80375C19.5 7.01658 19.4282 7.19475 19.2845 7.33825C19.141 7.48192 18.9628 7.55375 18.75 7.55375H0.75ZM5.998 3.97675C5.78517 3.87425 5.63517 3.71017 5.548 3.4845C5.46083 3.25883 5.46858 3.03967 5.57125 2.827C5.81475 1.9475 6.33617 1.25642 7.1355 0.753751C7.93483 0.251251 8.823 0 9.8 0C10.5552 0 11.2661 0.158668 11.9327 0.476001C12.5994 0.793168 13.1583 1.24925 13.6095 1.84425C13.7417 2.01975 13.7856 2.2175 13.7413 2.4375C13.6971 2.65733 13.5821 2.83325 13.3962 2.96525C13.1897 3.10758 12.9689 3.16017 12.7337 3.123C12.4984 3.08583 12.2962 2.96917 12.127 2.773C11.8448 2.4255 11.5051 2.1675 11.1077 1.999C10.7102 1.83033 10.2846 1.746 9.83075 1.746C9.20775 1.746 8.63817 1.90342 8.122 2.21825C7.606 2.53292 7.28133 2.97167 7.148 3.5345C7.0455 3.75767 6.88142 3.91283 6.65575 4C6.43008 4.08717 6.21083 4.07942 5.998 3.97675Z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/format-underlined.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 -960 960 960'>\n    <Path\n      fill={color}\n      d='M213.85-155v-60h532.3v60h-532.3ZM480-298.85q-93.31 0-145.65-56.65Q282-412.15 282-507.31v-316.15h74.15v319.84q0 60.62 32.23 97.16T480-369.92q59.39 0 91.62-36.54 32.23-36.54 32.23-97.16v-319.84H678v316.15q0 95.16-52.35 151.81-52.34 56.65-145.65 56.65Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/forward-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m19.377 11-4.058-4.058a.74.74 0 0 1-.233-.534q0-.303.233-.535a.75.75 0 0 1 .54-.213.72.72 0 0 1 .514.213l4.494 4.494a.83.83 0 0 1 .256.633.829.829 0 0 1-.256.633l-4.494 4.494a.7.7 0 0 1-.527.225.78.78 0 0 1-.527-.225.74.74 0 0 1-.235-.532.7.7 0 0 1 .22-.522zm-5.308.75H7.25a3.13 3.13 0 0 0-2.298.952A3.13 3.13 0 0 0 4 15v2.75a.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535V15q0-1.97 1.39-3.36t3.36-1.39h6.82L10.76 6.942a.74.74 0 0 1-.232-.534q0-.303.232-.535a.75.75 0 0 1 .54-.213.72.72 0 0 1 .515.213l4.493 4.494a.83.83 0 0 1 .256.633.83.83 0 0 1-.256.633l-4.493 4.494a.7.7 0 0 1-.527.225.78.78 0 0 1-.527-.225.74.74 0 0 1-.236-.532.7.7 0 0 1 .22-.522z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/forward.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m19.626 10.933-4.142-4.158a.81.81 0 0 1-.25-.596q0-.346.25-.596a.82.82 0 0 1 .588-.241.8.8 0 0 1 .579.241l4.767 4.767q.132.133.187.275a.9.9 0 0 1 .054.308.9.9 0 0 1-.054.309.8.8 0 0 1-.187.275l-4.75 4.75a.8.8 0 0 1-.592.245.84.84 0 0 1-.592-.245.85.85 0 0 1-.262-.584.76.76 0 0 1 .237-.583zm-5.5.834H6.984q-1.416 0-2.358.941-.942.942-.942 2.359v3.1a.81.81 0 0 1-.833.833.81.81 0 0 1-.833-.833v-3.1q0-2.109 1.429-3.538Q4.876 10.1 6.984 10.1h7.142l-3.308-3.325a.81.81 0 0 1-.25-.596q0-.346.25-.596a.82.82 0 0 1 .587-.241.8.8 0 0 1 .58.241l4.766 4.767q.133.133.187.275a.9.9 0 0 1 .055.308.9.9 0 0 1-.055.309.8.8 0 0 1-.187.275L12 16.267a.8.8 0 0 1-.592.245.84.84 0 0 1-.591-.245.85.85 0 0 1-.263-.584.76.76 0 0 1 .238-.583z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/gif-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      fillRule='evenodd'\n      d='M4.2 4.8a2.4 2.4 0 0 0-2.4 2.4v9.6a2.4 2.4 0 0 0 2.4 2.4h15.6a2.4 2.4 0 0 0 2.4-2.4V7.2a2.4 2.4 0 0 0-2.4-2.4zm7.644 9.785a.54.54 0 0 0 .399.158.54.54 0 0 0 .557-.557v-4.029a.54.54 0 0 0-.557-.557.54.54 0 0 0-.557.557v4.029a.54.54 0 0 0 .158.399m-5.632-.067q.213.225.56.225H9.4a.73.73 0 0 0 .56-.225.77.77 0 0 0 .211-.547V12.73a.54.54 0 0 0-.557-.558.54.54 0 0 0-.557.558v.9H7.114v-2.915h2.5a.54.54 0 0 0 .557-.557.54.54 0 0 0-.557-.557H6.771a.73.73 0 0 0-.559.225.77.77 0 0 0-.212.546v3.6q0 .322.212.547m8.203.067a.54.54 0 0 0 .4.158.54.54 0 0 0 .556-.557V12.97h1.172a.54.54 0 0 0 .557-.557.54.54 0 0 0-.557-.557H15.37v-1.143h2.072a.54.54 0 0 0 .557-.557.54.54 0 0 0-.557-.557h-2.629a.54.54 0 0 0-.557.557v4.029a.54.54 0 0 0 .158.399'\n      clipRule='evenodd'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/gif.tsx",
    "content": "import Svg, { Path, Rect } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.243 14.743a.54.54 0 0 1-.557-.557v-4.029a.54.54 0 0 1 .557-.557.54.54 0 0 1 .557.557v4.029a.54.54 0 0 1-.557.557m-5.472 0a.73.73 0 0 1-.559-.225A.77.77 0 0 1 6 13.97v-3.6q0-.321.212-.546a.73.73 0 0 1 .56-.225h2.842a.54.54 0 0 1 .557.557.54.54 0 0 1-.557.557h-2.5v2.915h1.943v-.9a.54.54 0 0 1 .557-.558.54.54 0 0 1 .557.558v1.242q0 .322-.212.547a.73.73 0 0 1-.559.225zm8.043 0a.54.54 0 0 1-.557-.557v-4.029a.54.54 0 0 1 .557-.557h2.629a.54.54 0 0 1 .557.557.54.54 0 0 1-.557.557H15.37v1.143h1.172a.54.54 0 0 1 .557.557.54.54 0 0 1-.557.557H15.37v1.215a.54.54 0 0 1-.557.557'\n    />\n    <Rect width={19.2} height={13.2} x={2.401} y={5.4} stroke={color} strokeWidth={1.2} rx={1.8} />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/graphic-eq-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M7.375 16.808V7.192q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .534.216a.73.73 0 0 1 .216.534v9.616q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534m3.875 3.942V3.25q0-.318.216-.534A.73.73 0 0 1 12 2.5q.32 0 .534.216a.73.73 0 0 1 .216.534v17.5q0 .318-.216.534A.73.73 0 0 1 12 21.5a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534M3.5 12.904v-1.808q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v1.808q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534m11.625 3.904V7.192q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v9.616q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534M19 12.904v-1.808q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v1.808q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/graphic-eq.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M7.375 16.808V7.192q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .534.216a.73.73 0 0 1 .216.534v9.616q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534m3.875 3.942V3.25q0-.318.216-.534A.73.73 0 0 1 12 2.5q.32 0 .534.216a.73.73 0 0 1 .216.534v17.5q0 .318-.216.534A.73.73 0 0 1 12 21.5a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534M3.5 12.904v-1.808q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v1.808q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534m11.625 3.904V7.192q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v9.616q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534M19 12.904v-1.808q0-.318.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v1.808q0 .318-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/group-add.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.596 11.642q.638-.694.944-1.594t.306-1.856-.306-1.856a4.4 4.4 0 0 0-.944-1.594q1.317.151 2.187 1.138t.87 2.312q0 1.327-.87 2.313a3.33 3.33 0 0 1-2.187 1.137m4.863 7.666q.188-.345.288-.733.099-.389.099-.787v-.827q0-.817-.333-1.556a3.6 3.6 0 0 0-.944-1.267q1.15.384 2.117 1.033.967.65.967 1.79v.827q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm2.194-8.558h-1.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h1.25V8q0-.319.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v1.25h1.25q.319 0 .535.216a.73.73 0 0 1 .215.534q0 .319-.215.534a.73.73 0 0 1-.535.216h-1.25V12q0 .318-.215.534a.73.73 0 0 1-.535.216.72.72 0 0 1-.534-.216.73.73 0 0 1-.216-.534zm-11.307.942q-1.444 0-2.472-1.028T4.846 8.192 5.874 5.72t2.472-1.028 2.472 1.028 1.028 2.472-1.028 2.472-2.472 1.028m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.991-1.09 12.95 12.95 0 0 1 6.087 0q1.509.364 2.991 1.09.667.337 1.066.963.4.625.4 1.36v.704q0 .633-.444 1.076a1.47 1.47 0 0 1-1.075.444H2.364q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/group-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M1.797 17.085q0-.774.399-1.38a2.7 2.7 0 0 1 1.066-.944q1.426-.697 2.866-1.075t3.169-.378 3.168.377 2.867 1.076q.666.338 1.066.944.399.606.399 1.38v.703q0 .605-.444 1.062a1.44 1.44 0 0 1-1.076.458H3.316q-.633 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076zm16.613 2.223q.189-.345.288-.733t.099-.787v-.826q0-.985-.482-1.877a4.54 4.54 0 0 0-1.368-1.531q1.005.15 1.91.464.903.315 1.724.743.775.414 1.197.975t.422 1.226v.826q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444zm-9.113-7.616q-1.444 0-2.472-1.028T5.797 8.192q0-1.443 1.028-2.471t2.472-1.029 2.472 1.029 1.028 2.471-1.028 2.472q-1.029 1.028-2.472 1.028m8.634-3.5q0 1.444-1.028 2.472t-2.472 1.028a3 3 0 0 1-.43-.038 4 4 0 0 1-.431-.085q.59-.711.91-1.578a5.2 5.2 0 0 0-.007-3.593 5.8 5.8 0 0 0-.903-1.582q.215-.078.43-.1.216-.024.431-.024 1.444 0 2.472 1.029t1.028 2.471'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/group.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M1.21 17.381q0-.85.431-1.54.43-.69 1.2-1.04 1.72-.776 3.2-1.137 1.478-.36 3.024-.36t3.013.36q1.468.361 3.186 1.136.771.35 1.211 1.04t.44 1.541v.819q0 .653-.465 1.118t-1.118.465H2.794q-.66 0-1.122-.465a1.53 1.53 0 0 1-.461-1.118zm20 2.402h-3.1q.159-.383.274-.77.114-.386.114-.813v-.817q0-1.41-.654-2.384-.654-.975-1.979-1.628 1.537.191 2.89.535 1.351.344 2.29.831.82.463 1.285 1.121.464.66.464 1.473v.865q0 .66-.465 1.123-.465.464-1.118.464M9.066 12q-1.62 0-2.677-1.056T5.332 8.267 6.388 5.59t2.677-1.057q1.62 0 2.677 1.057t1.056 2.677-1.056 2.677Q10.685 12 9.065 12m9.033-3.743q0 1.61-1.056 2.67t-2.677 1.06q-.255 0-.596-.042a3 3 0 0 1-.604-.13 4.4 4.4 0 0 0 .944-1.565 5.9 5.9 0 0 0 .323-1.986q0-1.08-.323-1.962a5.2 5.2 0 0 0-.944-1.602q.271-.084.596-.126a5 5 0 0 1 .604-.04q1.62 0 2.677 1.059t1.056 2.664M2.794 18.2h12.538v-.815q0-.364-.209-.69a1.3 1.3 0 0 0-.512-.474q-1.655-.755-2.9-1.044-1.246-.29-2.642-.29-1.4 0-2.662.29-1.262.289-2.909 1.044-.312.15-.508.476-.195.327-.196.686zm6.27-7.783q.922 0 1.536-.613.615-.614.615-1.536t-.613-1.537-1.535-.614-1.537.613-.615 1.535q0 .922.613 1.537t1.535.615'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/help-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.989 17.615a.98.98 0 0 0 .723-.295.99.99 0 0 0 .296-.724.98.98 0 0 0-.296-.724.99.99 0 0 0-.724-.295.98.98 0 0 0-.723.296.99.99 0 0 0-.296.724q0 .428.296.723a.99.99 0 0 0 .724.295m.013 3.885a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749m.079-13.78q.691 0 1.198.435.506.435.506 1.09 0 .549-.324.984-.323.435-.738.81a6.4 6.4 0 0 0-.959 1.062 2.34 2.34 0 0 0-.443 1.291.56.56 0 0 0 .193.458.68.68 0 0 0 .47.184.68.68 0 0 0 .489-.192.88.88 0 0 0 .256-.47 2.06 2.06 0 0 1 .426-.919 8 8 0 0 1 .706-.766q.547-.534.954-1.162.408-.63.408-1.404 0-1.188-.937-1.953-.936-.764-2.186-.764-.893 0-1.683.395a2.9 2.9 0 0 0-1.23 1.153.69.69 0 0 0-.088.49q.049.257.264.39.274.152.562.087a.85.85 0 0 0 .49-.328q.304-.394.736-.633.432-.24.93-.239'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/help.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.065 17.767q.42 0 .71-.29a.96.96 0 0 0 .291-.71.97.97 0 0 0-.29-.71.96.96 0 0 0-.709-.29.97.97 0 0 0-.71.29.96.96 0 0 0-.291.709q0 .42.29.71t.709.29m-.06 4.066a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.056 5.04 10 10 0 0 1 8.18 2.939a9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.1 3.122 9.6 9.6 0 0 1 .773 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836T12 3.75q-3.421 0-5.836 2.403T3.749 12q0 3.42 2.414 5.836 2.415 2.415 5.844 2.414m.072-12.633q.72 0 1.261.441.543.442.543 1.12 0 .612-.363 1.079-.364.465-.82.856a7.3 7.3 0 0 0-1.021 1.064q-.447.573-.434 1.314 0 .288.215.465a.77.77 0 0 0 .506.177.72.72 0 0 0 .525-.206.96.96 0 0 0 .273-.518q.084-.525.414-.937.33-.414.742-.768.6-.513.99-1.168.39-.657.39-1.43 0-1.242-.937-2.057-.935-.816-2.224-.816-.89 0-1.708.378a3.2 3.2 0 0 0-1.327 1.114.8.8 0 0 0-.148.546.6.6 0 0 0 .26.454.75.75 0 0 0 .61.114.88.88 0 0 0 .523-.36q.296-.417.755-.64.458-.222.975-.222'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/history-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.98 20.5q-3.015 0-5.315-1.84t-2.948-4.71a.62.62 0 0 1 .097-.548.7.7 0 0 1 .504-.29.78.78 0 0 1 .55.12.85.85 0 0 1 .34.476q.57 2.318 2.46 3.804Q9.558 19 11.98 19q2.925 0 4.963-2.038Q18.98 14.926 18.98 12t-2.037-4.962T11.98 5a6.7 6.7 0 0 0-3.07.728 7.6 7.6 0 0 0-2.468 2.003h1.865q.319 0 .534.216a.73.73 0 0 1 .216.534q0 .319-.216.534a.73.73 0 0 1-.534.216H4.884a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V4.904q0-.319.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v1.619a8.5 8.5 0 0 1 2.916-2.23A8.4 8.4 0 0 1 11.98 3.5q1.77 0 3.316.67a8.6 8.6 0 0 1 2.696 1.819 8.6 8.6 0 0 1 1.819 2.695q.67 1.546.67 3.316t-.67 3.315a8.6 8.6 0 0 1-1.82 2.697 8.6 8.6 0 0 1-2.695 1.819 8.3 8.3 0 0 1-3.316.669m.779-8.804 2.75 2.75q.208.208.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217l-2.925-2.925a.9.9 0 0 1-.27-.648V7.75q-.001-.318.215-.535A.73.73 0 0 1 12.009 7q.319 0 .535.215a.73.73 0 0 1 .215.535z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/history.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.944 20.85q-3.36 0-5.819-2.202t-2.883-5.474a.67.67 0 0 1 .167-.567.76.76 0 0 1 .559-.269q.315-.017.555.187.239.204.304.53.377 2.662 2.378 4.437 2 1.775 4.739 1.775 3.052 0 5.185-2.146t2.133-5.2q0-3.017-2.15-5.102t-5.183-2.086a6.66 6.66 0 0 0-3.146.769 8.2 8.2 0 0 0-2.52 2.048h1.82q.338 0 .567.23a.76.76 0 0 1 .23.557.77.77 0 0 1-.23.567.77.77 0 0 1-.567.23H4.367a.777.777 0 0 1-.788-.796V4.633q0-.326.225-.555a.76.76 0 0 1 .563-.229q.338 0 .566.23a.76.76 0 0 1 .23.557v1.746a9.1 9.1 0 0 1 3.026-2.375 8.4 8.4 0 0 1 3.74-.858q1.842 0 3.466.69a9 9 0 0 1 2.835 1.885 8.9 8.9 0 0 1 1.914 2.797 8.5 8.5 0 0 1 .702 3.444 8.6 8.6 0 0 1-.702 3.461 9 9 0 0 1-1.91 2.823 9 9 0 0 1-2.828 1.902 8.65 8.65 0 0 1-3.462.698m.885-9.202 2.72 2.688a.78.78 0 0 1 .243.572q0 .33-.242.567a.773.773 0 0 1-1.118-.005l-2.952-2.936a.77.77 0 0 1-.234-.572V7.822q0-.329.225-.558a.76.76 0 0 1 .562-.23q.338 0 .567.23a.76.76 0 0 1 .23.558z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/image-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#F6AD00'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G fill='#fff' filter='url(#prefix__b)'>\n      <Path d='m9.96 21.991 2.43-3.571a.376.376 0 0 1 .578-.057l1.605 1.573 3.524-5.522a.378.378 0 0 1 .646.014l4.378 7.587a.378.378 0 0 1-.328.567h-12.52a.378.378 0 0 1-.313-.59M10.688 13.905a1.905 1.905 0 1 0 3.81 0 1.905 1.905 0 0 0-3.81 0' />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#FBDE99' d='M22.267 9.333h4.534l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/incoming-audio-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 20 20' fill='none'>\n    <Path\n      fill={color}\n      d='M16.2005 17.0837C14.6301 17.0837 13.0524 16.7185 11.4674 15.9882C9.88255 15.258 8.42505 14.2278 7.09491 12.8978C5.77019 11.5677 4.74269 10.1114 4.01241 8.52908C3.28213 6.94685 2.91699 5.37053 2.91699 3.80012C2.91699 3.55012 3.00033 3.3404 3.16699 3.17095C3.33366 3.00165 3.54199 2.91699 3.79199 2.91699H6.50991C6.72033 2.91699 6.90595 2.98567 7.06678 3.12303C7.22762 3.26026 7.32991 3.42984 7.37366 3.63178L7.85137 6.08366C7.88442 6.31116 7.87748 6.50665 7.83053 6.67012C7.78345 6.83359 7.69901 6.97088 7.5772 7.08199L5.65262 8.95553C5.96234 9.52276 6.31623 10.0594 6.71428 10.5653C7.1122 11.0712 7.54303 11.5543 8.00678 12.0147C8.46401 12.4721 8.95012 12.8968 9.46512 13.2889C9.98012 13.681 10.5362 14.0458 11.1335 14.3835L13.0035 12.4972C13.1339 12.3615 13.2918 12.2664 13.4772 12.2118C13.6625 12.1573 13.855 12.144 14.0549 12.1718L16.3689 12.643C16.5793 12.6986 16.751 12.806 16.8841 12.9651C17.0171 13.1243 17.0837 13.3048 17.0837 13.5068V16.2087C17.0837 16.4587 16.999 16.667 16.8297 16.8337C16.6603 17.0003 16.4505 17.0837 16.2005 17.0837ZM12.9297 7.94908H14.7599C14.9373 7.94908 15.0857 8.00894 15.2053 8.12866C15.325 8.24824 15.3849 8.39671 15.3849 8.57408C15.3849 8.75144 15.325 8.89991 15.2053 9.01949C15.0857 9.13921 14.9373 9.19908 14.7599 9.19908H11.5547C11.34 9.19908 11.1608 9.1272 11.0172 8.98345C10.8735 8.83984 10.8016 8.66067 10.8016 8.44595V5.24074C10.8016 5.06338 10.8614 4.91491 10.9812 4.79533C11.1007 4.6756 11.2492 4.61574 11.4266 4.61574C11.6039 4.61574 11.7524 4.6756 11.872 4.79533C11.9917 4.91491 12.0516 5.06338 12.0516 5.24074V7.07095L15.9714 3.15095C16.0868 3.03553 16.2319 2.97651 16.4066 2.97387C16.5813 2.97123 16.729 3.03026 16.8497 3.15095C16.9704 3.27164 17.0307 3.41803 17.0307 3.59012C17.0307 3.76206 16.9704 3.90845 16.8497 4.02928L12.9297 7.94908Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/incoming-audio.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 21 20' fill='none'>\n    <Path\n      fill={color}\n      d='M13.4297 7.95005H15.2599C15.437 7.95005 15.5855 8.00998 15.7053 8.12984C15.825 8.24971 15.8849 8.39818 15.8849 8.57526C15.8849 8.75248 15.825 8.90089 15.7053 9.02047C15.5855 9.14019 15.437 9.20005 15.2599 9.20005H12.0547C11.8414 9.20005 11.6625 9.12783 11.5182 8.98339C11.3738 8.83908 11.3016 8.66026 11.3016 8.44693V5.24172C11.3016 5.06464 11.3615 4.91616 11.4814 4.7963C11.6012 4.67658 11.7497 4.61672 11.9268 4.61672C12.104 4.61672 12.2524 4.67658 12.372 4.7963C12.4917 4.91616 12.5516 5.06464 12.5516 5.24172V7.07193L16.4714 3.15193C16.5868 3.03651 16.7319 2.97748 16.9066 2.97484C17.0813 2.9722 17.229 3.03123 17.3497 3.15193C17.4704 3.27262 17.5307 3.41901 17.5307 3.59109C17.5307 3.76304 17.4704 3.90943 17.3497 4.03026L13.4297 7.95005ZM16.7005 17.0846C15.1301 17.0846 13.5524 16.7195 11.9674 15.9892C10.3825 15.2589 8.92637 14.2288 7.59887 12.8988C6.27151 11.5687 5.24269 10.1124 4.51241 8.53005C3.78213 6.94783 3.41699 5.37151 3.41699 3.80109C3.41699 3.54873 3.50033 3.33845 3.66699 3.17026C3.83366 3.00207 4.04199 2.91797 4.29199 2.91797H7.00991C7.22033 2.91797 7.40595 2.98665 7.56678 3.12401C7.72762 3.26123 7.82991 3.43082 7.87366 3.63276L8.35137 6.08464C8.38442 6.31214 8.37748 6.50762 8.33053 6.67109C8.28345 6.83457 8.19901 6.97186 8.0772 7.08297L6.15262 8.95651C6.46234 9.52373 6.81623 10.0603 7.21428 10.5663C7.6122 11.0721 8.04303 11.5553 8.50678 12.0157C8.96401 12.473 9.45012 12.8978 9.96512 13.2898C10.4801 13.6819 11.0362 14.0468 11.6335 14.3844L13.5035 12.4982C13.6339 12.3625 13.7918 12.2673 13.9772 12.2128C14.1625 12.1583 14.355 12.145 14.5549 12.1728L16.8689 12.644C17.0793 12.6996 17.251 12.8069 17.3841 12.9661C17.5171 13.1253 17.5837 13.3058 17.5837 13.5078V16.2096C17.5837 16.4596 17.4996 16.668 17.3314 16.8346C17.1632 17.0013 16.9529 17.0846 16.7005 17.0846ZM5.56116 7.7738L7.04845 6.35068C7.07512 6.32929 7.09248 6.29991 7.10053 6.26255C7.10859 6.22519 7.10727 6.19047 7.09658 6.15839L6.73428 4.29609C6.72359 4.25346 6.70491 4.22144 6.67824 4.20005C6.65158 4.17866 6.61685 4.16797 6.57408 4.16797H4.79199C4.75991 4.16797 4.73317 4.17866 4.71178 4.20005C4.69053 4.22144 4.67991 4.24818 4.67991 4.28026C4.72255 4.84971 4.81574 5.42818 4.95949 6.01568C5.1031 6.60332 5.30366 7.18936 5.56116 7.7738ZM12.8112 14.9757C13.3635 15.2332 13.9396 15.4301 14.5395 15.5663C15.1395 15.7024 15.7001 15.7833 16.2214 15.809C16.2535 15.809 16.2802 15.7983 16.3016 15.7769C16.323 15.7555 16.3337 15.7288 16.3337 15.6967V13.9436C16.3337 13.9008 16.323 13.8661 16.3016 13.8394C16.2802 13.8128 16.2482 13.7941 16.2055 13.7834L14.4555 13.4276C14.4235 13.4169 14.3954 13.4155 14.3714 13.4236C14.3473 13.4317 14.3219 13.449 14.2951 13.4757L12.8112 14.9757Z'\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/incoming-video-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        fillRule='evenodd'\n        d='M3.265 18.975q.525.525 1.283.525h11.385q.757 0 1.282-.525t.525-1.283v-4.577l2.746 2.746q.221.222.497.111.276-.11.276-.422v-7.1q0-.311-.276-.422t-.497.11l-2.746 2.747V6.308q0-.758-.525-1.283a1.75 1.75 0 0 0-1.283-.525H4.548q-.758 0-1.283.525T2.74 6.308v11.384q0 .758.525 1.283m7.485-5.005H8.554l4.704-4.703a.72.72 0 0 0 .217-.527.72.72 0 0 0-.217-.527.7.7 0 0 0-.532-.213.73.73 0 0 0-.522.213L7.5 12.917V10.72a.73.73 0 0 0-.215-.534.73.73 0 0 0-.535-.216.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534v3.847q0 .384.26.643.26.26.644.26h3.846q.319 0 .534-.215a.73.73 0 0 0 .216-.534.73.73 0 0 0-.216-.535.73.73 0 0 0-.534-.216'\n        clipRule='evenodd'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/incoming-video.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.78 19.85q-.64 0-1.111-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112A1.52 1.52 0 0 1 3.78 4.15h12.536q.629 0 1.105.471.475.471.475 1.112v5.1L21.131 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.105.47zm0-1.583h12.534V5.733H3.781z'\n    />\n    <Path\n      fill={color}\n      d='M8.555 13.97h2.196q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215H6.905a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.643V10.72q0-.318.216-.534a.73.73 0 0 1 .534-.216q.319 0 .534.216a.73.73 0 0 1 .216.534v2.197l4.703-4.704A.73.73 0 0 1 12.728 8a.7.7 0 0 1 .531.213q.218.216.218.527a.72.72 0 0 1-.218.527z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/info-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 16.75q.32 0 .534-.216A.73.73 0 0 0 12.75 16v-4.25a.73.73 0 0 0-.216-.534A.73.73 0 0 0 12 11a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534V16q0 .318.216.534a.73.73 0 0 0 .534.216m0-7.461q.343 0 .575-.233a.78.78 0 0 0 .233-.575.78.78 0 0 0-.232-.576.78.78 0 0 0-.576-.232.78.78 0 0 0-.575.232.78.78 0 0 0-.233.576q0 .343.232.575A.78.78 0 0 0 12 9.29m.002 12.211a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/info.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.063 16.917q.33 0 .559-.232a.77.77 0 0 0 .23-.564v-4.317a.76.76 0 0 0-.234-.556.76.76 0 0 0-.557-.231.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v4.317q0 .333.232.565a.77.77 0 0 0 .563.23m-.064-7.75q.36 0 .606-.24a.8.8 0 0 0 .246-.591.85.85 0 0 0-.243-.623.82.82 0 0 0-.604-.246.83.83 0 0 0-.611.243.84.84 0 0 0-.242.616q0 .36.244.6t.604.24m.008 12.666a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.058 5.04a10 10 0 0 1 3.125-2.102 9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.121 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836-5.847-2.406q-3.421 0-5.836 2.403T3.75 12q0 3.42 2.414 5.836Q8.58 20.25 12.01 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keep-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M15.5 5v7.211l1.565 1.566a.75.75 0 0 1 .223.539v.427q0 .317-.219.537a.73.73 0 0 1-.54.22h-3.78v5.389q0 .318-.215.534a.73.73 0 0 1-.535.215.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.534V15.5H7.471a.73.73 0 0 1-.542-.22.73.73 0 0 1-.218-.537v-.427a.77.77 0 0 1 .223-.539L8.5 12.212V5h-.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534A.73.73 0 0 1 8.25 3.5h7.5q.32 0 .535.216a.73.73 0 0 1 .215.534q0 .32-.215.535a.73.73 0 0 1-.535.215z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keep-off-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M14.185 11.36 7.57 4.744a.7.7 0 0 1-.166-.238.7.7 0 0 1-.048-.262q0-.284.192-.514a.65.65 0 0 1 .528-.23h7.675q.318 0 .534.217a.73.73 0 0 1 .216.536q0 .3-.262.44a4 4 0 0 0-.488.307h-.25v5.808q0 .518-.473.72-.474.201-.843-.168M11.251 21v-5.5H8.18q-.551 0-.882-.385a1.28 1.28 0 0 1-.33-.857q0-.237.094-.469t.304-.443l1.135-1.135V10.64L2.658 4.765a.74.74 0 0 1-.22-.514.7.7 0 0 1 .222-.541.71.71 0 0 1 .525-.216q.31 0 .527.218L19.89 19.889a.71.71 0 0 1 .22.53.787.787 0 0 1-.763.764.7.7 0 0 1-.526-.226L13.362 15.5h-.611V21q0 .318-.216.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keep-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M14.218 4.717H9.684v3.141L5.966 4.14a.54.54 0 0 1-.13-.188.6.6 0 0 1-.035-.221q0-.232.151-.415.15-.183.437-.183h9.458q.324 0 .556.232t.232.556a.54.54 0 0 1-.265.49q-.264.164-.523.306h-.046v7.5a.75.75 0 0 1-.234.56.78.78 0 0 1-.56.227.77.77 0 0 1-.558-.227.76.76 0 0 1-.231-.56zm-3.004 17.22v-5.904H6.95q-.459 0-.727-.313a1.03 1.03 0 0 1-.269-.686q0-.18.07-.363.071-.182.239-.35L8.1 12.546v-1.83l-5.97-5.978a.75.75 0 0 1 0-1.105.74.74 0 0 1 .547-.241q.315 0 .559.243L20.422 20.83a.78.78 0 0 1-.004 1.11.75.75 0 0 1-.552.234.77.77 0 0 1-.555-.242l-5.91-5.898h-.604v5.905q0 .333-.232.564a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.23.78.78 0 0 1-.224-.565M8.318 14.45h3.496l-2.13-2.13v.847z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keep.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M15.8 4.717v7.833l1.827 1.665a.8.8 0 0 1 .18.274.8.8 0 0 1 .06.305v.453a.76.76 0 0 1-.233.555.76.76 0 0 1-.555.231h-4.284v5.771q0 .333-.232.565a.77.77 0 0 1-.566.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.225-.565v-5.77H6.93a.77.77 0 0 1-.565-.233.76.76 0 0 1-.231-.557v-.453a.8.8 0 0 1 .058-.303q.06-.15.188-.271l1.72-1.667V4.717h-.37a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.563q0-.33.231-.56a.77.77 0 0 1 .565-.229h8.45q.323 0 .555.233a.76.76 0 0 1 .232.558q0 .334-.232.564a.76.76 0 0 1-.555.229zM8.316 14.45h7.267l-1.367-1.28V4.717H9.683v8.45z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keyboard-arrow-down-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 14.677a.832.832 0 0 1-.633-.256L6.873 9.927a.73.73 0 0 1-.213-.522.7.7 0 0 1 .213-.532.72.72 0 0 1 .527-.217q.31 0 .527.217L12 12.946l4.073-4.073a.73.73 0 0 1 .522-.212.7.7 0 0 1 .532.212q.217.217.217.527a.72.72 0 0 1-.217.527l-4.494 4.494a.83.83 0 0 1-.633.256'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keyboard-arrow-down.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 14.996a.8.8 0 0 1-.298-.057.75.75 0 0 1-.256-.185L6.66 9.971a.72.72 0 0 1-.23-.563.78.78 0 0 1 .251-.558.8.8 0 0 1 .565-.23q.322 0 .552.23l4.2 4.217 4.217-4.217a.71.71 0 0 1 .543-.22q.315.008.557.237a.75.75 0 0 1 .243.558.76.76 0 0 1-.23.562l-4.768 4.767a.8.8 0 0 1-.264.185.8.8 0 0 1-.298.057'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keyboard-arrow-up-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 10.454-4.072 4.073a.73.73 0 0 1-.523.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.218-.527l4.494-4.494A.87.87 0 0 1 12 8.708q.36 0 .632.27l4.495 4.495q.207.208.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keyboard-arrow-up.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12 10.333 7.784 14.55a.71.71 0 0 1-.545.22.82.82 0 0 1-.556-.237.75.75 0 0 1-.244-.558.78.78 0 0 1 .241-.572l4.765-4.764A.76.76 0 0 1 12 8.4q.317 0 .562.238l4.783 4.783a.78.78 0 0 1 .231.562.74.74 0 0 1-.244.55.8.8 0 0 1-.564.23.75.75 0 0 1-.553-.23z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keyboard-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M4.308 18.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V7.308q0-.758.525-1.283T4.308 5.5h15.384q.758 0 1.283.525t.525 1.283v9.384q0 .758-.525 1.283t-1.283.525zM9 15.884h6a.86.86 0 0 0 .626-.253.85.85 0 0 0 .258-.631.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258H9a.86.86 0 0 0-.626.253.85.85 0 0 0-.258.631q0 .368.258.626a.85.85 0 0 0 .626.258m-3-6a.85.85 0 0 0 .626-.258A.85.85 0 0 0 6.884 9a.85.85 0 0 0-.258-.626A.85.85 0 0 0 6 8.116a.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626A.85.85 0 0 0 6 9.884m3 0a.85.85 0 0 0 .626-.258A.85.85 0 0 0 9.884 9a.85.85 0 0 0-.258-.626A.85.85 0 0 0 9 8.116a.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626A.85.85 0 0 0 9 9.884m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m-12 3a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258m3 0a.85.85 0 0 0 .626-.258.85.85 0 0 0 .258-.626.85.85 0 0 0-.258-.626.85.85 0 0 0-.626-.258.85.85 0 0 0-.626.258.85.85 0 0 0-.258.626q0 .368.258.626a.85.85 0 0 0 .626.258'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/keyboard.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.634 18.917q-.64 0-1.112-.48-.471-.48-.471-1.104V6.583q0-.64.471-1.112a1.52 1.52 0 0 1 1.112-.47h16.75q.64 0 1.112.47.471.472.472 1.112v10.75q0 .624-.472 1.104-.471.48-1.112.48zm0-1.584h16.75V6.583H3.634zm4.846-1.5h7.05a.76.76 0 0 0 .556-.232.77.77 0 0 0 .232-.562.76.76 0 0 0-.233-.56.76.76 0 0 0-.555-.229H8.48a.77.77 0 0 0-.565.233.76.76 0 0 0-.231.558q0 .334.231.563a.77.77 0 0 0 .565.23M5.895 9.667q.33 0 .56-.232a.77.77 0 0 0 .229-.563.76.76 0 0 0-.233-.56.77.77 0 0 0-.557-.229.76.76 0 0 0-.564.233.77.77 0 0 0-.23.558q0 .334.233.564.232.228.562.229m3.067 0q.33 0 .56-.232a.77.77 0 0 0 .229-.563.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.229.76.76 0 0 0-.563.233.77.77 0 0 0-.23.558q0 .334.233.564a.77.77 0 0 0 .562.229m3.05 0q.33 0 .56-.232a.77.77 0 0 0 .229-.563.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.229.76.76 0 0 0-.563.233.77.77 0 0 0-.23.558q0 .334.232.564a.77.77 0 0 0 .563.229m3.067 0q.33 0 .56-.232a.77.77 0 0 0 .229-.563.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.229.76.76 0 0 0-.564.233.77.77 0 0 0-.229.558q0 .334.232.564a.77.77 0 0 0 .563.229m3.05 0q.33 0 .56-.232a.77.77 0 0 0 .229-.563.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.229.76.76 0 0 0-.564.233.77.77 0 0 0-.229.558q0 .334.232.564a.77.77 0 0 0 .563.229M5.895 12.75q.33 0 .56-.232a.77.77 0 0 0 .229-.562.76.76 0 0 0-.233-.56.77.77 0 0 0-.557-.23.76.76 0 0 0-.564.234.77.77 0 0 0-.23.557q0 .335.233.564.232.23.562.23m3.067 0q.33 0 .56-.232a.77.77 0 0 0 .229-.562.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.23.76.76 0 0 0-.563.234.77.77 0 0 0-.23.557q0 .335.233.564a.77.77 0 0 0 .562.23m3.05 0q.33 0 .56-.232a.77.77 0 0 0 .229-.562.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.23.76.76 0 0 0-.563.234.77.77 0 0 0-.23.557q0 .335.232.564a.77.77 0 0 0 .563.23m3.067 0q.33 0 .56-.232a.77.77 0 0 0 .229-.562.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.23.76.76 0 0 0-.564.234.77.77 0 0 0-.229.557.76.76 0 0 0 .232.564.77.77 0 0 0 .563.23m3.05 0q.33 0 .56-.232a.77.77 0 0 0 .229-.562.76.76 0 0 0-.233-.56.77.77 0 0 0-.558-.23.76.76 0 0 0-.564.234.77.77 0 0 0-.229.557.76.76 0 0 0 .232.564.77.77 0 0 0 .563.23'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/language-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 21.5a9.2 9.2 0 0 1-3.687-.749 9.6 9.6 0 0 1-3.025-2.04 9.6 9.6 0 0 1-2.039-3.023A9.2 9.2 0 0 1 2.5 12q0-1.97.749-3.695a9.7 9.7 0 0 1 2.04-3.016 9.6 9.6 0 0 1 3.024-2.04A9.2 9.2 0 0 1 12 2.5q1.97 0 3.695.749 1.725.75 3.017 2.04 1.29 1.29 2.039 3.016T21.5 12q0 1.954-.749 3.688a9.6 9.6 0 0 1-2.04 3.024 9.7 9.7 0 0 1-3.016 2.039q-1.725.75-3.695.749m0-1.521q.765-1.016 1.289-2.048.522-1.033.851-2.258H9.86q.348 1.263.861 2.296.514 1.034 1.279 2.01m-1.937-.275a11 11 0 0 1-1.032-1.876 12.3 12.3 0 0 1-.712-2.155H4.927a7.8 7.8 0 0 0 2.125 2.618 7.14 7.14 0 0 0 3.012 1.413m3.874 0a7.14 7.14 0 0 0 3.011-1.413 7.8 7.8 0 0 0 2.125-2.618h-3.392a16 16 0 0 1-.76 2.164q-.457 1.051-.984 1.867m-9.639-5.531h3.717A12.5 12.5 0 0 1 7.836 12q0-.545.043-1.08t.136-1.093H4.298q-.144.51-.221 1.06a8 8 0 0 0 0 2.226q.077.55.221 1.06m5.217 0h4.97A12.7 12.7 0 0 0 14.664 12q0-.564-.043-1.09-.043-.525-.136-1.083h-4.97A12.6 12.6 0 0 0 9.336 12q0 .564.043 1.09.043.525.136 1.083m6.47 0h3.717q.144-.51.221-1.06a8 8 0 0 0 0-2.226 8 8 0 0 0-.221-1.06h-3.718a12.5 12.5 0 0 1 .18 2.173q0 .545-.043 1.08t-.136 1.093m-.304-5.846h3.392a7.8 7.8 0 0 0-2.11-2.617 7.4 7.4 0 0 0-3.027-1.423q.576.872 1.024 1.91.448 1.035.72 2.13m-5.821 0h4.28a13 13 0 0 0-.876-2.31A11.7 11.7 0 0 0 12 4.02q-.736.938-1.264 1.995a13 13 0 0 0-.876 2.311m-4.933 0h3.392q.274-1.095.721-2.13.449-1.037 1.024-1.91-1.728.382-3.031 1.428a7.9 7.9 0 0 0-2.106 2.612'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/language.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.995 21.833a9.7 9.7 0 0 1-3.845-.77 9.9 9.9 0 0 1-3.127-2.105 9.8 9.8 0 0 1-2.093-3.136 9.8 9.8 0 0 1-.762-3.85q0-2.064.762-3.847a9.8 9.8 0 0 1 2.093-3.112A9.7 9.7 0 0 1 8.15 2.926a9.8 9.8 0 0 1 3.845-.76q2.064 0 3.856.76a9.8 9.8 0 0 1 3.124 2.088 9.8 9.8 0 0 1 2.096 3.112q.764 1.785.763 3.84 0 2.055-.763 3.856a9.8 9.8 0 0 1-2.096 3.136 9.9 9.9 0 0 1-3.124 2.105q-1.792.77-3.856.77m.006-1.516q.816-.912 1.368-2.025t.912-2.559H9.747q.325 1.38.886 2.517.561 1.14 1.368 2.067m-2.183-.35a13 13 0 0 1-1.006-1.965 14 14 0 0 1-.7-2.269H4.613q.866 1.62 2.079 2.602 1.212.982 3.125 1.632m4.383-.017q1.695-.541 3.03-1.619a8.6 8.6 0 0 0 2.166-2.598h-3.481a16 16 0 0 1-.713 2.252 12.5 12.5 0 0 1-1.002 1.965m-10.154-5.8h3.754q-.075-.63-.096-1.15a26 26 0 0 1-.02-1.033q0-.58.029-1.067t.104-1.067H4.047a7 7 0 0 0-.233 1.054 8 8 0 0 0-.063 1.08q0 .603.063 1.104.063.5.233 1.079m5.383 0h5.167q.096-.696.125-1.18a17 17 0 0 0 0-1.974q-.03-.467-.125-1.163H9.43q-.104.696-.133 1.163t-.029.97q0 .521.029 1.005t.133 1.179m6.755 0h3.779q.162-.579.225-1.08.062-.499.062-1.103 0-.605-.062-1.08a8 8 0 0 0-.225-1.054H16.2q.075.763.104 1.234.03.471.03.9 0 .528-.038 1.016-.037.488-.113 1.167m-.285-5.9h3.497a8 8 0 0 0-2.133-2.66 8.45 8.45 0 0 0-3.08-1.557q.585.913 1.003 1.931.419 1.02.713 2.286m-6.153 0h4.55a10.2 10.2 0 0 0-.882-2.43q-.598-1.168-1.414-2.103a7 7 0 0 0-1.293 1.833q-.536 1.071-.961 2.7m-5.133 0h3.514q.261-1.216.671-2.223T9.801 4.05q-1.746.483-3.058 1.535T4.613 8.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/link-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M7.039 16.539q-1.884 0-3.211-1.328Q2.5 13.884 2.5 12.001t1.328-3.212 3.21-1.328h3.02q.318 0 .534.216a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215h-3.02q-1.26 0-2.149.89A2.93 2.93 0 0 0 4 12q0 1.26.89 2.149.888.89 2.147.89h3.02q.32 0 .535.215a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215zM9 12.75a.73.73 0 0 1-.534-.216A.73.73 0 0 1 8.25 12q0-.32.216-.534A.73.73 0 0 1 9 11.25h6q.319 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.534a.73.73 0 0 1-.534.216zm4.942 3.788a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.319.216-.534a.73.73 0 0 1 .534-.215h3.02q1.26 0 2.148-.89.89-.89.89-2.149 0-1.26-.89-2.149a2.93 2.93 0 0 0-2.147-.89h-3.02a.73.73 0 0 1-.535-.215.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.216h3.02q1.883 0 3.21 1.328 1.328 1.328 1.328 3.21 0 1.884-1.328 3.211-1.327 1.328-3.21 1.328z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/link.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M7.048 16.883q-2.026 0-3.453-1.427T2.168 12t1.427-3.456 3.453-1.427h3.316q.323 0 .555.233.232.232.232.557a.76.76 0 0 1-.232.564.76.76 0 0 1-.555.229H7.05q-1.37 0-2.335.964a3.18 3.18 0 0 0-.965 2.333q0 1.37.965 2.336a3.18 3.18 0 0 0 2.335.967h3.313q.323 0 .555.233a.76.76 0 0 1 .232.557q0 .335-.232.564a.76.76 0 0 1-.555.23zm1.899-4.1a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.229h6.117q.323 0 .555.233a.76.76 0 0 1 .232.558q0 .334-.232.563a.76.76 0 0 1-.555.23zm4.7 4.1a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.229h3.309q1.369 0 2.332-.964a3.18 3.18 0 0 0 .963-2.333q0-1.37-.963-2.336a3.17 3.17 0 0 0-2.332-.967h-3.309a.77.77 0 0 1-.565-.232.77.77 0 0 1-.23-.562q0-.33.23-.56a.77.77 0 0 1 .565-.23h3.304q2.03 0 3.456 1.428Q21.836 9.97 21.835 12t-1.427 3.456-3.456 1.427z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/loading-fill.tsx",
    "content": "import Svg, { Circle, Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Circle cx={12} cy={12} r={11.25} fill={color} stroke='#A1A1A1' strokeWidth={1.5} />\n    <Path\n      fill='#fff'\n      d='M17.25 21.093a.75.75 0 0 0 .75 1.3zm5.144-4.788.693.287zm.473-7.217-.725.194zm-4.018-6.013.456-.595zM18 22.392a12 12 0 0 0 5.087-5.8l-1.386-.574a10.5 10.5 0 0 1-4.451 5.075zm5.087-5.8a12 12 0 0 0 .504-7.698l-1.449.388a10.5 10.5 0 0 1-.441 6.736zm.504-7.698a12 12 0 0 0-4.286-6.414l-.913 1.19a10.5 10.5 0 0 1 3.75 5.612zM19.305 2.48A12 12 0 0 0 12 0v1.5a10.5 10.5 0 0 1 6.392 2.17zM12 12.698l-3.19 3.19a.477.477 0 0 1-.699-.003.479.479 0 0 1 .002-.687L11.302 12 8.114 8.802a.46.46 0 0 1-.143-.34.5.5 0 0 1 .143-.347q.143-.152.344-.153.2 0 .351.145L12 11.302l3.196-3.195a.487.487 0 0 1 .698.008q.143.15.139.347a.48.48 0 0 1-.147.34L12.697 12l3.188 3.198a.47.47 0 0 1 .143.34.5.5 0 0 1-.143.347.46.46 0 0 1-.344.153.46.46 0 0 1-.346-.15z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/loading.tsx",
    "content": "import Svg, { Circle, Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 25 25'>\n    <Circle cx={12} cy={13} r={11} stroke={color} strokeWidth={2} opacity={0.2} />\n    <Path\n      fill={color}\n      d='M17.125 21.877a1 1 0 1 0 1 1.732zm5.269-4.572.924.383zm.473-7.217-.966.26zm-4.018-6.013.608-.794zm-.724 19.534a12.25 12.25 0 0 0 5.192-5.921l-1.847-.765a10.25 10.25 0 0 1-4.345 4.954zm5.192-5.921a12.25 12.25 0 0 0 .516-7.859l-1.932.518a10.25 10.25 0 0 1-.431 6.576zm.516-7.859a12.25 12.25 0 0 0-4.376-6.548L18.24 4.868a10.25 10.25 0 0 1 3.66 5.48zm-4.376-6.548A12.25 12.25 0 0 0 12 .75v2c2.256 0 4.45.745 6.24 2.118zM12 13.698l-3.19 3.19a.477.477 0 0 1-.699-.003.479.479 0 0 1 .002-.687L11.302 13 8.114 9.802a.46.46 0 0 1-.143-.34.5.5 0 0 1 .143-.347q.143-.151.344-.153.2 0 .351.145L12 12.302l3.196-3.195a.487.487 0 0 1 .698.008q.143.15.139.347a.48.48 0 0 1-.147.34L12.697 13l3.188 3.198a.47.47 0 0 1 .143.34.5.5 0 0 1-.143.347.46.46 0 0 1-.344.153.46.46 0 0 1-.346-.15z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/location-on-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 20.94q-.293 0-.585-.1a1.5 1.5 0 0 1-.53-.313 37 37 0 0 1-2.519-2.555 23 23 0 0 1-2.039-2.628 14 14 0 0 1-1.371-2.603q-.505-1.29-.505-2.493 0-3.462 2.24-5.605Q8.93 2.5 11.998 2.5t5.309 2.143q2.24 2.144 2.24 5.605-.001 1.201-.506 2.488a14.4 14.4 0 0 1-1.366 2.603 22 22 0 0 1-2.035 2.628 37 37 0 0 1-2.519 2.55 1.6 1.6 0 0 1-.53.317q-.296.106-.593.106m0-9.075q.747 0 1.277-.531.53-.532.53-1.278t-.532-1.276a1.75 1.75 0 0 0-1.278-.53q-.745 0-1.276.531-.53.532-.53 1.278t.532 1.277 1.278.53'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/location-on.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.001 19.75q3.188-2.938 4.719-5.323 1.53-2.385 1.531-4.206 0-2.843-1.806-4.655t-4.446-1.812-4.443 1.812-1.805 4.655q0 1.821 1.556 4.206t4.694 5.323m-.004 1.542q-.284 0-.56-.098a1.4 1.4 0 0 1-.494-.302 48 48 0 0 1-2.213-2.136 23.6 23.6 0 0 1-2.202-2.608 16 16 0 0 1-1.687-2.906q-.673-1.513-.673-3.021 0-3.68 2.364-5.866 2.365-2.184 5.47-2.184t5.468 2.184q2.364 2.185 2.364 5.866 0 1.508-.672 3.02a16 16 0 0 1-1.688 2.907q-1.014 1.393-2.202 2.608a48 48 0 0 1-2.213 2.136q-.216.203-.497.302a1.7 1.7 0 0 1-.565.098m.006-9.505a1.73 1.73 0 0 0 1.267-.525 1.72 1.72 0 0 0 .527-1.264q0-.74-.529-1.267a1.74 1.74 0 0 0-1.269-.527 1.7 1.7 0 0 0-1.262.53 1.74 1.74 0 0 0-.523 1.268q0 .74.525 1.262.525.523 1.264.523'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/lock-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.308 21.5a1.74 1.74 0 0 1-1.277-.531 1.74 1.74 0 0 1-.531-1.277v-9.384q0-.746.531-1.277A1.74 1.74 0 0 1 6.308 8.5H7.5v-2q0-1.873 1.313-3.186Q10.128 2 12 2t3.187 1.314Q16.5 4.626 16.5 6.5v2h1.192q.746 0 1.277.531t.531 1.277v9.384q0 .746-.531 1.277a1.74 1.74 0 0 1-1.277.531zM12 16.75q.729 0 1.24-.51.51-.511.51-1.24t-.51-1.24-1.24-.51-1.24.51q-.51.511-.51 1.24t.51 1.24 1.24.51M9 8.5h6v-2q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3.5q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6.5z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/lock-open-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.308 21.5a1.74 1.74 0 0 1-1.277-.531 1.74 1.74 0 0 1-.531-1.277v-9.384q0-.746.531-1.277A1.74 1.74 0 0 1 6.308 8.5H15v-2q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3.5q-1.085 0-1.898.686a3 3 0 0 0-1.03 1.702.78.78 0 0 1-.304.444.85.85 0 0 1-.514.168.74.74 0 0 1-.538-.213.53.53 0 0 1-.16-.5q.217-1.595 1.475-2.69Q10.288 2 12 2q1.873 0 3.187 1.314Q16.5 4.626 16.5 6.5v2h1.192q.746 0 1.277.531t.531 1.277v9.384q0 .746-.531 1.277a1.74 1.74 0 0 1-1.277.531zM12 16.75q.729 0 1.24-.51.51-.511.51-1.24t-.51-1.24-1.24-.51-1.24.51q-.51.511-.51 1.24t.51 1.24 1.24.51'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/lock-open.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.734 21.833q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118V9.783q0-.653.465-1.118T5.734 8.2h9.4V5.96q0-1.305-.91-2.215-.911-.912-2.227-.912a3.03 3.03 0 0 0-1.985.707A3.06 3.06 0 0 0 8.93 5.313a.8.8 0 0 1-.302.472.86.86 0 0 1-.539.182.71.71 0 0 1-.574-.259.71.71 0 0 1-.158-.597q.297-1.648 1.59-2.755T12 1.25q1.97 0 3.344 1.375t1.374 3.347V8.2h1.55q.654 0 1.118.465t.465 1.118V20.25q0 .654-.465 1.118t-1.118.465zm0-1.583h12.533V9.783H5.734zm6.268-3.333q.784 0 1.341-.548t.557-1.317q0-.754-.558-1.345a1.78 1.78 0 0 0-1.343-.59q-.784 0-1.341.589-.558.59-.558 1.351 0 .762.559 1.31.558.55 1.343.55'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/lock.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.732 21.833q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118V9.783q0-.653.465-1.118T5.732 8.2h1.55V5.968q0-1.97 1.375-3.344T12 1.25t3.341 1.375 1.373 3.347V8.2h1.55q.654 0 1.118.465t.465 1.118V20.25q0 .654-.465 1.118t-1.118.465zm0-1.583h12.533V9.783H5.732zM12 16.917q.785 0 1.341-.548.557-.548.557-1.317 0-.754-.558-1.345a1.78 1.78 0 0 0-1.343-.59q-.785 0-1.341.589-.558.59-.558 1.351 0 .762.559 1.31.558.55 1.343.55M8.865 8.2h6.267V5.96q0-1.305-.908-2.215-.907-.912-2.22-.912t-2.226.91q-.913.909-.913 2.22z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/logout-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h5.952q.318 0 .534.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.952q.318 0 .534.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215zm12.31-7.75H9.845a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h7.771l-1.923-1.923a.7.7 0 0 1-.212-.507.74.74 0 0 1 .212-.531.72.72 0 0 1 .527-.241.72.72 0 0 1 .543.225l3.094 3.094q.27.271.27.633 0 .361-.27.633l-3.094 3.094a.71.71 0 0 1-.53.22.75.75 0 0 1-.54-.236.73.73 0 0 1-.21-.534.74.74 0 0 1 .226-.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/logout.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h5.952q.318 0 .534.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215H5.308a.3.3 0 0 0-.212.096.3.3 0 0 0-.096.212v13.384q0 .116.096.212a.3.3 0 0 0 .212.096h5.952q.318 0 .534.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215zm12.31-7.75H9.845a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534a.73.73 0 0 1 .534-.216h7.771l-1.923-1.923a.7.7 0 0 1-.212-.507.74.74 0 0 1 .212-.531.72.72 0 0 1 .527-.241.72.72 0 0 1 .543.225l3.094 3.094q.27.271.27.633 0 .361-.27.633l-3.094 3.094a.71.71 0 0 1-.53.22.75.75 0 0 1-.54-.236.73.73 0 0 1-.21-.534.74.74 0 0 1 .226-.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mail-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M4.308 19.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T4.308 4.5h15.384q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525zM12 12.392a.9.9 0 0 0 .248-.037q.123-.038.239-.103l7.152-4.579a.6.6 0 0 0 .242-.274.64.64 0 0 0 .042-.355.59.59 0 0 0-.338-.514.61.61 0 0 0-.645.03L12 11 5.06 6.56q-.325-.198-.64-.032a.6.6 0 0 0-.343.507.7.7 0 0 0 .042.375.5.5 0 0 0 .243.263l7.152 4.579q.115.065.238.103a.9.9 0 0 0 .248.037'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mail.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.75 19.85q-.642 0-1.113-.471a1.52 1.52 0 0 1-.471-1.112V5.733q0-.64.471-1.112A1.52 1.52 0 0 1 3.75 4.15h16.5q.64 0 1.112.471.471.472.471 1.112v12.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.471zm16.5-12.617-7.83 5.063q-.107.054-.206.091a.6.6 0 0 1-.215.038.6.6 0 0 1-.214-.038 2 2 0 0 1-.198-.091L3.749 7.233v11.034h16.5zM12 10.983l8.166-5.25H3.849zm-8.25-3.75v.188-1.08.014-.622.617-.015 1.087zv11.033z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/message-blocked.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill=\"none\"\n    color={color}\n    viewBox=\"0 0 16 16\"\n  >\n    <Mask\n      id=\"prefix__a\"\n      width={16}\n      height={16}\n      x={0}\n      y={0}\n      maskUnits=\"userSpaceOnUse\"\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill=\"#D9D9D9\" d=\"M0 0h16v16H0z\" />\n    </Mask>\n    <G mask=\"url(#prefix__a)\">\n      <Path\n        fill={color}\n        d=\"M2.283 13.667a.583.583 0 0 1-.514-.301.64.64 0 0 1-.086-.292.54.54 0 0 1 .085-.311l5.707-9.859a.6.6 0 0 1 .524-.301q.154 0 .293.073.14.074.232.228l5.707 9.859q.093.153.085.31a.64.64 0 0 1-.296.51.57.57 0 0 1-.304.084zm5.716-1.795q.229 0 .384-.155a.52.52 0 0 0 .155-.384.52.52 0 0 0-.155-.383.52.52 0 0 0-.384-.155.52.52 0 0 0-.383.155.52.52 0 0 0-.155.383q0 .229.155.384a.52.52 0 0 0 .383.155m0-1.744q.213 0 .357-.144a.48.48 0 0 0 .143-.356V7.295a.48.48 0 0 0-.143-.356.48.48 0 0 0-.357-.144.48.48 0 0 0-.356.144.48.48 0 0 0-.144.356v2.333q0 .213.144.356a.48.48 0 0 0 .357.144\"\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mic-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 13.5q-1.048 0-1.774-.726A2.41 2.41 0 0 1 9.5 11V5q0-1.048.726-1.774A2.41 2.41 0 0 1 12 2.5q1.048 0 1.774.726T14.5 5v6q0 1.048-.726 1.774A2.41 2.41 0 0 1 12 13.5m-.75 6.5v-2.546a6.33 6.33 0 0 1-3.827-1.835q-1.594-1.576-1.868-3.844a.65.65 0 0 1 .168-.547A.68.68 0 0 1 6.25 11q.318 0 .534.22t.287.54a4.8 4.8 0 0 0 1.68 3.038A4.85 4.85 0 0 0 12 16q1.867 0 3.261-1.214 1.395-1.215 1.667-3.027.072-.318.287-.539a.72.72 0 0 1 .535-.22q.318 0 .527.228a.65.65 0 0 1 .167.547 6.4 6.4 0 0 1-1.855 3.814q-1.581 1.598-3.84 1.865V20a.73.73 0 0 1-.215.535.73.73 0 0 1-.534.215.73.73 0 0 1-.535-.215.73.73 0 0 1-.215-.535'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mic-off-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M16.52 13.902a.7.7 0 0 1-.349-.454.75.75 0 0 1 .057-.575q.146-.256.225-.544a5 5 0 0 0 .119-.585 1.05 1.05 0 0 1 .287-.531.73.73 0 0 1 .534-.213q.32 0 .527.233a.7.7 0 0 1 .177.551 6 6 0 0 1-.186.928 4 4 0 0 1-.362.873.72.72 0 0 1-.449.366.74.74 0 0 1-.58-.05m-3.692-4.163L9.67 6.56a1.8 1.8 0 0 1-.386-.58 1.8 1.8 0 0 1-.14-.694V5q0-1.048.725-1.774a2.41 2.41 0 0 1 1.774-.726q1.048 0 1.774.726T14.143 5v4.187q0 .52-.473.72-.473.201-.842-.168M10.893 20v-2.542a6.35 6.35 0 0 1-3.827-1.839Q5.472 14.043 5.2 11.775a.65.65 0 0 1 .167-.547.68.68 0 0 1 .527-.228q.32 0 .535.22t.286.54a4.8 4.8 0 0 0 1.68 3.038A4.85 4.85 0 0 0 11.644 16q.965 0 1.814-.344a4.9 4.9 0 0 0 1.502-.954l1.07 1.069a6.4 6.4 0 0 1-1.65 1.121 6.3 6.3 0 0 1-1.987.566V20a.73.73 0 0 1-.215.535.73.73 0 0 1-.535.215.73.73 0 0 1-.535-.215.73.73 0 0 1-.215-.535m8.447 1.189L2.204 4.053a.72.72 0 0 1-.213-.522A.7.7 0 0 1 2.205 3a.72.72 0 0 1 .527-.217q.31 0 .527.217l17.134 17.135q.208.207.213.522a.7.7 0 0 1-.213.532.72.72 0 0 1-.527.217.72.72 0 0 1-.526-.217'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mic-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.43 14.492a.76.76 0 0 1-.166-1.109q.255-.385.41-.822t.227-.903q.054-.338.287-.572a.77.77 0 0 1 .573-.236q.332 0 .553.243a.68.68 0 0 1 .179.57 7.3 7.3 0 0 1-.321 1.389 5.3 5.3 0 0 1-.633 1.281.777.777 0 0 1-1.109.159M11.68 2.167q1.07 0 1.828.751.756.752.756 1.832v5.338q0 .402-.243.599a.84.84 0 0 1-1.093-.008q-.248-.205-.248-.604V4.75a.97.97 0 0 0-1-1 .97.97 0 0 0-.72.284.98.98 0 0 0-.28.716v1.733q0 .402-.248.595a.87.87 0 0 1-.545.193.84.84 0 0 1-.548-.2q-.242-.2-.242-.592V4.75q0-1.08.751-1.832.75-.75 1.832-.751m-.796 17.904v-2.492a6.74 6.74 0 0 1-4.035-1.904q-1.698-1.641-1.99-4.017a.65.65 0 0 1 .177-.571.75.75 0 0 1 .563-.237.76.76 0 0 1 .568.23q.225.23.284.574.305 1.934 1.802 3.165a5.25 5.25 0 0 0 3.43 1.231q.906 0 1.75-.3a5.5 5.5 0 0 0 1.53-.833l1.143 1.141q-.755.63-1.676 1.02a6.9 6.9 0 0 1-1.962.501v2.492q0 .333-.232.564a.76.76 0 0 1-.559.232.76.76 0 0 1-.563-.232.77.77 0 0 1-.23-.564m8.98 1.812L1.73 3.75a.71.71 0 0 1-.217-.513.7.7 0 0 1 .216-.52.68.68 0 0 1 .513-.213q.31 0 .52.213l18.138 18.137a.7.7 0 0 1 .217.51.72.72 0 0 1-.217.526.7.7 0 0 1-.523.208.7.7 0 0 1-.514-.215'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mic.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12 13.533q-1.092 0-1.838-.784-.746-.783-.746-1.899V4.733q0-1.07.753-1.818A2.5 2.5 0 0 1 12 2.167q1.076 0 1.83.748.754.75.754 1.818v6.117q0 1.116-.746 1.9a2.44 2.44 0 0 1-1.838.783m-.788 6.538v-2.494a6.76 6.76 0 0 1-4.044-1.904q-1.698-1.64-1.981-4.015a.65.65 0 0 1 .17-.571.74.74 0 0 1 .569-.237.76.76 0 0 1 .561.231q.23.231.292.573.303 1.938 1.801 3.167a5.24 5.24 0 0 0 3.418 1.229q1.947 0 3.433-1.235t1.798-3.16a1.02 1.02 0 0 1 .287-.573.77.77 0 0 1 .568-.232.73.73 0 0 1 .557.237.68.68 0 0 1 .18.571 6.6 6.6 0 0 1-1.977 4q-1.684 1.652-4.049 1.921v2.492q0 .333-.232.564a.77.77 0 0 1-.567.232.75.75 0 0 1-.559-.232.78.78 0 0 1-.225-.564m.787-8.121a.91.91 0 0 0 .72-.32q.28-.323.28-.78V4.74a.96.96 0 0 0-.287-.705.97.97 0 0 0-.711-.285.98.98 0 0 0-.713.283.94.94 0 0 0-.289.7v6.115q0 .46.28.781.282.321.72.321'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/missed-video-call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M10.96 15.267q.18 0 .336-.065a.9.9 0 0 0 .296-.206l2.994-3.019a.74.74 0 0 0 .22-.514.7.7 0 0 0-.22-.54.72.72 0 0 0-.527-.217.72.72 0 0 0-.526.217l-2.573 2.592-2.816-2.784h1.134a.73.73 0 0 0 .535-.216.73.73 0 0 0 .215-.534.73.73 0 0 0-.215-.535.73.73 0 0 0-.535-.215H6.432a.88.88 0 0 0-.645.258.88.88 0 0 0-.258.646v2.846q0 .319.215.534a.73.73 0 0 0 .534.216.73.73 0 0 0 .535-.216.73.73 0 0 0 .215-.534v-1.258l3.299 3.298a.8.8 0 0 0 .296.193q.156.053.337.053M4.548 19.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T4.548 4.5h11.385q.757 0 1.282.525t.525 1.283v4.577l2.746-2.746q.221-.222.497-.111.276.11.276.422v7.1q0 .311-.276.422t-.497-.11l-2.746-2.747v4.577q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/missed-video-call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M10.762 15.408a.8.8 0 0 0 .298-.056.8.8 0 0 0 .259-.178l3.22-3.24a.73.73 0 0 0 .233-.555.77.77 0 0 0-.247-.562.77.77 0 0 0-.559-.23q-.323 0-.552.23l-2.65 2.65-2.833-2.817h1.237q.333 0 .565-.232a.77.77 0 0 0 .231-.563.76.76 0 0 0-.231-.56.77.77 0 0 0-.565-.228H5.935a.75.75 0 0 0-.56.231.77.77 0 0 0-.228.556v3.133q0 .334.228.565t.563.231.564-.231a.77.77 0 0 0 .229-.565v-1.304l3.47 3.488a.71.71 0 0 0 .561.237M3.781 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112A1.52 1.52 0 0 1 3.78 4.15h12.536q.629 0 1.105.471.475.471.475 1.112v5.1L21.131 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.105.47zm0-1.583h12.533V5.733H3.781z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mood-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M15.406 10.808q.546 0 .926-.383.38-.381.38-.927t-.383-.926a1.27 1.27 0 0 0-.928-.38q-.545 0-.925.383-.38.381-.38.927t.382.926q.382.38.928.38m-6.808 0q.546 0 .926-.383.38-.381.38-.927t-.382-.926a1.27 1.27 0 0 0-.928-.38q-.546 0-.926.383-.38.381-.38.927t.382.926q.383.38.928.38M12.003 21.5a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749M12 17.192q1.346 0 2.484-.637a5.2 5.2 0 0 0 1.847-1.73.55.55 0 0 0-.009-.549.5.5 0 0 0-.472-.276h-7.7a.5.5 0 0 0-.472.276.55.55 0 0 0-.009.549 5.2 5.2 0 0 0 1.859 1.73q1.149.637 2.472.637'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/mood.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M15.55 10.767q.567 0 .968-.399.4-.397.4-.966 0-.57-.398-.969a1.31 1.31 0 0 0-.967-.4q-.569 0-.969.398-.4.399-.4.967 0 .57.399.969.397.4.966.4m-7.1 0q.567 0 .968-.399.4-.397.4-.966 0-.57-.398-.969a1.31 1.31 0 0 0-.967-.4q-.569 0-.969.398-.4.399-.4.967 0 .57.398.969.399.4.967.4m3.552 11.066a9.6 9.6 0 0 1-3.836-.775 9.9 9.9 0 0 1-3.124-2.104 9.9 9.9 0 0 1-2.101-3.122 9.6 9.6 0 0 1-.773-3.831q0-2.04.775-3.836a9.9 9.9 0 0 1 2.104-3.124A9.9 9.9 0 0 1 8.17 2.939 9.6 9.6 0 0 1 12 2.167q2.041 0 3.836.775a9.9 9.9 0 0 1 3.124 2.104 9.9 9.9 0 0 1 2.102 3.122 9.6 9.6 0 0 1 .772 3.831q0 2.04-.775 3.836a9.9 9.9 0 0 1-2.104 3.124 9.9 9.9 0 0 1-3.121 2.102 9.6 9.6 0 0 1-3.832.772m0-1.583q3.45 0 5.85-2.399t2.4-5.851q0-3.452-2.4-5.851t-5.85-2.399q-3.453 0-5.852 2.399T3.751 12q0 3.452 2.4 5.851 2.398 2.4 5.85 2.399m-.011-2.867a5.35 5.35 0 0 0 4.54-2.446.54.54 0 0 0-.018-.562.55.55 0 0 0-.505-.275h-8.01q-.347 0-.511.275a.52.52 0 0 0-.007.556 5.2 5.2 0 0 0 1.932 1.802 5.3 5.3 0 0 0 2.579.65'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/more-vert-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 19.27q-.619 0-1.06-.442a1.44 1.44 0 0 1-.44-1.059q0-.618.44-1.06.442-.44 1.06-.44t1.06.44q.44.442.44 1.06 0 .62-.44 1.06-.442.44-1.06.44m0-5.77q-.619 0-1.06-.44A1.44 1.44 0 0 1 10.5 12q0-.619.44-1.06.442-.44 1.06-.44t1.06.44q.44.441.44 1.06 0 .618-.44 1.06-.442.44-1.06.44m0-5.77q-.619 0-1.06-.44a1.44 1.44 0 0 1-.44-1.06q0-.618.44-1.059.442-.44 1.06-.44t1.06.44q.44.44.44 1.06 0 .618-.44 1.06-.442.44-1.06.44'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/more-vert.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12 20.167q-.594 0-1.013-.422a1.39 1.39 0 0 1-.419-1.014q0-.594.421-1.012.422-.42 1.015-.419.594 0 1.012.421.42.421.419 1.015 0 .593-.421 1.012-.422.42-1.015.419m0-6.734q-.594 0-1.013-.42a1.39 1.39 0 0 1-.419-1.015q0-.594.421-1.012.422-.42 1.015-.42.594 0 1.012.422.42.42.419 1.014 0 .594-.421 1.012-.422.42-1.015.42M12 6.7q-.594 0-1.013-.421a1.39 1.39 0 0 1-.419-1.015q0-.593.421-1.012.422-.42 1.015-.419.594 0 1.012.422.42.42.419 1.014 0 .594-.421 1.012-.422.42-1.015.419'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/near-me-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.376 13.604-5.744-2.327a.8.8 0 0 1-.427-.352 1 1 0 0 1-.13-.485q0-.247.142-.486a.85.85 0 0 1 .44-.354l13.69-5.126a.78.78 0 0 1 .518-.044.96.96 0 0 1 .434.251.96.96 0 0 1 .251.433q.068.251-.044.519L14.36 19.317a.85.85 0 0 1-.35.434.9.9 0 0 1-.486.145 1 1 0 0 1-.487-.136.8.8 0 0 1-.353-.431z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/near-me.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.376 13.604-5.744-2.327a.8.8 0 0 1-.427-.352 1 1 0 0 1-.13-.485q0-.247.142-.486a.85.85 0 0 1 .44-.354l13.69-5.126a.78.78 0 0 1 .518-.044.96.96 0 0 1 .434.251.96.96 0 0 1 .251.433q.068.251-.044.519L14.36 19.317a.85.85 0 0 1-.35.434.9.9 0 0 1-.486.145 1 1 0 0 1-.487-.136.8.8 0 0 1-.353-.431zm3.116 3.735 4.165-11.016-11.035 4.146 4.9 1.97z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/no-photography-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M20.266 22.373 18.377 20.5H4.308a1.74 1.74 0 0 1-1.277-.531 1.74 1.74 0 0 1-.53-1.277V7.308q0-.746.53-1.277A1.74 1.74 0 0 1 4.308 5.5h1.208v2.139L1.808 3.93a.74.74 0 0 1 0-1.07.74.74 0 0 1 .535-.232q.302 0 .534.233l18.458 18.457a.73.73 0 0 1 .228.522.72.72 0 0 1-.228.532.74.74 0 0 1-.535.233.73.73 0 0 1-.534-.233m.667-5.075a.9.9 0 0 1-.497.066.86.86 0 0 1-.472-.262l-3.873-3.873a4 4 0 0 0-.251-1.701 4.03 4.03 0 0 0-2.368-2.367 4 4 0 0 0-1.7-.251L8.445 5.598a.8.8 0 0 1-.198-.297.96.96 0 0 1 0-.656q.057-.15.173-.287l.25-.275q.248-.27.597-.426A1.8 1.8 0 0 1 10 3.5h4q.384 0 .733.157.35.157.598.426L16.64 5.5h3.054q.745 0 1.276.531t.531 1.277v9.161q0 .316-.16.52a1 1 0 0 1-.407.309M12 17.115q.62 0 1.197-.177a3.7 3.7 0 0 0 1.078-.54l-5.673-5.673q-.36.5-.54 1.078A4 4 0 0 0 7.886 13q0 1.715 1.2 2.915t2.915 1.2m.005-1.5q-1.101 0-1.86-.76-.76-.759-.76-1.86a2.6 2.6 0 0 1 .763-1.847l3.704 3.704a2.594 2.594 0 0 1-1.847.764'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/no-photography.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M21.834 6.854v11.08q0 .401-.243.594a.862.862 0 0 1-1.093-.008q-.247-.2-.247-.59V6.853h-3.496q-.357 0-.67-.131a1.34 1.34 0 0 1-.534-.406L14.22 4.75H9.789l-.716.846a.74.74 0 0 1-.565.279.78.78 0 0 1-.597-.241.8.8 0 0 1-.238-.532.72.72 0 0 1 .196-.544l.712-.841q.21-.273.529-.411.317-.14.68-.14h4.43q.356 0 .674.14.317.139.537.41l1.32 1.555h3.5q.625 0 1.105.48t.479 1.103M3.751 20.834a1.52 1.52 0 0 1-1.112-.472 1.52 1.52 0 0 1-.471-1.112V6.854q0-.624.47-1.104.472-.48 1.113-.48h1.537l1.584 1.584H3.75V19.25h15.508l1.584 1.583zm11.75-5.342a4.2 4.2 0 0 1-1.479 1.318q-.903.49-2.023.49-1.82 0-3.036-1.229T7.747 13.04q0-1.115.481-2.031a4.1 4.1 0 0 1 1.31-1.487l1.155 1.162a2.6 2.6 0 0 0-.992.96q-.37.62-.371 1.4 0 1.136.76 1.904.762.77 1.909.77.774 0 1.399-.371.623-.371.965-1zm-.471-5.48q.575.575.904 1.343.33.767.33 1.682 0 .238-.017.446a2.4 2.4 0 0 1-.071.417l-5.03-5.038q.205-.037.413-.058.208-.02.442-.02.895 0 1.672.328.778.33 1.357.9m5.079 12.334L1.422 3.67a.76.76 0 0 1-.246-.565q0-.327.242-.564a.79.79 0 0 1 .569-.238q.327 0 .565.239l18.695 18.686q.237.24.237.558t-.238.561a.8.8 0 0 1-.573.244.76.76 0 0 1-.564-.246'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/notifications-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5.25 18.885a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.215h1.058V9.923q0-2.017 1.245-3.567a5.52 5.52 0 0 1 3.197-1.983V3.75q0-.52.364-.885Q11.48 2.5 12 2.5t.885.365.366.885v.623a5.52 5.52 0 0 1 3.197 1.983q1.245 1.55 1.245 3.567v7.462h1.058q.318 0 .534.215a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.215zm6.748 2.807q-.746 0-1.276-.53a1.74 1.74 0 0 1-.53-1.277h3.616q0 .747-.532 1.277-.531.53-1.278.53'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/notifications-off-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M16.514 18.885H5.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.535q0-.318.216-.534a.73.73 0 0 1 .534-.215h1.058V9.923q0-.7.169-1.39.17-.692.508-1.317L9.69 9.923H7.584L2.56 4.9a.73.73 0 0 1-.212-.522.7.7 0 0 1 .212-.532.72.72 0 0 1 .527-.217q.31 0 .527.217l16.789 16.789q.207.207.22.514a.7.7 0 0 1-.223.541.71.71 0 0 1-.524.216.72.72 0 0 1-.527-.217zm1.177-6.016a.82.82 0 0 1-.16.49 1.03 1.03 0 0 1-.407.339.8.8 0 0 1-.513.063.87.87 0 0 1-.457-.244L9.135 6.498a1.02 1.02 0 0 1-.305-.742q0-.249.123-.486a.86.86 0 0 1 .344-.362q.342-.192.704-.322.36-.128.748-.213V3.75q0-.52.364-.885.365-.365.885-.365t.885.365.366.885v.623a5.4 5.4 0 0 1 3.202 1.976 5.6 5.6 0 0 1 1.24 3.574zm-5.692 8.823q-.68 0-1.21-.373-.532-.374-.532-1.007 0-.18.146-.304a.5.5 0 0 1 .328-.123h2.533q.19 0 .334.123a.39.39 0 0 1 .143.305q0 .63-.532 1.005-.531.374-1.21.374'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/notifications-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.666 18.87H4.9a.76.76 0 0 1-.563-.224.76.76 0 0 1-.225-.563.776.776 0 0 1 .788-.796h1.129v-7.25q0-.833.206-1.64a5.6 5.6 0 0 1 .648-1.53L8.066 8.05a4.6 4.6 0 0 0-.454 1.987v7.25h7.52L2.463 4.65a.73.73 0 0 1-.237-.544q0-.315.233-.556a.75.75 0 0 1 .55-.244.77.77 0 0 1 .558.231l16.878 16.895q.23.231.229.549a.778.778 0 0 1-1.328.552zm-3.2-14.67q2.03.446 3.271 2.099a6.07 6.07 0 0 1 1.242 3.738v3.75q0 .399-.245.597a.84.84 0 0 1-.544.2.86.86 0 0 1-.547-.198q-.248-.198-.248-.594v-3.755q0-1.822-1.288-3.105Q13.82 5.65 12 5.65a4.28 4.28 0 0 0-2.284.654q-.321.2-.687.17a.8.8 0 0 1-.592-.318.8.8 0 0 1-.2-.546.6.6 0 0 1 .267-.485q.45-.321.963-.558a5.6 5.6 0 0 1 1.066-.369v-.562q0-.61.427-1.038.427-.427 1.038-.427.61 0 1.04.427.428.427.428 1.039zm-1.468 17.6q-.753 0-1.332-.432a1.36 1.36 0 0 1-.579-1.146.36.36 0 0 1 .12-.28.43.43 0 0 1 .304-.109h2.986a.44.44 0 0 1 .298.109.35.35 0 0 1 .125.28q0 .715-.584 1.146-.585.432-1.338.432'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/notifications.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.9 18.87a.76.76 0 0 1-.56-.227.77.77 0 0 1-.227-.563.77.77 0 0 1 .227-.563.76.76 0 0 1 .56-.23h1.13v-7.25q0-2.083 1.246-3.729A5.52 5.52 0 0 1 10.534 4.2v-.563q0-.612.427-1.039T12 2.171q.61 0 1.04.427.428.427.428 1.039V4.2a5.53 5.53 0 0 1 3.265 2.108 6 6 0 0 1 1.248 3.73v7.25h1.125q.325 0 .558.231a.77.77 0 0 1 .233.567.75.75 0 0 1-.23.56.78.78 0 0 1-.565.225zM12 21.8a1.9 1.9 0 0 1-1.389-.577 1.9 1.9 0 0 1-.577-1.39h3.933q0 .813-.577 1.39A1.9 1.9 0 0 1 12 21.8m-4.387-4.513h8.784v-7.25q0-1.822-1.289-3.105t-3.11-1.282q-1.823 0-3.103 1.282-1.282 1.283-1.282 3.105z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/objects.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G clipPath='url(#prefix__a)'>\n      <Path\n        fill={color ?? \"#A1A1A1\"}\n        fillRule='evenodd'\n        d='M6.743 17.575a.96.96 0 0 0 .586.314q.832.133 1.675.145.842-.012 1.673-.146a.96.96 0 0 0 .828-.934v-2.083l.107-.927c.15-1.22.75-2.34 1.68-3.142a5.3 5.3 0 0 0 1.756-4.146A5.88 5.88 0 0 0 9.004.967a5.88 5.88 0 0 0-6.048 5.689 5.3 5.3 0 0 0 1.76 4.146 4.95 4.95 0 0 1 1.679 3.142l.106.928v2.084a.96.96 0 0 0 .242.619M4.344 8.48c-.24-.577-.35-1.2-.321-1.824a4.815 4.815 0 0 1 4.98-4.622 4.834 4.834 0 0 1 4.979 4.661 4.24 4.24 0 0 1-1.404 3.316 5.98 5.98 0 0 0-2.019 3.756H7.448a6.02 6.02 0 0 0-2.039-3.77A4.3 4.3 0 0 1 4.344 8.48m4.301 2.18v2.396h.71v-2.397l.895-.824a.71.71 0 1 0-.518-.527L9 9.982l-.731-.674a.7.7 0 0 0-.101-.558.71.71 0 1 0-.413 1.085zm.345 6.307a10 10 0 0 1-1.422-.121v-.59h1.436v-.712H7.568v-.672l.003-.038h2.864v2.013q-.718.111-1.445.12'\n        clipRule='evenodd'\n      />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/outgoing-audio-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 20 20' fill='none'>\n    <Path\n      fill={color}\n      d='M11.4674 15.9882C13.0524 16.7185 14.6301 17.0837 16.2005 17.0837C16.4505 17.0837 16.6603 17.0003 16.8297 16.8337C16.999 16.667 17.0837 16.4587 17.0837 16.2087V13.5068C17.0837 13.3048 17.0171 13.1243 16.8841 12.9651C16.751 12.806 16.5793 12.6986 16.3689 12.643L14.0549 12.1718C13.855 12.144 13.6625 12.1573 13.4772 12.2118C13.2918 12.2664 13.1339 12.3615 13.0035 12.4972L11.1335 14.3835C10.5362 14.0458 9.98012 13.681 9.46512 13.2889C8.95012 12.8968 8.46401 12.4721 8.00678 12.0147C7.54303 11.5543 7.1122 11.0712 6.71428 10.5653C6.31623 10.0594 5.96234 9.52276 5.65262 8.95553L7.5772 7.08199C7.69901 6.97088 7.78345 6.83359 7.83053 6.67012C7.87748 6.50665 7.88442 6.31116 7.85137 6.08366L7.37366 3.63178C7.32991 3.42984 7.22762 3.26026 7.06678 3.12303C6.90595 2.98567 6.72033 2.91699 6.50991 2.91699H3.79199C3.54199 2.91699 3.33366 3.00165 3.16699 3.17095C3.00033 3.3404 2.91699 3.55012 2.91699 3.80012C2.91699 5.37053 3.28213 6.94685 4.01241 8.52908C4.74269 10.1114 5.77019 11.5677 7.09491 12.8978C8.42505 14.2278 9.88255 15.258 11.4674 15.9882Z'\n    />\n    <Path\n      fill={color}\n      d='M13.0724 4.22374H14.9026L10.9826 8.14353C10.8619 8.26437 10.8016 8.41076 10.8016 8.5827C10.8016 8.75478 10.8619 8.90117 10.9826 9.02187C11.1033 9.14256 11.251 9.20159 11.4258 9.19895C11.6005 9.19631 11.7456 9.13728 11.861 9.02187L15.7808 5.10187V6.93208C15.7808 7.10916 15.8406 7.25763 15.9603 7.37749C16.0799 7.49722 16.2283 7.55708 16.4056 7.55708C16.5826 7.55708 16.7311 7.49722 16.851 7.37749C16.9708 7.25763 17.0308 7.10916 17.0308 6.93208V3.72687C17.0308 3.51353 16.9585 3.33471 16.8141 3.19041C16.6698 3.04596 16.491 2.97374 16.2776 2.97374H13.0724C12.8953 2.97374 12.7469 3.0336 12.627 3.15333C12.5073 3.27291 12.4474 3.42131 12.4474 3.59853C12.4474 3.77562 12.5073 3.92409 12.627 4.04395C12.7469 4.16381 12.8953 4.22374 13.0724 4.22374Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/outgoing-audio.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} viewBox='0 0 20 20' fill='none'>\n    <Path\n      fill={color}\n      d='M11.4674 15.9882C13.0524 16.7185 14.6301 17.0837 16.2005 17.0837C16.4505 17.0837 16.6603 17.0003 16.8297 16.8337C16.999 16.667 17.0837 16.4587 17.0837 16.2087V13.5068C17.0837 13.3048 17.0171 13.1243 16.8841 12.9651C16.751 12.806 16.5793 12.6986 16.3689 12.643L14.0549 12.1718C13.855 12.144 13.6625 12.1573 13.4772 12.2118C13.2918 12.2664 13.1339 12.3615 13.0035 12.4972L11.1335 14.3835C10.5362 14.0458 9.98012 13.681 9.46512 13.2889C8.95012 12.8968 8.46401 12.4721 8.00678 12.0147C7.54303 11.5543 7.1122 11.0712 6.71428 10.5653C6.31623 10.0594 5.96234 9.52276 5.65262 8.95553L7.5772 7.08199C7.69901 6.97088 7.78345 6.83359 7.83053 6.67012C7.87748 6.50665 7.88442 6.31116 7.85137 6.08366L7.37366 3.63178C7.32991 3.42984 7.22762 3.26026 7.06678 3.12303C6.90595 2.98567 6.72033 2.91699 6.50991 2.91699H3.79199C3.54199 2.91699 3.33366 3.00165 3.16699 3.17095C3.00033 3.3404 2.91699 3.55012 2.91699 3.80012C2.91699 5.37053 3.28213 6.94685 4.01241 8.52908C4.74269 10.1114 5.77019 11.5677 7.09491 12.8978C8.42505 14.2278 9.88255 15.258 11.4674 15.9882Z'\n    />\n    <Path\n      fill={color}\n      d='M13.0724 4.22374H14.9026L10.9826 8.14353C10.8619 8.26437 10.8016 8.41076 10.8016 8.5827C10.8016 8.75478 10.8619 8.90117 10.9826 9.02187C11.1033 9.14256 11.251 9.20159 11.4258 9.19895C11.6005 9.19631 11.7456 9.13728 11.861 9.02187L15.7808 5.10187V6.93208C15.7808 7.10916 15.8406 7.25763 15.9603 7.37749C16.0799 7.49722 16.2283 7.55708 16.4056 7.55708C16.5826 7.55708 16.7311 7.49722 16.851 7.37749C16.9708 7.25763 17.0308 7.10916 17.0308 6.93208V3.72687C17.0308 3.51353 16.9585 3.33471 16.8141 3.19041C16.6698 3.04596 16.491 2.97374 16.2776 2.97374H13.0724C12.8953 2.97374 12.7469 3.0336 12.627 3.15333C12.5073 3.27291 12.4474 3.42131 12.4474 3.59853C12.4474 3.77562 12.5073 3.92409 12.627 4.04395C12.7469 4.16381 12.8953 4.22374 13.0724 4.22374Z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/outgoing-video-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        fillRule='evenodd'\n        d='M3.265 18.975q.525.525 1.283.525h11.385q.757 0 1.282-.525t.525-1.283v-4.577l2.746 2.746q.221.222.497.111.276-.11.276-.422v-7.1q0-.311-.276-.422t-.497.11l-2.746 2.747V6.308q0-.758-.525-1.283a1.75 1.75 0 0 0-1.283-.525H4.548q-.758 0-1.283.525T2.74 6.308v11.384q0 .758.525 1.283M8.725 9.5h2.196l-4.704 4.704A.72.72 0 0 0 6 14.73q0 .31.217.527a.7.7 0 0 0 .532.212.72.72 0 0 0 .522-.212l4.704-4.704v2.196q0 .319.215.534a.73.73 0 0 0 .534.216q.32 0 .535-.216a.73.73 0 0 0 .216-.534V8.904a.87.87 0 0 0-.26-.644.87.87 0 0 0-.644-.26H8.725a.73.73 0 0 0-.535.215.73.73 0 0 0-.215.535q0 .318.215.534a.73.73 0 0 0 .535.216'\n        clipRule='evenodd'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/outgoing-video.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.78 19.85q-.64 0-1.111-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112A1.52 1.52 0 0 1 3.78 4.15h12.536q.629 0 1.105.471.475.471.475 1.112v5.1L21.131 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.105.47zm0-1.583h12.534V5.733H3.781z'\n    />\n    <Path\n      fill={color}\n      d='M10.922 9.5H8.726a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.534A.73.73 0 0 1 8.726 8h3.846q.383 0 .644.26.26.26.26.644v3.846q0 .319-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534v-2.196l-4.704 4.704a.73.73 0 0 1-.522.212.7.7 0 0 1-.532-.212.72.72 0 0 1-.217-.527q0-.31.217-.527z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/pause-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M15.5 18.5q-.613 0-1.057-.443A1.44 1.44 0 0 1 14 17V7q0-.613.443-1.057A1.44 1.44 0 0 1 15.5 5.5h.75q.613 0 1.057.443.443.444.443 1.057v10q0 .613-.443 1.057a1.44 1.44 0 0 1-1.057.443zm-7.75 0q-.613 0-1.057-.443A1.44 1.44 0 0 1 6.25 17V7q0-.613.443-1.057A1.44 1.44 0 0 1 7.75 5.5h.75q.613 0 1.057.443Q10 6.387 10 7v10q0 .613-.443 1.057A1.44 1.44 0 0 1 8.5 18.5z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/pause.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M14.7 18.867q-.654 0-1.119-.466a1.53 1.53 0 0 1-.465-1.118V6.717q0-.654.465-1.119t1.119-.465h2.583q.653 0 1.118.465t.465 1.119v10.566q0 .654-.465 1.118t-1.118.466zm-7.984 0q-.653 0-1.118-.466a1.52 1.52 0 0 1-.465-1.118V6.717q0-.654.465-1.119t1.118-.465H9.3q.653 0 1.118.465t.465 1.119v10.566q0 .654-.465 1.118t-1.118.466zm7.984-1.584h2.583V6.717H14.7zm-7.984 0H9.3V6.717H6.716z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/pdf-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#FA4E4E'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <Path\n      fill='#fff'\n      d='M20.33 20.626a4.25 4.25 0 0 1-2.465-1.002c-1.366.3-2.666.735-3.966 1.27-1.033 1.837-2 2.773-2.833 2.773-.166 0-.366-.033-.5-.134a.99.99 0 0 1-.566-.902c0-.3.067-1.136 3.233-2.506a24 24 0 0 0 1.766-4.144c-.4-.802-1.267-2.773-.667-3.776.2-.367.6-.568 1.034-.534.333 0 .666.167.866.434.433.601.4 1.871-.167 3.743a10.1 10.1 0 0 0 2.066 2.673c.7-.134 1.4-.234 2.1-.234 1.566.033 1.8.768 1.766 1.203 0 1.136-1.1 1.136-1.666 1.136M11 22.698l.1-.034c.466-.167.833-.5 1.1-.935-.5.2-.9.534-1.2.969m4.432-10.025h-.1c-.033 0-.1 0-.133.033-.134.568-.034 1.17.2 1.705.2-.568.2-1.17.033-1.738m.233 4.845-.033.067-.033-.033c-.3.768-.634 1.537-1 2.272l.066-.033v.066a19 19 0 0 1 2.267-.668l-.034-.033h.1c-.5-.502-.966-1.07-1.333-1.638m4.533 1.771c-.3 0-.567 0-.867.067.333.167.667.234 1 .268.233.033.466 0 .666-.067 0-.1-.133-.268-.8-.268'\n    />\n    <Path fill='url(#prefix__b)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#FDB8B8' d='M22.266 9.333h4.533l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__b'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/person-add-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.125 10.75h-2.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h2.25V7q0-.319.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v2.25h2.25q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .319-.216.534a.73.73 0 0 1-.534.216h-2.25V13q0 .318-.216.534a.73.73 0 0 1-.534.216.72.72 0 0 1-.535-.216.73.73 0 0 1-.215-.534zM9 11.692q-1.444 0-2.472-1.028T5.5 8.192 6.528 5.72 9 4.692t2.472 1.028T12.5 8.192t-1.028 2.472Q10.443 11.693 9 11.692m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.067-.963 14.5 14.5 0 0 1 2.99-1.09 12.95 12.95 0 0 1 6.087 0q1.509.364 2.992 1.09.667.337 1.066.963t.399 1.36v.704q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444H3.019q-.633 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/person-add.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M18.13 10.817h-2.345a.75.75 0 0 1-.56-.234.78.78 0 0 1-.228-.561q0-.327.227-.558a.76.76 0 0 1 .56-.23h2.347V6.887q0-.325.227-.556a.76.76 0 0 1 .563-.232q.334 0 .564.232t.229.556v2.345h2.337q.333 0 .565.233a.76.76 0 0 1 .231.558q0 .334-.231.564a.77.77 0 0 1-.565.229h-2.337v2.337q0 .333-.232.565a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.565zM9.019 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06 2.69 1.06 1.06 2.69-1.06 2.69T9.018 12m-7.85 6.2v-.818q0-.85.427-1.539a2.67 2.67 0 0 1 1.21-1.04q1.746-.795 3.22-1.147a12.8 12.8 0 0 1 5.973 0q1.467.352 3.22 1.144.78.363 1.215 1.046a2.8 2.8 0 0 1 .435 1.537v.821q0 .648-.465 1.114-.465.465-1.118.465H2.75q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.534v-.813q0-.364-.196-.686a1.23 1.23 0 0 0-.525-.48q-1.63-.763-2.882-1.048a12 12 0 0 0-2.662-.286q-1.419 0-2.677.286t-2.88 1.048q-.337.158-.524.479a1.33 1.33 0 0 0-.188.683zm6.267-7.783q.925 0 1.546-.621.62-.621.62-1.546t-.62-1.546q-.622-.62-1.546-.62-.925 0-1.546.62-.62.621-.62 1.546t.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/person-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 11.692q-1.448 0-2.474-1.026A3.37 3.37 0 0 1 8.5 8.192q0-1.448 1.026-2.474A3.37 3.37 0 0 1 12 4.692q1.448 0 2.474 1.026A3.37 3.37 0 0 1 15.5 8.192q0 1.449-1.026 2.474A3.37 3.37 0 0 1 12 11.692m-7.5 6.096v-.704q0-.735.399-1.36a2.66 2.66 0 0 1 1.066-.963 14.5 14.5 0 0 1 2.992-1.09 12.95 12.95 0 0 1 6.086 0q1.509.364 2.992 1.09.667.337 1.066.963t.399 1.36v.704q0 .633-.443 1.076a1.47 1.47 0 0 1-1.076.444H6.019q-.632 0-1.076-.444a1.47 1.47 0 0 1-.443-1.076'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/person-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg\n    width={width}\n    height={height}\n    fill={color ?? \"#none\"}\n    viewBox=\"0 0 24 24\"\n  >\n    <Path\n    fill={color ?? \"#000\"}\n      d=\"m18.777 20.6-2.108-2.092H6.622q-.552 0-.937-.385a1.27 1.27 0 0 1-.385-.935v-.404q0-.529.274-.982a2.1 2.1 0 0 1 .766-.74q1.171-.68 2.449-1.07 1.275-.39 2.61-.448h.158q.081 0 .159.01L3.385 5.223a.6.6 0 0 1-.195-.45.66.66 0 0 1 .21-.47.64.64 0 0 1 .464-.207q.255 0 .455.208l15.375 15.39a.65.65 0 0 1 .205.454.6.6 0 0 1-.203.452.62.62 0 0 1-.455.208.64.64 0 0 1-.464-.208M6.6 17.208h8.77l-2.328-2.352q-.27-.02-.52-.034a9.93 9.93 0 0 0-3.109.326 9.5 9.5 0 0 0-2.384 1.002.9.9 0 0 0-.315.278.6.6 0 0 0-.114.356zm11.296-2.04q.222.14.358.34.136.202.177.46l-1.187-1.187.33.18q.165.09.322.206m-4.18-4.009-.975-.95q.481-.22.77-.662.29-.44.289-.964 0-.741-.526-1.266a1.73 1.73 0 0 0-1.264-.525q-.522 0-.963.29-.442.288-.664.77l-.95-.975a2.85 2.85 0 0 1 1.112-1.027q.69-.358 1.455-.358 1.298 0 2.199.901.9.9.901 2.2 0 .764-.358 1.454-.357.69-1.027 1.112\"\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/person.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.999 12q-1.63 0-2.69-1.06t-1.06-2.69 1.06-2.69 2.69-1.06q1.628 0 2.689 1.06 1.06 1.06 1.06 2.69t-1.06 2.69-2.69 1.06m-7.85 6.2v-.817q0-.9.451-1.568a2.9 2.9 0 0 1 1.185-1.018 18 18 0 0 1 3.14-1.116 12.8 12.8 0 0 1 3.073-.377q1.545 0 3.065.381a18.4 18.4 0 0 1 3.134 1.118q.75.348 1.201 1.012.45.664.45 1.568v.821q0 .648-.465 1.114-.465.465-1.118.465H5.732q-.653 0-1.119-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h12.533v-.814q0-.366-.208-.686a1.36 1.36 0 0 0-.513-.48q-1.512-.728-2.831-1.03A12 12 0 0 0 12 14.887q-1.403 0-2.732.303-1.328.302-2.825 1.03-.312.16-.512.481-.2.322-.2.686zm6.266-7.783q.925 0 1.546-.621t.621-1.546-.62-1.546-1.547-.62-1.545.62-.621 1.546.62 1.546q.622.62 1.546.62'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-in-talk-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M19.789 11.73a.76.76 0 0 1-.535-.215.85.85 0 0 1-.271-.534q-.325-2.421-2.04-4.136-1.714-1.714-4.135-2.04a.87.87 0 0 1-.535-.263.75.75 0 0 1-.215-.542q0-.319.223-.534a.61.61 0 0 1 .527-.175q3.053.33 5.207 2.483 2.152 2.153 2.483 5.207a.61.61 0 0 1-.175.527.71.71 0 0 1-.535.223m-4.137 0a.63.63 0 0 1-.436-.166 1.03 1.03 0 0 1-.283-.447 3.3 3.3 0 0 0-.84-1.431 3.1 3.1 0 0 0-1.422-.84 1.04 1.04 0 0 1-.446-.283.65.65 0 0 1-.167-.451q0-.375.258-.606a.62.62 0 0 1 .588-.15q1.313.305 2.263 1.256a4.7 4.7 0 0 1 1.266 2.273q.081.329-.163.587a.81.81 0 0 1-.618.259m3.788 8.77q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247.29.248.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.021 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.244.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.286.239.65v3.242q0 .45-.305.75t-.755.3'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-in-talk.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.967 11.833a.78.78 0 0 1-.536-.215.85.85 0 0 1-.276-.547 7.25 7.25 0 0 0-2.106-4.24 7.25 7.25 0 0 0-4.248-2.106.9.9 0 0 1-.538-.282.79.79 0 0 1-.216-.551.78.78 0 0 1 .236-.573.63.63 0 0 1 .568-.186q3.125.353 5.33 2.558t2.566 5.33a.66.66 0 0 1-.199.577.8.8 0 0 1-.581.235m-4.21 0q-.255 0-.48-.179a.9.9 0 0 1-.306-.462 3.25 3.25 0 0 0-.85-1.438 3.3 3.3 0 0 0-1.432-.846.96.96 0 0 1-.467-.29.72.72 0 0 1-.175-.483q0-.398.268-.638a.66.66 0 0 1 .624-.155 4.7 4.7 0 0 1 2.307 1.287 4.95 4.95 0 0 1 1.307 2.31.65.65 0 0 1-.16.622.82.82 0 0 1-.637.272m3.994 9q-2.892 0-5.862-1.368-2.97-1.37-5.467-3.886-2.517-2.496-3.885-5.462-1.37-2.967-1.369-5.872 0-.458.31-.768t.773-.31h3.567q.35 0 .598.235T8.75 4l.666 3.194q.042.336-.026.617a1 1 0 0 1-.26.476L6.705 10.75q.605 1.041 1.305 1.953.702.91 1.545 1.735.859.882 1.82 1.626.96.742 2.026 1.336l2.384-2.417q.23-.246.523-.337t.593-.046l3.081.649q.37.089.611.38a1 1 0 0 1 .241.654v3.467q0 .464-.309.774a1.05 1.05 0 0 1-.774.31M5.922 9.283l1.9-1.916-.538-2.617H4.772q.058 1.025.33 2.142.27 1.116.82 2.391m8.967 8.867q.987.459 2.128.742 1.14.283 2.234.341v-2.516l-2.466-.521z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-incoming-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.44 20.5q-2.827 0-5.68-1.314t-5.246-3.709q-2.385-2.395-3.7-5.242Q3.5 7.386 3.5 4.56A1.034 1.034 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932a19 19 0 0 0 1.55 1.74 17.2 17.2 0 0 0 3.753 2.842l2.244-2.264a1.27 1.27 0 0 1 .568-.342q.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.305.75t-.755.3M15.515 9.539h2.197q.318 0 .534.215a.73.73 0 0 1 .216.535.73.73 0 0 1-.216.534.73.73 0 0 1-.534.216h-3.847a.88.88 0 0 1-.645-.26.88.88 0 0 1-.259-.644V6.289q0-.32.216-.535a.73.73 0 0 1 .534-.215q.32 0 .535.215a.73.73 0 0 1 .215.535v2.196l4.704-4.704a.73.73 0 0 1 .523-.213.7.7 0 0 1 .531.213q.217.217.217.527a.72.72 0 0 1-.217.527z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-incoming.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M15.515 9.539h2.197q.318 0 .534.215a.73.73 0 0 1 .216.535q0 .318-.216.534a.73.73 0 0 1-.534.216h-3.847a.87.87 0 0 1-.643-.26.87.87 0 0 1-.26-.644V6.289q0-.32.215-.535a.73.73 0 0 1 .535-.215q.318 0 .534.215a.73.73 0 0 1 .215.535v2.196l4.704-4.704a.73.73 0 0 1 .523-.213.7.7 0 0 1 .531.213q.217.217.217.527a.72.72 0 0 1-.217.527zM19.44 20.5q-2.827 0-5.68-1.314t-5.242-3.709-3.704-5.242T3.5 4.56A1.03 1.03 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932a19 19 0 0 0 1.55 1.74 17.2 17.2 0 0 0 3.753 2.842l2.244-2.264a1.27 1.27 0 0 1 .568-.342q.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M6.073 9.327l1.785-1.708a.18.18 0 0 0 .062-.106.23.23 0 0 0-.005-.125l-.434-2.234a.2.2 0 0 0-.067-.115A.2.2 0 0 0 7.289 5H5.15a.13.13 0 0 0-.135.135 12.8 12.8 0 0 0 1.058 4.192m8.7 8.642q.994.465 2.074.709t2.018.291a.13.13 0 0 0 .135-.135v-2.103a.2.2 0 0 0-.038-.125.2.2 0 0 0-.116-.068l-2.1-.427a.16.16 0 0 0-.1-.004.25.25 0 0 0-.092.062z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-missed-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m3.371 19.775-1.906-1.856a.95.95 0 0 1-.305-.714 1 1 0 0 1 .315-.724 14 14 0 0 1 4.868-3.332Q9.136 12 12 12t5.649 1.149a14.15 14.15 0 0 1 4.876 3.332q.308.309.314.724a.95.95 0 0 1-.305.714l-1.905 1.856q-.305.294-.681.329a1 1 0 0 1-.696-.2l-2.516-1.912a1.3 1.3 0 0 1-.367-.417 1.1 1.1 0 0 1-.12-.517v-2.822a15 15 0 0 0-2.113-.552A12 12 0 0 0 12 13.5q-1.107 0-2.137.184-1.029.186-2.113.553v2.82q0 .289-.12.518-.118.229-.367.417l-2.515 1.912a1 1 0 0 1-.696.2 1.1 1.1 0 0 1-.681-.329M6.259 9a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V4.404q0-.387.259-.645a.88.88 0 0 1 .645-.259h3.846q.32 0 .535.216a.73.73 0 0 1 .215.534.73.73 0 0 1-.215.535.73.73 0 0 1-.535.215H8.063l3.679 3.679a.3.3 0 0 0 .221.086.3.3 0 0 0 .222-.086l4.738-4.738a.74.74 0 0 1 1.069 0 .74.74 0 0 1 .233.534q0 .302-.233.535l-4.733 4.732a1.74 1.74 0 0 1-1.28.517 1.74 1.74 0 0 1-1.281-.517L7.009 6.054V8.25a.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-missed.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.517 20.233 1.2 17.934a.74.74 0 0 1-.234-.55.84.84 0 0 1 .233-.567 13.8 13.8 0 0 1 4.917-3.588A14.65 14.65 0 0 1 12 12q3.045 0 5.88 1.23 2.833 1.228 4.92 3.587.229.255.233.566a.74.74 0 0 1-.235.551l-2.298 2.3a.86.86 0 0 1-.573.239.86.86 0 0 1-.595-.158L16.5 18.187a.8.8 0 0 1-.237-.275.8.8 0 0 1-.08-.358v-3.27a11.3 11.3 0 0 0-2.077-.534 13.4 13.4 0 0 0-2.09-.167q-1.05 0-2.11.167t-2.073.533v3.267a.8.8 0 0 1-.077.352.73.73 0 0 1-.24.281L4.665 20.32q-.286.21-.598.183a.84.84 0 0 1-.55-.27m2.65-5.4q-.887.442-1.715 1.063-.826.621-1.602 1.32l1.317 1.35 2-1.5zm11.6-.05v2.184l2.066 1.583 1.334-1.317a12 12 0 0 0-1.619-1.366 17 17 0 0 0-1.781-1.084M6.13 8.804a.76.76 0 0 1-.56-.227.77.77 0 0 1-.225-.56V3.954q0-.323.227-.556a.75.75 0 0 1 .56-.231h4.05q.339 0 .567.23a.78.78 0 0 1 .23.57.75.75 0 0 1-.23.564.78.78 0 0 1-.567.223H7.98l4.009 3.975 5.016-5.016a.8.8 0 0 1 .565-.246.74.74 0 0 1 .564.234.79.79 0 0 1 .238.568q0 .327-.239.565l-5.028 5.029q-.465.47-1.127.47a1.46 1.46 0 0 1-1.106-.47L6.929 5.888v2.129a.75.75 0 0 1-.232.56.78.78 0 0 1-.566.227'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-outgoing-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M13.76 19.186q2.853 1.314 5.68 1.314a1.034 1.034 0 0 0 1.06-1.05v-3.242q0-.365-.24-.65a1.17 1.17 0 0 0-.618-.387l-2.776-.565a1.6 1.6 0 0 0-.694.048q-.334.098-.568.342L13.36 17.26a17.229 17.229 0 0 1-3.752-2.842 19 19 0 0 1-1.551-1.74 14 14 0 0 1-1.274-1.932l2.31-2.248q.218-.2.303-.494t.025-.704l-.573-2.942a1.1 1.1 0 0 0-.368-.61A1 1 0 0 0 7.81 3.5H4.55q-.45 0-.75.305t-.3.755q0 2.826 1.314 5.675t3.7 5.242q2.394 2.394 5.247 3.709'\n    />\n    <Path\n      fill={color}\n      d='M15.687 5.068h2.196l-4.704 4.704a.72.72 0 0 0-.218.527q0 .31.218.527a.7.7 0 0 0 .531.212.73.73 0 0 0 .523-.212l4.703-4.704v2.196q0 .319.216.535a.73.73 0 0 0 .534.215q.319 0 .535-.215a.73.73 0 0 0 .215-.535V4.472a.87.87 0 0 0-.26-.644.87.87 0 0 0-.643-.26h-3.847a.73.73 0 0 0-.534.216.73.73 0 0 0-.215.534q0 .318.215.534a.73.73 0 0 0 .535.216'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/phone-outgoing.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.44 20.5q-2.827 0-5.68-1.314t-5.242-3.709-3.704-5.242T3.5 4.56A1.03 1.03 0 0 1 4.55 3.5h3.262q.378 0 .668.247t.368.61L9.421 7.3q.06.41-.025.704-.084.293-.304.494l-2.31 2.248q.558 1.02 1.275 1.932.716.91 1.55 1.74a17.2 17.2 0 0 0 3.753 2.842l2.244-2.264q.235-.245.568-.342.334-.099.694-.048l2.776.565q.38.1.619.387.24.285.239.65v3.242q0 .45-.303.75t-.757.3M6.073 9.327l1.785-1.708a.18.18 0 0 0 .062-.106.23.23 0 0 0-.005-.125l-.434-2.234a.2.2 0 0 0-.067-.115A.2.2 0 0 0 7.289 5H5.15a.13.13 0 0 0-.135.135 12.8 12.8 0 0 0 1.058 4.192m8.7 8.642q.994.465 2.074.709t2.018.291a.13.13 0 0 0 .135-.135v-2.103a.2.2 0 0 0-.038-.125.2.2 0 0 0-.116-.068l-2.1-.427a.16.16 0 0 0-.1-.004.25.25 0 0 0-.092.062z'\n    />\n    <Path\n      fill={color}\n      d='M17.883 5.068h-2.196a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.319.215-.534a.73.73 0 0 1 .535-.216h3.846q.384 0 .643.26.26.26.26.644v3.846q0 .319-.215.535a.73.73 0 0 1-.535.215.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535V6.122l-4.703 4.704a.73.73 0 0 1-.523.212.7.7 0 0 1-.531-.212.72.72 0 0 1-.218-.527q0-.31.218-.527z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/photo-camera-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 17.115q1.722 0 2.918-1.197 1.197-1.196 1.197-2.918t-1.197-2.918Q13.722 8.885 12 8.885t-2.918 1.197Q7.885 11.278 7.885 13t1.197 2.918T12 17.115m0-1.5q-1.108.001-1.861-.754-.755-.753-.755-1.861t.755-1.861q.753-.755 1.861-.755t1.861.755q.755.753.755 1.861t-.755 1.861q-.753.755-1.861.755M4.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V7.308q0-.758.525-1.283T4.308 5.5h3.054l1.307-1.417q.246-.271.595-.427.35-.156.736-.156h4q.387 0 .736.156.348.156.595.427L16.639 5.5h3.053q.758 0 1.283.525t.525 1.283v11.384q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/photo-camera.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.995 17.3q1.81 0 3.04-1.229t1.229-3.042q0-1.803-1.229-3.025-1.229-1.22-3.042-1.22t-3.03 1.22q-1.216 1.221-1.216 3.03 0 1.807 1.219 3.037 1.218 1.23 3.029 1.229m-.007-1.583q-1.14 0-1.9-.766-.758-.766-.758-1.912 0-1.148.759-1.91.758-.762 1.899-.762 1.149 0 1.92.762.773.762.773 1.91 0 1.146-.772 1.912t-1.921.766M3.75 20.833a1.52 1.52 0 0 1-1.112-.471 1.52 1.52 0 0 1-.471-1.112V6.854q0-.624.471-1.104t1.112-.48h3.5L8.58 3.716q.213-.27.53-.41.317-.138.68-.138h4.43q.356 0 .674.139t.537.41l1.321 1.555h3.5q.624 0 1.104.48t.48 1.103V19.25q0 .64-.48 1.112-.48.471-1.104.471zm0-1.583h16.5V6.854h-4.226L14.218 4.75H9.792L7.984 6.854H3.751z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/photo-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M5.308 20.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.308q0-.758.525-1.283T5.308 3.5h13.384q.758 0 1.283.525t.525 1.283v13.384q0 .758-.525 1.283t-1.283.525zm2.346-3.75h8.769q.27 0 .402-.246a.42.42 0 0 0-.04-.477L14.4 12.833a.44.44 0 0 0-.361-.181.44.44 0 0 0-.362.18l-2.446 3.187-1.639-2.1a.44.44 0 0 0-.357-.171.43.43 0 0 0-.356.181l-1.577 2.098q-.18.231-.05.477a.43.43 0 0 0 .402.246'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/photo.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M4.734 20.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V4.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h14.533q.64 0 1.112.471.471.471.471 1.112v14.534q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.47zm0-1.583h14.533V4.733H4.734zM6.863 17h10.31a.37.37 0 0 0 .348-.215.39.39 0 0 0-.026-.42l-2.827-3.755a.4.4 0 0 0-.318-.156.39.39 0 0 0-.316.154l-2.85 3.709-1.95-2.636a.39.39 0 0 0-.317-.156.39.39 0 0 0-.317.154l-2.034 2.686q-.165.206-.05.42a.37.37 0 0 0 .347.215'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/play-arrow-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M8.5 16.444V7.556q0-.396.272-.65a.9.9 0 0 1 .871-.221 1 1 0 0 1 .238.098l6.996 4.454a.9.9 0 0 1 .309.336q.102.196.102.427a.9.9 0 0 1-.102.427.9.9 0 0 1-.31.337l-6.995 4.453a.95.95 0 0 1-.476.131.9.9 0 0 1-.634-.254.85.85 0 0 1-.271-.65'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/play-arrow.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M8.117 17.313V6.578a.73.73 0 0 1 .24-.57.8.8 0 0 1 .56-.216q.096 0 .206.022a.7.7 0 0 1 .21.08l8.438 5.393q.18.133.273.298t.094.365a.7.7 0 0 1-.094.365.9.9 0 0 1-.273.29l-8.437 5.393a.6.6 0 0 1-.212.087 1 1 0 0 1-.201.023.81.81 0 0 1-.564-.216.74.74 0 0 1-.24-.579m1.583-1.43 6.184-3.933L9.7 8.017z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/play-circle-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m10.794 15.365 4.356-2.798a.64.64 0 0 0 .321-.566.64.64 0 0 0-.321-.568l-4.356-2.798a.62.62 0 0 0-.69-.037.64.64 0 0 0-.354.604v5.596q0 .41.354.604.354.195.69-.037m1.208 6.135a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/play-circle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m10.516 15.65 4.935-3.152a.56.56 0 0 0 .277-.499.58.58 0 0 0-.274-.503L10.516 8.35a.54.54 0 0 0-.604-.031.56.56 0 0 0-.313.53v6.297q0 .367.313.535t.604-.031M12 21.833a9.5 9.5 0 0 1-3.819-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.128 9.5 9.5 0 0 1-.777-3.814q0-2.04.777-3.836a9.9 9.9 0 0 1 2.113-3.124 10 10 0 0 1 3.128-2.102 9.5 9.5 0 0 1 3.814-.772q2.041 0 3.836.775a9.9 9.9 0 0 1 3.125 2.104 9.9 9.9 0 0 1 2.1 3.122 9.6 9.6 0 0 1 .773 3.831q0 2.024-.773 3.82a9.95 9.95 0 0 1-2.104 3.127 10 10 0 0 1-3.123 2.11 9.5 9.5 0 0 1-3.832.777M12 20.25q3.438 0 5.844-2.414t2.406-5.835q0-3.438-2.405-5.845Q15.437 3.75 12 3.75q-3.421 0-5.836 2.406T3.749 12q0 3.42 2.414 5.836Q8.578 20.25 12 20.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/poll-Icon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <G fill={color ?? \"#6852D6\"}>\n      <Path d='M2 5a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1M2 12a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1M2 19a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1' />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/poll-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2 5a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1M2 12a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1M2 19a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/poll.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2 5a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1M2 12a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1M2 19a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/presentation-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#FF9333'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path\n        fill='#fff'\n        d='M14.192 22.667v-4.09h2.913c2.287 0 3.444-.994 3.444-2.969q0-2.94-3.43-2.941h-4.07v10zm2.858-5.084h-2.858v-3.922h2.858c.794 0 1.38.155 1.77.49.39.295.586.785.586 1.457s-.195 1.163-.572 1.485c-.39.322-.976.49-1.784.49'\n      />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#FFD3AD' d='M22.266 9.333h4.533l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/rearrange-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      stroke={color}\n      strokeLinecap='round'\n      strokeWidth={1.5}\n      d='M4 6h16M4 10h16M4 14h16M4 18h16'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/rearrange.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      stroke={color}\n      strokeLinecap='round'\n      strokeWidth={1.5}\n      d='M4 6h16M4 10h16M4 14h16M4 18h16'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/refresh-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12.04 19.5q-3.142 0-5.321-2.18-2.18-2.178-2.18-5.318T6.72 6.68Q8.899 4.5 12.039 4.5q1.755 0 3.32.78a7.1 7.1 0 0 1 2.603 2.2V5.25q0-.319.216-.534a.73.73 0 0 1 .534-.216q.32 0 .535.216a.73.73 0 0 1 .215.534v4.461q0 .385-.26.644a.88.88 0 0 1-.644.26h-4.461a.73.73 0 0 1-.535-.215.73.73 0 0 1-.215-.535q0-.319.215-.534a.73.73 0 0 1 .535-.215h3.2a5.9 5.9 0 0 0-2.193-2.282A5.9 5.9 0 0 0 12.04 6q-2.5 0-4.25 1.75T6.04 12t1.75 4.25T12.04 18a5.86 5.86 0 0 0 3.192-.916 5.9 5.9 0 0 0 2.195-2.442.799.799 0 0 1 .98-.394.65.65 0 0 1 .428.4.66.66 0 0 1-.02.577 7.45 7.45 0 0 1-2.742 3.109Q14.257 19.5 12.039 19.5'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/refresh.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.013 19.85q-3.28 0-5.573-2.292Q4.146 15.268 4.146 12T6.44 6.442Q8.734 4.15 12.013 4.15q1.97 0 3.55.806a8.1 8.1 0 0 1 2.717 2.26V4.938q0-.33.225-.558a.76.76 0 0 1 .563-.23.77.77 0 0 1 .566.23.76.76 0 0 1 .23.558v4.8a.77.77 0 0 1-.23.567.77.77 0 0 1-.567.23h-4.8a.778.778 0 0 1-.787-.796q0-.33.225-.559a.76.76 0 0 1 .563-.23h3.328A7 7 0 0 0 15.27 6.61q-1.432-.876-3.257-.877-2.63 0-4.456 1.819T5.73 12t1.827 4.448 4.456 1.819q1.8 0 3.336-.94a6.04 6.04 0 0 0 2.319-2.535.83.83 0 0 1 .435-.421.77.77 0 0 1 .598-.009.74.74 0 0 1 .437.423q.12.299-.02.602a7.67 7.67 0 0 1-2.882 3.255q-1.91 1.208-4.223 1.208'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/reply-all-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m4.623 11 4.073 4.073a.7.7 0 0 1 .22.522.74.74 0 0 1-.235.532.78.78 0 0 1-.527.225.7.7 0 0 1-.527-.225l-4.494-4.494A.83.83 0 0 1 2.877 11q0-.18.058-.336a.8.8 0 0 1 .198-.297l4.494-4.494a.72.72 0 0 1 .514-.213.75.75 0 0 1 .54.213.74.74 0 0 1 .232.535q0 .302-.232.534zm5.308.75 3.323 3.323a.7.7 0 0 1 .22.522.74.74 0 0 1-.236.532.78.78 0 0 1-.527.225.7.7 0 0 1-.527-.225L7.69 11.633A.83.83 0 0 1 7.434 11q0-.18.058-.336a.8.8 0 0 1 .198-.297l4.494-4.494a.72.72 0 0 1 .515-.213.75.75 0 0 1 .54.213.74.74 0 0 1 .232.535q0 .302-.232.534L9.93 10.25h6.819q1.97 0 3.36 1.39T21.5 15v2.75a.73.73 0 0 1-.215.535.73.73 0 0 1-.535.215.73.73 0 0 1-.535-.215.73.73 0 0 1-.215-.535V15q0-1.347-.952-2.298a3.13 3.13 0 0 0-2.298-.952z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/reply-all.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m4.42 10.933 4.15 4.15a.72.72 0 0 1 .225.553.8.8 0 0 1-.245.552.77.77 0 0 1-.567.243.75.75 0 0 1-.567-.244l-4.7-4.7a.9.9 0 0 1-.18-.256.7.7 0 0 1-.061-.298q0-.162.06-.297.06-.136.181-.265l4.717-4.717a.8.8 0 0 1 .569-.233q.327 0 .564.233a.8.8 0 0 1 .234.573q0 .327-.238.565zm5.217.788 3.354 3.362a.74.74 0 0 1 .232.559.79.79 0 0 1-.244.558.82.82 0 0 1-.571.231.75.75 0 0 1-.563-.244l-4.7-4.7a.75.75 0 0 1-.185-.256.8.8 0 0 1-.056-.298q0-.162.056-.297.057-.136.185-.265l4.721-4.721a.75.75 0 0 1 .563-.236.83.83 0 0 1 .566.236.77.77 0 0 1 .238.564q0 .328-.238.565l-3.358 3.358h7.296q2.074 0 3.48 1.411 1.403 1.41 1.403 3.473v3.017a.77.77 0 0 1-.229.566.76.76 0 0 1-.558.23.77.77 0 0 1-.567-.23.77.77 0 0 1-.23-.566V15.02q0-1.417-.94-2.359-.943-.94-2.36-.941z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/reply-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m6.373 11.75 3.323 3.323a.7.7 0 0 1 .22.522.74.74 0 0 1-.235.532.78.78 0 0 1-.527.225.7.7 0 0 1-.527-.225l-4.494-4.494A.87.87 0 0 1 3.86 11q0-.361.272-.633l4.494-4.494a.72.72 0 0 1 .514-.213.75.75 0 0 1 .54.213.74.74 0 0 1 .232.535q0 .302-.232.534L6.373 10.25h9.377q1.97 0 3.36 1.39T20.5 15v2.75a.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215.73.73 0 0 1-.535-.215.73.73 0 0 1-.215-.535V15a3.13 3.13 0 0 0-.952-2.298 3.13 3.13 0 0 0-2.298-.952z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/reply.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m6.222 11.72 3.354 3.363a.74.74 0 0 1 .231.559.79.79 0 0 1-.243.558.82.82 0 0 1-.571.231.75.75 0 0 1-.563-.244l-4.7-4.7a.75.75 0 0 1-.246-.554q0-.316.246-.562L8.451 5.65a.75.75 0 0 1 .563-.236.83.83 0 0 1 .566.236.77.77 0 0 1 .238.564q0 .328-.238.565l-3.358 3.358h9.73q2.073 0 3.478 1.411 1.404 1.41 1.404 3.473v3.017a.77.77 0 0 1-.229.566.76.76 0 0 1-.558.23.77.77 0 0 1-.567-.23.77.77 0 0 1-.229-.566V15.02q0-1.417-.942-2.359-.94-.94-2.358-.941z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/retry-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.064 20.85a8.7 8.7 0 0 1-3.467-.698 9 9 0 0 1-2.829-1.902 8.9 8.9 0 0 1-1.906-2.822 8.6 8.6 0 0 1-.698-3.46q0-1.843.698-3.445A8.86 8.86 0 0 1 8.607 3.84a8.8 8.8 0 0 1 3.474-.69q1.97 0 3.731.858a9.2 9.2 0 0 1 3.035 2.375V4.637q0-.327.225-.557a.76.76 0 0 1 .563-.23q.338 0 .567.229t.229.555v3.703a.77.77 0 0 1-.23.567.77.77 0 0 1-.566.23h-3.717a.75.75 0 0 1-.562-.232.77.77 0 0 1-.225-.56q0-.33.225-.561a.75.75 0 0 1 .562-.231h1.83a8.3 8.3 0 0 0-2.531-2.048 6.64 6.64 0 0 0-3.134-.769q-3.04 0-5.188 2.086T4.747 11.92q0 3.054 2.13 5.2 2.131 2.145 5.187 2.146 2.73 0 4.731-1.775t2.38-4.437a.9.9 0 0 1 .303-.53.76.76 0 0 1 .556-.187q.342.016.56.267a.67.67 0 0 1 .166.566q-.425 3.275-2.883 5.477-2.457 2.202-5.813 2.202m.767-9.202 2.72 2.688a.78.78 0 0 1 .242.572q0 .33-.241.567a.77.77 0 0 1-1.118-.005l-2.952-2.936a.77.77 0 0 1-.235-.572V7.822q0-.329.225-.558a.76.76 0 0 1 .563-.23.77.77 0 0 1 .567.23.76.76 0 0 1 .229.558z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/retry.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.064 20.85a8.7 8.7 0 0 1-3.467-.698 9 9 0 0 1-2.829-1.902 8.9 8.9 0 0 1-1.906-2.822 8.6 8.6 0 0 1-.698-3.46q0-1.843.698-3.445A8.86 8.86 0 0 1 8.607 3.84a8.8 8.8 0 0 1 3.474-.69q1.97 0 3.731.858a9.2 9.2 0 0 1 3.035 2.375V4.637q0-.327.225-.557a.76.76 0 0 1 .563-.23q.338 0 .567.229t.229.555v3.703a.77.77 0 0 1-.23.567.77.77 0 0 1-.566.23h-3.717a.75.75 0 0 1-.562-.232.77.77 0 0 1-.225-.56q0-.33.225-.561a.75.75 0 0 1 .562-.231h1.83a8.3 8.3 0 0 0-2.531-2.048 6.64 6.64 0 0 0-3.134-.769q-3.04 0-5.188 2.086T4.747 11.92q0 3.054 2.13 5.2 2.131 2.145 5.187 2.146 2.73 0 4.731-1.775t2.38-4.437a.9.9 0 0 1 .303-.53.76.76 0 0 1 .556-.187q.342.016.56.267a.67.67 0 0 1 .166.566q-.425 3.275-2.883 5.477-2.457 2.202-5.813 2.202m.767-9.202 2.72 2.688a.78.78 0 0 1 .242.572q0 .33-.241.567a.77.77 0 0 1-1.118-.005l-2.952-2.936a.77.77 0 0 1-.235-.572V7.822q0-.329.225-.558a.76.76 0 0 1 .563-.23.77.77 0 0 1 .567.23.76.76 0 0 1 .229.558z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/right-arrow.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ height = 11, width = 11, color = \"#858585\" }: SvgProps) => (\n  <Svg width={width} height={height} fill=\"none\" viewBox=\"0 0 11 11\">\n    <Path\n      d=\"M8.70648 6.52443H0.809309C0.659309 6.52443 0.533364 6.47443 0.431475 6.37443C0.329698 6.27443 0.278809 6.14943 0.278809 5.99943C0.278809 5.84943 0.329698 5.72348 0.431475 5.62159C0.533364 5.51982 0.659309 5.46893 0.809309 5.46893H8.70648L5.12881 1.89109C5.02325 1.78554 4.96997 1.66237 4.96898 1.52159C4.96809 1.38093 5.02225 1.25593 5.13147 1.14659C5.23703 1.04293 5.3602 0.990649 5.50098 0.98976C5.64175 0.98876 5.76675 1.04104 5.87598 1.14659L10.3538 5.62443C10.4075 5.68187 10.4478 5.74065 10.4746 5.80076C10.5014 5.86098 10.5148 5.92721 10.5148 5.99943C10.5148 6.07165 10.5014 6.13787 10.4746 6.19809C10.4478 6.25821 10.4075 6.31515 10.3538 6.36893L5.87598 10.8466C5.76853 10.954 5.64398 11.0078 5.50231 11.0078C5.36064 11.0078 5.23703 10.954 5.13147 10.8466C5.02225 10.741 4.96764 10.6174 4.96764 10.4758C4.96764 10.3341 5.02225 10.2105 5.13147 10.1049L8.70648 6.52443Z\"\n      fill={color}\n    />\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/schedule-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12.75 11.696V7.75a.73.73 0 0 0-.216-.535A.73.73 0 0 0 12 7a.73.73 0 0 0-.534.215.73.73 0 0 0-.216.535v4.177a.899.899 0 0 0 .271.648L14.946 16q.208.209.522.212A.7.7 0 0 0 16 16a.72.72 0 0 0 .217-.527.72.72 0 0 0-.217-.527zm-.748 9.804a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/schedule.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12.851 11.683V7.621a.76.76 0 0 0-.23-.558.76.76 0 0 0-.557-.23.77.77 0 0 0-.567.23.76.76 0 0 0-.229.558v4.375q0 .158.056.296a1 1 0 0 0 .161.258l3.482 3.572a.72.72 0 0 0 .576.249.83.83 0 0 0 .588-.25.78.78 0 0 0 .233-.571.8.8 0 0 0-.233-.578zm-.849 10.15a9.5 9.5 0 0 1-3.819-.777 10 10 0 0 1-3.128-2.113 10 10 0 0 1-2.11-3.128 9.5 9.5 0 0 1-.777-3.814q0-2.024.777-3.82a10 10 0 0 1 2.113-3.127 10 10 0 0 1 3.128-2.11A9.5 9.5 0 0 1 12 2.167q2.025 0 3.82.777a10 10 0 0 1 3.128 2.113q1.333 1.335 2.11 3.127A9.5 9.5 0 0 1 21.834 12a9.5 9.5 0 0 1-.777 3.82 10 10 0 0 1-2.112 3.127 10 10 0 0 1-3.128 2.11 9.5 9.5 0 0 1-3.815.777m-.005-1.583q3.401 0 5.827-2.423t2.427-5.823-2.427-5.827Q15.4 3.75 12.002 3.75q-3.405 0-5.827 2.427T3.751 12q0 3.405 2.423 5.827t5.823 2.423'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/screen-share-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.135 10.808h2v1.111q0 .156.135.214t.251-.058l1.696-1.696q.14-.14.14-.321a.44.44 0 0 0-.14-.322L13.521 8.04q-.115-.115-.25-.057-.136.058-.136.213v1.112h-2q-1.144 0-1.947.803a2.65 2.65 0 0 0-.803 1.947v1.25q0 .319.215.534a.73.73 0 0 0 .535.216q.318 0 .534-.216a.73.73 0 0 0 .216-.534v-1.25q0-.522.364-.886t.886-.364m-9 9.423a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.535a.73.73 0 0 1 .535-.215h19.73q.32 0 .535.215a.73.73 0 0 1 .215.535.73.73 0 0 1-.215.534.73.73 0 0 1-.535.216zm2.173-2.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V5.539q0-.758.525-1.283t1.283-.525h15.384q.758 0 1.283.525t.525 1.282v10.385q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/screen-share.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.186 10.754h1.98v1.292q0 .146.117.193.116.048.231-.066l1.93-1.921a.4.4 0 0 0 .122-.29q0-.16-.121-.279L13.512 7.75q-.113-.105-.23-.06-.116.046-.116.192v1.289h-1.987q-1.204 0-2.042.84a2.79 2.79 0 0 0-.838 2.043V13.4a.75.75 0 0 0 .234.56.78.78 0 0 0 .56.227.77.77 0 0 0 .559-.227.76.76 0 0 0 .23-.56v-1.351q0-.54.375-.917.375-.378.928-.378M1.961 20.8a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.563q0-.33.231-.56a.77.77 0 0 1 .565-.228h20.083q.324 0 .556.232.232.233.232.558a.76.76 0 0 1-.232.564.76.76 0 0 1-.556.229zm1.787-2.9a1.52 1.52 0 0 1-1.112-.471 1.52 1.52 0 0 1-.471-1.112V4.767q0-.641.471-1.112a1.52 1.52 0 0 1 1.112-.472h16.5q.64 0 1.112.472.471.47.471 1.112v11.55q0 .64-.47 1.112a1.52 1.52 0 0 1-1.113.47zm0-1.583h16.5V4.767H3.75z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/search-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M9.52 15.615q-2.562 0-4.339-1.777T3.404 9.5t1.777-4.339T9.52 3.385q2.56 0 4.338 1.776 1.777 1.777 1.777 4.339 0 1.071-.36 2.046t-.96 1.696l5.755 5.754q.207.209.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217l-5.754-5.754q-.75.62-1.725.97t-2.017.35m0-1.5q1.932 0 3.274-1.341 1.341-1.34 1.341-3.274t-1.341-3.274Q11.452 4.885 9.52 4.884q-1.933 0-3.274 1.342Q4.904 7.566 4.904 9.5t1.342 3.274q1.34 1.342 3.274 1.342'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/search.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M9.5 15.75q-2.663 0-4.508-1.845t-1.846-4.47q0-2.627 1.841-4.472t4.48-1.846q2.637 0 4.476 1.846 1.837 1.845 1.837 4.476 0 1.061-.346 2.047a6.3 6.3 0 0 1-1 1.814l5.865 5.82q.231.23.231.568t-.238.579a.78.78 0 0 1-.578.241.74.74 0 0 1-.564-.238l-5.845-5.85a5.3 5.3 0 0 1-1.729.986q-.987.345-2.076.344m-.02-1.583q1.967 0 3.342-1.382 1.375-1.383 1.375-3.353t-1.375-3.35T9.476 4.7q-1.984 0-3.366 1.382-1.38 1.383-1.38 3.352 0 1.97 1.381 3.352 1.382 1.38 3.369 1.38'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/send-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.767 18.723a.9.9 0 0 1-.86-.077.84.84 0 0 1-.407-.752v-4.221L12.423 12 5.5 10.327V6.106q0-.494.407-.752a.9.9 0 0 1 .86-.077l13.956 5.885q.558.249.558.84 0 .59-.558.836z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/send.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M22 12.725 6.245 19.362a.77.77 0 0 1-.757-.062.73.73 0 0 1-.355-.646V5.329q0-.417.355-.646a.79.79 0 0 1 .757-.07L22 11.257q.474.207.475.732 0 .527-.475.735m-15.284 4.68 12.9-5.418-12.9-5.466v3.966l5.917 1.5-5.917 1.467z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/settings-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M10.893 21.5q-.512 0-.884-.34a1.4 1.4 0 0 1-.453-.835l-.244-1.871a5.334 5.334 0 0 1-1.579-.896L6 18.294q-.471.208-.947.04a1.37 1.37 0 0 1-.739-.607l-1.127-1.954a1.22 1.22 0 0 1-.152-.932 1.4 1.4 0 0 1 .512-.81l1.498-1.125a5 5 0 0 1-.05-.448 7 7 0 0 1 0-.882q.015-.22.05-.482L3.547 9.97a1.4 1.4 0 0 1-.507-.815 1.25 1.25 0 0 1 .157-.937l1.117-1.925q.263-.43.74-.602A1.23 1.23 0 0 1 6 5.725l1.723.727a6.4 6.4 0 0 1 .773-.524 5 5 0 0 1 .806-.382l.254-1.871q.081-.496.453-.835.372-.34.884-.34h2.215q.512 0 .884.34.372.339.453.835l.244 1.88q.45.165.814.383.365.218.736.514l1.771-.727q.471-.207.947-.035a1.4 1.4 0 0 1 .74.602l1.117 1.935q.264.44.152.932a1.4 1.4 0 0 1-.512.81l-1.536 1.154q.054.243.059.453t.004.424q0 .204-.01.415-.009.21-.069.481l1.508 1.135q.4.32.517.81.116.492-.148.932l-1.133 1.944a1.37 1.37 0 0 1-.744.608q-.48.167-.951-.04l-1.712-.737q-.372.296-.758.524a4.6 4.6 0 0 1-.792.372l-.244 1.881q-.081.496-.453.835-.372.34-.884.34zm1.119-6.5q1.248 0 2.124-.876A2.9 2.9 0 0 0 15.012 12q0-1.248-.876-2.124A2.9 2.9 0 0 0 12.012 9q-1.263 0-2.132.876A2.9 2.9 0 0 0 9.012 12q0 1.248.868 2.124.87.876 2.132.876'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/settings.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M10.526 21.833q-.375 0-.665-.243a.95.95 0 0 1-.34-.623L9.18 18.72a7 7 0 0 1-.903-.427 6 6 0 0 1-.835-.548l-2.054.917a.97.97 0 0 1-.738.04 1.04 1.04 0 0 1-.57-.461l-1.471-2.596a.92.92 0 0 1-.119-.709 1.08 1.08 0 0 1 .406-.624l1.892-1.405a2.6 2.6 0 0 1-.05-.437 11.233 11.233 0 0 1 .002-.917q.01-.255.052-.496L2.913 9.675a1.07 1.07 0 0 1-.413-.614.91.91 0 0 1 .121-.72L4.08 5.759q.206-.336.578-.462a.99.99 0 0 1 .734.033l2.08.933q.333-.274.789-.537t.919-.42l.341-2.288a.95.95 0 0 1 .342-.623q.292-.244.675-.244h2.938q.375 0 .664.244.29.243.348.623l.337 2.247q.438.165.899.425.46.261.802.573l2.095-.933a.95.95 0 0 1 .721-.033q.36.126.571.462l1.475 2.584q.204.341.112.724a1.02 1.02 0 0 1-.404.609l-1.925 1.383q.034.226.046.46a9 9 0 0 1 0 .961 4 4 0 0 1-.05.45l1.909 1.383q.32.236.408.618a.93.93 0 0 1-.117.716l-1.454 2.596a1.1 1.1 0 0 1-.587.462.96.96 0 0 1-.734-.041l-2.066-.934q-.33.279-.761.538-.432.258-.935.45l-.342 2.25a.98.98 0 0 1-.348.623 1 1 0 0 1-.665.243zm.415-1.583h2.101l.363-2.762a5.7 5.7 0 0 0 1.475-.598 6 6 0 0 0 1.262-.982l2.604 1.125.98-1.75-2.3-1.691q.095-.4.158-.792.062-.392.062-.8 0-.413-.047-.798a4.7 4.7 0 0 0-.174-.794l2.3-1.708-.962-1.75-2.625 1.133A5.5 5.5 0 0 0 14.9 7.071a4.4 4.4 0 0 0-1.495-.567l-.34-2.77h-2.123L10.605 6.5a5.6 5.6 0 0 0-1.517.577 5.7 5.7 0 0 0-1.263.998L5.246 6.95 4.26 8.7l2.279 1.67a8 8 0 0 0-.167.818 5.4 5.4 0 0 0-.058.799q0 .409.052.8.052.393.16.821L4.26 15.283l.99 1.75 2.572-1.116q.604.603 1.294.996.69.39 1.49.587zm1.044-5q1.345 0 2.295-.951t.95-2.3q0-1.35-.951-2.299a3.13 3.13 0 0 0-2.3-.95q-1.362 0-2.306.951-.943.951-.943 2.3 0 1.35.943 2.299.944.95 2.313.95'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/share-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M16.806 21.5a2.6 2.6 0 0 1-1.907-.785 2.6 2.6 0 0 1-.783-1.907q0-.15.103-.73l-7.111-4.186q-.362.375-.86.588a2.7 2.7 0 0 1-1.065.212 2.58 2.58 0 0 1-1.9-.788A2.6 2.6 0 0 1 2.5 12q0-1.115.783-1.904a2.58 2.58 0 0 1 1.9-.788q.568 0 1.066.212.497.213.859.588l7.111-4.177a2 2 0 0 1-.082-.362 3 3 0 0 1-.021-.377q0-1.122.785-1.907A2.6 2.6 0 0 1 16.81 2.5a2.6 2.6 0 0 1 1.906.786q.784.786.784 1.908 0 1.124-.785 1.907a2.6 2.6 0 0 1-1.907.784q-.571 0-1.063-.218a2.7 2.7 0 0 1-.853-.592l-7.111 4.187q.06.184.082.361.021.177.021.377t-.022.377a2 2 0 0 1-.081.361l7.111 4.187q.362-.375.853-.592a2.6 2.6 0 0 1 1.063-.217q1.122 0 1.907.785a2.6 2.6 0 0 1 .785 1.909 2.6 2.6 0 0 1-.786 1.906 2.6 2.6 0 0 1-1.908.784'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/share.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M17.01 21.833a2.73 2.73 0 0 1-2-.825 2.73 2.73 0 0 1-.828-2.004q0-.215.121-.8l-7.274-4.25a3 3 0 0 1-.923.645 2.7 2.7 0 0 1-1.127.234 2.7 2.7 0 0 1-1.993-.823 2.73 2.73 0 0 1-.82-2.004q0-1.168.822-2.002a2.7 2.7 0 0 1 1.995-.833q.587 0 1.102.223.515.224.899.61l7.32-4.218a2.588 2.588 0 0 1-.121-.798q0-1.17.824-1.996a2.73 2.73 0 0 1 2.002-.825q1.17 0 1.997.825a2.72 2.72 0 0 1 .826 2q0 1.178-.825 2.003t-1.998.826q-.575 0-1.082-.217a2.9 2.9 0 0 1-.894-.6l-7.359 4.15a2.83 2.83 0 0 1 .046 1.595l7.328 4.222a2.9 2.9 0 0 1 .891-.588 2.7 2.7 0 0 1 1.075-.212q1.176 0 1.997.827a2.74 2.74 0 0 1 .822 2.005q0 1.176-.825 2.003a2.72 2.72 0 0 1-1.998.827m-.002-1.583q.534 0 .888-.354.353-.354.353-.888 0-.53-.356-.891a1.19 1.19 0 0 0-.882-.363 1.2 1.2 0 0 0-.892.365 1.23 1.23 0 0 0-.353.886 1.21 1.21 0 0 0 1.242 1.245m-12.025-7q.525 0 .885-.354t.36-.888q0-.53-.358-.891a1.2 1.2 0 0 0-.888-.363q-.524 0-.878.365a1.22 1.22 0 0 0-.355.886q0 .524.355.884t.88.361m12.025-7.012q.534 0 .888-.36.353-.359.353-.887t-.356-.885a1.2 1.2 0 0 0-.882-.356q-.538 0-.892.356a1.2 1.2 0 0 0-.353.882 1.22 1.22 0 0 0 1.242 1.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/shield-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12 21.371a1.9 1.9 0 0 1-.596-.1q-3.164-1.125-5.034-3.99Q4.5 14.418 4.5 11.1V6.596q0-.567.329-1.024.33-.456.846-.662l5.692-2.125q.321-.116.633-.116t.633.116l5.692 2.125q.517.206.846.662.33.457.329 1.024V11.1q0 3.318-1.87 6.182t-5.034 3.989a1.7 1.7 0 0 1-.596.1'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/shield.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M12 21.371a1.9 1.9 0 0 1-.596-.1q-3.164-1.125-5.034-3.99Q4.5 14.418 4.5 11.1V6.596q0-.57.328-1.025.327-.457.847-.661l5.692-2.125q.321-.116.633-.116t.633.116l5.692 2.125q.52.205.847.66.328.457.328 1.026V11.1q0 3.318-1.87 6.182t-5.034 3.989a1.7 1.7 0 0 1-.596.1m0-1.471q2.6-.825 4.3-3.3t1.7-5.5V6.587a.3.3 0 0 0-.053-.173.3.3 0 0 0-.149-.116l-5.692-2.125a.3.3 0 0 0-.106-.02.3.3 0 0 0-.106.02L6.202 6.298a.3.3 0 0 0-.149.115.3.3 0 0 0-.053.173V11.1q0 3.025 1.7 5.5t4.3 3.3'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/smileys-&-people.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G clipPath='url(#prefix__a)'>\n      <Path\n        fill={color ?? \"#A1A1A1\"}\n        d='M9 18.034A8.533 8.533 0 1 1 17.534 9.5 8.543 8.543 0 0 1 9 18.034m.01-16A7.467 7.467 0 1 0 16.478 9.5a7.475 7.475 0 0 0-7.466-7.467m0 14a5.34 5.34 0 0 1-5.333-5.333c0-.134.096-.197.301-.197q.625.058 1.227.231h.01c1.238.335 2.513.52 3.796.552a16 16 0 0 0 3.795-.552h.01l.022-.005a6.5 6.5 0 0 1 1.204-.226c.205 0 .301.063.301.197a5.34 5.34 0 0 1-5.336 5.333zM4.909 11.61c-.12 0-.175.04-.175.125 0 1.763 2.32 2.133 4.267 2.133a9.4 9.4 0 0 0 2.623-.3c1.091-.335 1.644-.953 1.644-1.828q.002-.126-.175-.125a4 4 0 0 0-.788.165A12.5 12.5 0 0 1 9 12.274a12.5 12.5 0 0 1-3.307-.494 4 4 0 0 0-.785-.17m7.026-3.177c-.442 0-.8-.477-.8-1.066s.358-1.067.8-1.067c.441 0 .8.48.8 1.067 0 .588-.36 1.066-.8 1.066m-5.334 0c-.441 0-.8-.477-.8-1.066S6.16 6.3 6.598 6.3s.799.48.799 1.067c0 .588-.358 1.066-.799 1.066z'\n      />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/spreadsheet-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#32BD7A'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path\n        fill='#fff'\n        d='m13.007 22.667 2.986-4.286 3.001 4.286h1.423l-3.734-5.196 3.465-4.804h-1.422l-2.733 3.894-2.719-3.894h-1.422l3.437 4.804-3.705 5.196z'\n      />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#EBFFEC' d='M22.267 9.333h4.534l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/star-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m12 16.689-3.89 2.346a.72.72 0 0 1-.436.106.8.8 0 0 1-.395-.147.84.84 0 0 1-.274-.331.65.65 0 0 1-.03-.454l1.032-4.417-3.434-2.973a.74.74 0 0 1-.245-.387.7.7 0 0 1 .027-.428.85.85 0 0 1 .233-.34.8.8 0 0 1 .416-.172l4.532-.396 1.76-4.171a.68.68 0 0 1 .291-.344.82.82 0 0 1 .825 0q.195.111.291.344l1.76 4.171 4.533.396a.8.8 0 0 1 .415.172q.152.132.233.34a.7.7 0 0 1 .028.428.74.74 0 0 1-.246.387l-3.434 2.973 1.033 4.417a.65.65 0 0 1-.031.454.84.84 0 0 1-.273.331.8.8 0 0 1-.395.147.72.72 0 0 1-.436-.106z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/star.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m8.268 17.613 3.733-2.238 3.734 2.258-.996-4.233 3.292-2.85-4.334-.388-1.695-4-1.688 3.988-4.333.375 3.291 2.846zm3.733-.388-4.504 2.717a.75.75 0 0 1-.46.12.76.76 0 0 1-.419-.154.9.9 0 0 1-.275-.346.7.7 0 0 1-.029-.475l1.187-5.129L3.526 10.5a.75.75 0 0 1-.254-.402.9.9 0 0 1 .013-.44.7.7 0 0 1 .248-.362.86.86 0 0 1 .448-.175l5.25-.459 2.041-4.837a.73.73 0 0 1 .304-.358.8.8 0 0 1 .425-.121q.222 0 .426.12.204.121.312.359l2.042 4.837 5.25.459q.255.03.44.175a.7.7 0 0 1 .247.362.9.9 0 0 1 .013.44.75.75 0 0 1-.255.402l-3.974 3.458 1.195 5.13a.73.73 0 0 1-.037.474.9.9 0 0 1-.275.346.76.76 0 0 1-.871.034z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/sticker-fill.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color ?? \"#212121\"}\n      d='M4 17.867V6.133C4 4.955 4.955 4 6.133 4h11.734C19.045 4 20 4.955 20 6.133v6.4h-3.2a3.733 3.733 0 0 0-3.733 3.734V20H6.133A2.133 2.133 0 0 1 4 17.867'\n    />\n    <Path fill={color ?? \"#212121\"} d='M19.97 13.6H16.8a2.667 2.667 0 0 0-2.667 2.667V20h.042z' />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/sticker.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.8 3.6a3.6 3.6 0 0 1 3.593 3.388l.007.212v5.254a2.4 2.4 0 0 1-.56 1.542l-.143.155-5.546 5.546c-.4.4-.927.644-1.486.694l-.211.009H7.2a3.6 3.6 0 0 1-3.594-3.388L3.6 16.8V7.2a3.6 3.6 0 0 1 3.388-3.594L7.2 3.6zm0 1.2H7.2a2.4 2.4 0 0 0-2.394 2.22L4.8 7.2v9.6a2.4 2.4 0 0 0 2.22 2.393l.18.007H12v-3.6a3.6 3.6 0 0 1 3.388-3.594L15.6 12h3.6V7.2a2.4 2.4 0 0 0-2.221-2.393zm2.139 8.402-3.34-.002a2.4 2.4 0 0 0-2.393 2.22l-.006.18v3.337l.103-.088 5.545-5.546q.049-.048.09-.101'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/stop-circle-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M9.154 15.75h5.692q.384 0 .644-.26a.88.88 0 0 0 .26-.644V9.154a.88.88 0 0 0-.26-.644.88.88 0 0 0-.644-.26H9.154a.88.88 0 0 0-.644.26.88.88 0 0 0-.26.644v5.692q0 .384.26.644t.644.26m2.848 5.75a9.3 9.3 0 0 1-3.706-.748 9.6 9.6 0 0 1-3.016-2.03 9.6 9.6 0 0 1-2.032-3.016 9.25 9.25 0 0 1-.748-3.704q0-1.972.748-3.706a9.6 9.6 0 0 1 2.03-3.016 9.6 9.6 0 0 1 3.016-2.032 9.25 9.25 0 0 1 3.704-.748q1.972 0 3.706.748a9.6 9.6 0 0 1 3.017 2.03 9.6 9.6 0 0 1 2.03 3.016 9.25 9.25 0 0 1 .749 3.704q0 1.972-.748 3.706a9.6 9.6 0 0 1-2.03 3.017 9.6 9.6 0 0 1-3.016 2.03 9.25 9.25 0 0 1-3.704.749'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/stop-circle.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M9 15.788h6q.332 0 .564-.228a.76.76 0 0 0 .231-.56V9a.77.77 0 0 0-.231-.565.77.77 0 0 0-.565-.23H9a.76.76 0 0 0-.56.23.78.78 0 0 0-.227.565v6q0 .333.227.56a.76.76 0 0 0 .56.227m3.005 6.045a9.5 9.5 0 0 1-3.828-.777 10 10 0 0 1-3.124-2.113 10 10 0 0 1-2.11-3.125 9.5 9.5 0 0 1-.777-3.825q0-2.038.777-3.83A9.9 9.9 0 0 1 5.056 5.04 10 10 0 0 1 8.18 2.939a9.55 9.55 0 0 1 3.826-.772q2.037 0 3.83.775a9.9 9.9 0 0 1 3.122 2.104 9.9 9.9 0 0 1 2.1 3.122 9.6 9.6 0 0 1 .773 3.827 9.6 9.6 0 0 1-.773 3.827 9.9 9.9 0 0 1-2.104 3.122 10 10 0 0 1-3.123 2.11q-1.793.78-3.827.78m.002-1.583q3.43 0 5.836-2.414t2.406-5.844-2.402-5.836T12 3.75q-3.421 0-5.836 2.403T3.749 12q0 3.42 2.414 5.836 2.415 2.415 5.844 2.414'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/stop-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill=\"none\" viewBox=\"0 0 24 24\">\n    <Mask\n      id=\"prefix__a\"\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits=\"userSpaceOnUse\"\n      style={{ maskType: \"alpha\" }}\n    >\n      <Path fill=\"#D9D9D9\" d=\"M0 0h24v24H0z\" />\n    </Mask>\n    <G mask=\"url(#prefix__a)\">\n      {/* Scale the inner square around center */}\n      <G transform=\"scale(1.2) translate(-2,-2)\">\n        <Path\n          fill={color}\n          d=\"M6.5 15.692V8.308q0-.746.531-1.277A1.74 1.74 0 0 1 8.308 6.5h7.384q.746 0 1.277.531t.531 1.277v7.384q0 .746-.531 1.277a1.74 1.74 0 0 1-1.277.531H8.308a1.74 1.74 0 0 1-1.277-.531 1.74 1.74 0 0 1-.531-1.277\"\n        />\n      </G>\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/stop-screen-share-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.124 20.23H2.134a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.534q0-.32.216-.535a.73.73 0 0 1 .534-.215h14.473l-1-1h-11.3q-.757 0-1.282-.525a1.75 1.75 0 0 1-.525-1.283V5.446q0-.164.086-.293a.65.65 0 0 1 .221-.207l-1.25-1.265a.76.76 0 0 1-.212-.522.72.72 0 0 1 .228-.532.72.72 0 0 1 .527-.218q.31 0 .527.218l18.746 18.746q.207.208.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217zm-8.085-8.063-1.306-1.32q-.163.225-.255.532a2.4 2.4 0 0 0-.093.679v1.25q0 .319.216.534a.73.73 0 0 0 .534.216q.32 0 .535-.216a.73.73 0 0 0 .215-.534v-.727a.6.6 0 0 1 .039-.231.6.6 0 0 1 .115-.183m10.373 5.394-6.179-6.178.994-1.004a.44.44 0 0 0 .14-.316.44.44 0 0 0-.14-.317L13.522 8.04q-.115-.115-.251-.057-.135.058-.136.213v1.112h-.977L6.581 3.73h13.112q.757 0 1.282.525t.525 1.282v10.385q0 .534-.285.974t-.803.665'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/stop-screen-share.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M13.906 11.792 11.284 9.17h1.884V7.888q0-.146.117-.194.116-.048.229.056l1.931 1.932a.4.4 0 0 1 .123.289q0 .16-.121.279zm6.228 6.241-1.566-1.566h1.583v-11.6H6.968L5.384 3.283h14.767q.64 0 1.112.472.471.471.471 1.112v11.6q0 .649-.464 1.103-.464.455-1.136.463m-1.833 2.684H1.964a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.563q0-.33.231-.56a.77.77 0 0 1 .565-.229h14.758l-1.083-1.083H3.868q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V4.79q0-.061.013-.086l-.82-.816a.77.77 0 0 1-.243-.566q0-.327.246-.568a.77.77 0 0 1 .563-.233q.33 0 .567.23l18.707 18.707a.78.78 0 0 1 .23.559.76.76 0 0 1-.233.554.78.78 0 0 1-.571.241.75.75 0 0 1-.562-.241zM9.884 12.3v1.104q0 .333-.232.565a.76.76 0 0 1-.558.231.76.76 0 0 1-.564-.231.77.77 0 0 1-.229-.565V12.05q0-.258.048-.55.048-.29.173-.559L3.868 6.279v10.188h10.183z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/stop.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M6.117 16.3V7.7q0-.653.465-1.118T7.7 6.117h8.6q.654 0 1.119.465t.465 1.118v8.6q0 .654-.465 1.118t-1.119.465H7.7q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118m1.583 0h8.6V7.7H7.7z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/subdirectory-arrow-left-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m7.373 15.75 3.154 3.154a.76.76 0 0 1 .236.522.69.69 0 0 1-.22.532.74.74 0 0 1-.535.232.74.74 0 0 1-.535-.232l-4.34-4.325A.83.83 0 0 1 4.877 15q0-.18.058-.336a.8.8 0 0 1 .198-.297l4.355-4.356a.7.7 0 0 1 .523-.22q.299.003.531.236.217.232.225.527a.7.7 0 0 1-.225.527L7.373 14.25h9.32a.3.3 0 0 0 .22-.086.3.3 0 0 0 .087-.222V5.25q0-.32.215-.535a.73.73 0 0 1 .535-.215q.32 0 .535.215a.73.73 0 0 1 .215.535v8.692q0 .749-.53 1.278-.53.53-1.278.53z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/subdirectory-arrow-left.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m7.192 13.917 3.308 3.308a.82.82 0 0 1 .254.583.78.78 0 0 1-.246.584.82.82 0 0 1-.596.25.81.81 0 0 1-.595-.25l-4.734-4.759a.8.8 0 0 1-.187-.277.9.9 0 0 1-.054-.308.9.9 0 0 1 .054-.306.8.8 0 0 1 .187-.275l4.725-4.725a.8.8 0 0 1 .592-.246.85.85 0 0 1 .598.254.85.85 0 0 1 .24.592.8.8 0 0 1-.246.591L7.175 12.25h10.158V2.833q0-.354.24-.594A.8.8 0 0 1 18.17 2q.356 0 .593.24a.8.8 0 0 1 .238.593v9.417q0 .687-.49 1.177-.489.49-1.177.49z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/subdirectory-arrow-right-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M13.473 19.958a.72.72 0 0 1-.228-.532.73.73 0 0 1 .228-.522l3.154-3.154h-9.32q-.747 0-1.277-.53a1.74 1.74 0 0 1-.53-1.278V5.25q0-.32.215-.535A.73.73 0 0 1 6.25 4.5q.32 0 .535.215A.73.73 0 0 1 7 5.25v8.692a.3.3 0 0 0 .087.222.3.3 0 0 0 .22.086h9.32l-3.17-3.17a.7.7 0 0 1-.224-.526.78.78 0 0 1 .225-.527.74.74 0 0 1 .531-.236q.3-.003.522.22l4.356 4.356a.83.83 0 0 1 .256.633.828.828 0 0 1-.256.633l-4.325 4.325a.74.74 0 0 1-.534.232.74.74 0 0 1-.535-.232'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/subdirectory-arrow-right.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M13.5 18.392a.8.8 0 0 1-.25-.584.8.8 0 0 1 .25-.583l3.308-3.308H6.667q-.688 0-1.178-.49A1.6 1.6 0 0 1 5 12.25V2.833q0-.354.24-.594A.8.8 0 0 1 5.837 2q.356 0 .593.24a.8.8 0 0 1 .238.594v9.417h10.158l-3.3-3.3a.8.8 0 0 1-.246-.592.85.85 0 0 1 .24-.591.87.87 0 0 1 .59-.263.76.76 0 0 1 .583.238l4.725 4.725a.8.8 0 0 1 .187.277.9.9 0 0 1 .054.308q0 .165-.054.306a.8.8 0 0 1-.187.275l-4.725 4.759a.82.82 0 0 1-.597.25.81.81 0 0 1-.595-.25'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/sunny-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 5.212a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V2.269q0-.318.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v2.193q0 .318-.216.534a.73.73 0 0 1-.534.216m4.797 1.992a.68.68 0 0 1-.204-.514.8.8 0 0 1 .213-.54l1.525-1.56a.72.72 0 0 1 .536-.232q.31 0 .545.235a.71.71 0 0 1 .215.524.72.72 0 0 1-.217.527l-1.56 1.56a.72.72 0 0 1-.527.217.72.72 0 0 1-.526-.217m2.742 5.546a.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534q0-.32.215-.534a.73.73 0 0 1 .535-.216h2.192q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.534a.73.73 0 0 1-.534.216zM12 22.48a.72.72 0 0 1-.534-.215.73.73 0 0 1-.216-.534v-2.183q0-.318.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v2.183q0 .318-.216.534a.73.73 0 0 1-.534.216M6.16 7.205 4.59 5.669a.73.73 0 0 1-.232-.543q0-.318.233-.535a.74.74 0 0 1 .533-.208.7.7 0 0 1 .52.208l1.57 1.569a.7.7 0 0 1 .208.52q0 .316-.206.524a.776.776 0 0 1-1.056 0M18.34 19.41l-1.534-1.56a.8.8 0 0 1-.213-.54.69.69 0 0 1 .201-.514.7.7 0 0 1 .515-.207q.309 0 .541.217l1.56 1.534a.66.66 0 0 1 .215.523.82.82 0 0 1-.218.547.74.74 0 0 1-.546.232.66.66 0 0 1-.52-.232M2.27 12.75a.73.73 0 0 1-.535-.216A.73.73 0 0 1 1.52 12q0-.32.215-.534a.73.73 0 0 1 .535-.216h2.192q.318 0 .534.216a.73.73 0 0 1 .216.534q0 .32-.216.534a.73.73 0 0 1-.534.216zm2.33 6.66a.76.76 0 0 1-.212-.532.7.7 0 0 1 .203-.522l1.55-1.55a.7.7 0 0 1 .519-.218.764.764 0 0 1 .767.757.74.74 0 0 1-.232.54L5.67 19.409a.75.75 0 0 1-.55.233.66.66 0 0 1-.52-.232m7.403-1.91q-2.291 0-3.897-1.604T6.5 12.003t1.604-3.896T11.998 6.5t3.897 1.604Q17.5 9.708 17.5 11.998t-1.603 3.896-3.895 1.606'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/sunny.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.997 15.37a3.27 3.27 0 0 0 2.392-.982q.99-.982.99-2.386 0-1.402-.985-2.392a3.25 3.25 0 0 0-2.39-.99q-1.404 0-2.39.985t-.986 2.39q0 1.405.983 2.39t2.386.986M12 16.955q-2.06 0-3.508-1.449T7.045 12t1.449-3.51T12 7.037t3.51 1.451 1.452 3.51-1.451 3.508T12 16.954M1.832 12.787a.76.76 0 0 1-.56-.228.77.77 0 0 1-.227-.562.77.77 0 0 1 .227-.564.76.76 0 0 1 .56-.229h2.334q.333 0 .564.232a.77.77 0 0 1 .232.567q0 .335-.232.56a.78.78 0 0 1-.564.224zm18 0a.76.76 0 0 1-.56-.228.77.77 0 0 1-.227-.562.77.77 0 0 1 .227-.564.76.76 0 0 1 .56-.229h2.334q.333 0 .564.232a.77.77 0 0 1 .232.567q0 .335-.232.56a.78.78 0 0 1-.564.224zm-7.836-7.833a.76.76 0 0 1-.56-.227.77.77 0 0 1-.224-.56V1.833q0-.333.228-.564a.76.76 0 0 1 .562-.232q.334 0 .564.232a.77.77 0 0 1 .229.564v2.334a.75.75 0 0 1-.232.56.78.78 0 0 1-.567.227m0 18a.76.76 0 0 1-.56-.227.77.77 0 0 1-.224-.56v-2.334q0-.333.228-.564a.76.76 0 0 1 .562-.232q.334 0 .564.232a.77.77 0 0 1 .229.564v2.334a.75.75 0 0 1-.232.56.78.78 0 0 1-.567.227M5.912 7.017 4.603 5.733a.7.7 0 0 1-.235-.56.82.82 0 0 1 .242-.567.77.77 0 0 1 .56-.242.73.73 0 0 1 .562.232l1.284 1.308a.8.8 0 0 1 0 1.11.7.7 0 0 1-.544.23.82.82 0 0 1-.56-.227m12.354 12.379-1.284-1.309a.8.8 0 0 1-.222-.562.74.74 0 0 1 .23-.554.68.68 0 0 1 .54-.227q.318.01.565.24l1.308 1.283q.238.228.227.56a.82.82 0 0 1-.241.566.77.77 0 0 1-.561.242.72.72 0 0 1-.562-.24m-1.29-12.38a.7.7 0 0 1-.233-.547.85.85 0 0 1 .24-.565l1.283-1.308a.71.71 0 0 1 .56-.227.82.82 0 0 1 .567.241.77.77 0 0 1 .242.561.73.73 0 0 1-.232.562l-1.308 1.284a.75.75 0 0 1-.549.225.82.82 0 0 1-.57-.225M4.606 19.395a.79.79 0 0 1-.242-.565.72.72 0 0 1 .24-.562l1.308-1.284a.8.8 0 0 1 .554-.235.73.73 0 0 1 .556.235q.244.23.233.548a.82.82 0 0 1-.24.556l-1.283 1.309a.7.7 0 0 1-.56.233.85.85 0 0 1-.566-.235'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/symbols.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G fill={color ?? \"#A1A1A1\"} clipPath='url(#prefix__a)'>\n      <Path d='m10.015 18.034 2.98-3.891 2.733-3.623h1.31l-3.024 3.748-2.651 3.734zm-5.902-.023c-.31.034-.623.006-.92-.083a2.3 2.3 0 0 1-.81-.43 2.2 2.2 0 0 1-.56-.705 2.1 2.1 0 0 1-.217-.861c.012-.425.154-.837.407-1.185a2.26 2.26 0 0 1 1.018-.778q.144-.081.294-.147a2.47 2.47 0 0 1-.913-1.655c.008-.232.065-.46.167-.671s.248-.4.428-.556c.18-.155.392-.274.621-.35.23-.075.473-.105.715-.089.242-.016.485.015.715.09.23.077.441.196.622.352.18.157.325.346.427.558.102.21.159.44.166.673 0 .914-.748 1.424-1.553 1.84l1.583 1.755c.27-.557.396-1.169.367-1.783v-.275h.969v.307a4.34 4.34 0 0 1-.605 2.46l1.46 1.48h-1.37l-.74-.793a3.56 3.56 0 0 1-2.271.846m-.234-3.57c-.079.035-.149.07-.234.116-.685.358-.99.758-.99 1.296 0 1.051 1.056 1.17 1.51 1.17h.097c.553 0 1.085-.202 1.487-.566zm.471-3.161a.98.98 0 0 0-.684.281.87.87 0 0 0-.258.67c0 .371.19.677.755 1.216.715-.352 1.13-.665 1.13-1.221a.87.87 0 0 0-.26-.667.98.98 0 0 0-.683-.28m11.33 6.697c-.914 0-1.528-.723-1.528-1.798v-.147a1.5 1.5 0 0 1 .05-.642 1.55 1.55 0 0 1 .314-.569c.144-.166.323-.303.525-.4.203-.097.424-.153.65-.165.92 0 1.54.716 1.54 1.782v.148c.029.216.011.436-.052.645-.064.21-.171.404-.317.572a1.6 1.6 0 0 1-.529.402 1.7 1.7 0 0 1-.652.167zm.016-2.896c-.431 0-.689.375-.689 1.002v.042c0 .638.262 1.026.684 1.026.421 0 .683-.389.683-1.016v-.043c.001-.639-.252-1.016-.677-1.016zm-4.454-.834c-.914 0-1.529-.723-1.529-1.798v-.147a1.5 1.5 0 0 1 .05-.642c.063-.209.17-.403.315-.57.144-.167.324-.303.526-.4.203-.098.424-.154.65-.165.92 0 1.54.715 1.54 1.782v.142c.03.217.013.437-.05.647s-.17.406-.316.574a1.65 1.65 0 0 1-.53.405 1.7 1.7 0 0 1-.655.166zm.016-2.892c-.429 0-.685.373-.685 1v.04c0 .63.26 1.02.681 1.02.42 0 .681-.388.681-1.01v-.043c0-.636-.253-1.012-.675-1.012zm.364-2.115a1.23 1.23 0 0 1-.705-.118 1.16 1.16 0 0 1-.505-.485 1.58 1.58 0 0 1 .336-1.802c.157-.156.344-.28.552-.366a2.2 2.2 0 0 1 1.027-.218q.373 0 .739.077l.032-5.36c.49-.01.978.073 1.435.245s.874.428 1.227.755c.338.348.597.76.762 1.208s.232.924.197 1.399a4.14 4.14 0 0 1-1.38 2.338c-.237-.217-.068-.508.145-.874.291-.418.446-.91.444-1.411a3.1 3.1 0 0 0-.589-1.315 3.3 3.3 0 0 0-1.132-.934v4.578c.003.33-.074.654-.226.95a2.1 2.1 0 0 1-.647.751c-.49.364-1.09.566-1.71.576zM9.01 2.73H1.244V1.668H9.01zM5.682 9.11h-1.11V4.857H1.244V3.794H9.01v1.063H5.682z' />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/text-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#8289AD'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path fill='#fff' d='M16.567 22.667v-9.006h3.518v-.994h-8.167v.994h3.518v9.006z' />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.176 8.708H20.76L26.8 14.75V9.333z' />\n    <Path fill='#CDCFDE' d='M22.267 9.333h4.534l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={20.76}\n        x2={28.156}\n        y1={8.608}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/thumb-down-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M3.308 15.5q-.714 0-1.26-.547-.549-.547-.548-1.26v-1.616a1.9 1.9 0 0 1 .13-.673l2.866-6.762q.216-.48.721-.811.506-.33 1.052-.331h8.097q.747 0 1.277.53t.53 1.278v9.444q0 .362-.148.695-.148.334-.394.58l-5.29 5.256a1.3 1.3 0 0 1-.691.348 1.2 1.2 0 0 1-.752-.117q-.36-.183-.519-.537a1.13 1.13 0 0 1-.063-.752L9.392 15.5zm16.384-12q.749 0 1.278.53.53.53.53 1.278v8.384q0 .749-.53 1.278-.53.53-1.278.53h-.211q-.748 0-1.278-.53a1.74 1.74 0 0 1-.53-1.278V5.298q0-.748.53-1.273A1.75 1.75 0 0 1 19.48 3.5z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/thumb-down.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2.832 15.767q-.63 0-1.107-.477t-.477-1.107v-2q0-.175-.018-.364a.7.7 0 0 1 .052-.352L4.348 4.4q.219-.521.733-.877t1.064-.356h10.903v12.6l-5.537 5.754a1.48 1.48 0 0 1-.857.458 1.4 1.4 0 0 1-.924-.17 1.45 1.45 0 0 1-.632-.72 1.6 1.6 0 0 1-.074-1.01l.941-4.312zm12.633-.663V4.75H5.898l-3.066 7.218v2.215h9.1l-1.305 5.95zm4.783-11.937q.654 0 1.118.465t.465 1.118v9.433q0 .654-.465 1.119-.464.465-1.118.465h-3.2v-1.584h3.2V4.75h-3.2V3.167z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/thumb-up-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M20.692 8.5q.714 0 1.26.547.549.548.548 1.26v1.616a1.9 1.9 0 0 1-.13.673l-2.866 6.762q-.216.48-.721.811-.506.33-1.052.331H9.634q-.747 0-1.277-.53a1.74 1.74 0 0 1-.53-1.278V9.248q0-.362.148-.695.148-.334.394-.58l5.29-5.256q.3-.282.691-.348a1.2 1.2 0 0 1 .752.117q.36.183.519.537t.063.752L14.609 8.5zm-16.384 12q-.748 0-1.278-.53a1.74 1.74 0 0 1-.53-1.278v-8.384q0-.748.53-1.278t1.278-.53h.211q.748 0 1.278.53t.53 1.278v8.394q0 .748-.53 1.273a1.75 1.75 0 0 1-1.278.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/thumb-up.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M21.166 8.233q.63 0 1.106.478.477.476.477 1.106v2q0 .175.018.364a.7.7 0 0 1-.051.352L19.649 19.6q-.219.521-.733.877-.513.357-1.063.356H6.949v-12.6l5.546-5.762a1.46 1.46 0 0 1 1.781-.283q.423.249.623.723.2.472.083 1.002l-.95 4.32zm-12.633.655V19.25h9.566l3.067-7.23V9.818h-9.1l1.313-5.95zM3.749 20.833q-.653 0-1.118-.465a1.53 1.53 0 0 1-.465-1.118V9.817q0-.653.465-1.118t1.118-.466h3.2v1.584h-3.2v9.433h3.2v1.583z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/translate-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m14.622 18.45-.956 2.579a.73.73 0 0 1-.268.34.7.7 0 0 1-.42.131.72.72 0 0 1-.615-.317.67.67 0 0 1-.08-.69l3.887-10.031a.8.8 0 0 1 .28-.335.7.7 0 0 1 .405-.127h.558q.223 0 .405.127a.8.8 0 0 1 .28.334l3.886 10.047a.66.66 0 0 1-.071.675.7.7 0 0 1-.608.317.7.7 0 0 1-.428-.132.84.84 0 0 1-.276-.354l-.956-2.564zm-5.64-4.86-4.446 4.44a.7.7 0 0 1-.515.226.71.71 0 0 1-.54-.225.72.72 0 0 1-.216-.527q0-.31.217-.527l4.446-4.456a13 13 0 0 1-1.645-2.077A14.5 14.5 0 0 1 4.999 8h1.59q.433.907 1.053 1.816.62.906 1.34 1.651 1.065-1.075 2.015-2.61Q11.947 7.32 12.34 6H2.44a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.32.216-.534A.73.73 0 0 1 2.44 4.5H8.25v-.865q0-.32.216-.535a.73.73 0 0 1 .534-.215q.32 0 .535.215a.73.73 0 0 1 .215.535V4.5h5.808q.319 0 .534.216a.73.73 0 0 1 .216.534.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215h-1.698q-.487 1.685-1.551 3.522-1.065 1.838-2.272 3.03l2.467 2.527-.567 1.54zm6.12 3.533h4.062l-2.03-5.456z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/translate.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m14.622 18.45-.956 2.579a.73.73 0 0 1-.268.34.7.7 0 0 1-.42.131.72.72 0 0 1-.615-.317.67.67 0 0 1-.08-.69l3.887-10.031a.8.8 0 0 1 .28-.335.7.7 0 0 1 .405-.127h.558q.223 0 .405.127a.8.8 0 0 1 .28.334l3.886 10.047a.66.66 0 0 1-.071.675.7.7 0 0 1-.608.317.7.7 0 0 1-.428-.132.84.84 0 0 1-.276-.354l-.956-2.564zm-5.64-4.86-4.446 4.44a.7.7 0 0 1-.515.226.71.71 0 0 1-.54-.225.72.72 0 0 1-.216-.527q0-.31.217-.527l4.446-4.456a13 13 0 0 1-1.645-2.077A14.5 14.5 0 0 1 4.999 8h1.59q.433.907 1.053 1.816.62.906 1.34 1.651 1.065-1.075 2.015-2.61Q11.947 7.32 12.34 6H2.44a.73.73 0 0 1-.534-.215.73.73 0 0 1-.216-.535q0-.32.216-.534A.73.73 0 0 1 2.44 4.5H8.25v-.865q0-.32.216-.535a.73.73 0 0 1 .534-.215q.32 0 .535.215a.73.73 0 0 1 .215.535V4.5h5.808q.319 0 .534.216a.73.73 0 0 1 .216.534.73.73 0 0 1-.216.535.73.73 0 0 1-.534.215h-1.698q-.487 1.685-1.551 3.522-1.065 1.838-2.272 3.03l2.467 2.527-.567 1.54zm6.12 3.533h4.062l-2.03-5.456z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/travel-&-places.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { ClipPath, Defs, G, Path } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 18 19'>\n    <G clipPath='url(#prefix__a)'>\n      <Path\n        fill={color ?? \"#A1A1A1\"}\n        d='M5.8 18.032H3.667V16.61a.71.71 0 0 1-.71-.71v-2.844a.71.71 0 0 1 .71-.712h1.066v-1.066a3.306 3.306 0 0 1 3.317-3.2h1.902a3.307 3.307 0 0 1 3.315 3.2v1.066h1.067a.71.71 0 0 1 .71.712v2.843a.71.71 0 0 1-.71.71v1.423H12.2V16.61H5.8zM3.667 15.19v.7h10.667v-.7h-2.667v-.012a.356.356 0 0 0-.357-.355H6.69a.355.355 0 0 0-.356.355v.012zm9.423-2.056a.8.8 0 1 0 .002 1.6.8.8 0 0 0-.002-1.6m-8.2 0a.8.8 0 1 0 .565.235.8.8 0 0 0-.566-.235zM9 9.145c-3.2 0-3.2 1.554-3.2 3.2h6.4v-.011a3.93 3.93 0 0 0-.4-2.205c-.453-.671-1.34-.984-2.8-.984m-7.467 7.812H.467v-9.6h3.562L4.023.967h7.467v4.257h6.044v11.732h-1.067V6.3h-4.978v1.066h-1.067V2.033H5.089l-.009 6.4H1.533zm13.867-6.4h-1.066V9.49H15.4zm-11.733 0H2.6V9.49h1.067zM15.4 8.424h-1.066V7.357H15.4zM9.355 6.3H8.29V5.233h1.066zm-2.133 0H6.155V5.233h1.067zm2.133-2.133H8.29V3.1h1.066zm-2.133 0H6.155V3.089l1.067.011z'\n      />\n    </G>\n    <Defs>\n      <ClipPath id='prefix__a'>\n        <Path fill='#fff' d='M.467.967h17.067v17.067H.467z' />\n      </ClipPath>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/unknown-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#CCC'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path\n        fill='#fff'\n        d='M12.666 15.666c0-.666.133-1.2.267-1.533.133-.333.4-.667.733-.933a2.44 2.44 0 0 1 1.133-.667c.4-.133.8-.2 1.267-.2.933 0 1.667.267 2.333.8.6.533.934 1.2.934 2.067 0 .4-.134.733-.267 1.066q-.2.501-1 1.2c-.533.467-.733.8-.933 1s-.267.467-.4.734c-.134.266-.067.466-.067 1.133h-1.333c0-.667.066-1 .133-1.333.067-.334.2-.6.4-.867s.4-.6.867-1.067.8-.8.933-1 .267-.533.267-1-.2-.8-.534-1.133c-.333-.333-.8-.533-1.4-.533-1.333 0-2 .933-2 2.266zm4 7.334h-1.333v-1.334h1.333z'\n      />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#EAEAEA' d='M22.266 9.333h4.533l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/unread.tsx",
    "content": "import * as React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nconst CommentIcon = (props: any) => (\n  <Svg\n    width={16}\n    height={16}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    {...props}\n  >\n    <Path\n      d=\"M13.3334 11.3337H2.66671V3.33366H9.40004C9.34671 3.07366 9.28004 2.59366 9.40004 2.00033H2.66671C1.93337 2.00033 1.33337 2.60033 1.33337 3.33366V15.3337L4.00004 12.667H13.3334C14.0667 12.667 14.6667 12.067 14.6667 11.3337V5.32033C14.28 5.61366 13.8267 5.83366 13.3334 5.93366V11.3337Z\"\n      fill=\"#A1A1A1\"\n    />\n    <Path\n      d=\"M12.6667 4.66699C13.7713 4.66699 14.6667 3.77156 14.6667 2.66699C14.6667 1.56242 13.7713 0.666992 12.6667 0.666992C11.5621 0.666992 10.6667 1.56242 10.6667 2.66699C10.6667 3.77156 11.5621 4.66699 12.6667 4.66699Z\"\n      fill=\"#A1A1A1\"\n    />\n    <Path d=\"M9.33337 8.66699H4.00004V10.0003H9.33337V8.66699Z\" fill=\"#A1A1A1\" />\n    <Path d=\"M12 6.66699H4.00004V8.00033H12V6.66699Z\" fill=\"#A1A1A1\" />\n    <Path\n      d=\"M4.00004 6.00033H12V5.93366C11.1934 5.76699 10.5 5.30033 10.02 4.66699H4.00004V6.00033Z\"\n      fill=\"#A1A1A1\"\n    />\n  </Svg>\n);\n\nexport default CommentIcon;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/upload-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.751 19.833q-.642 0-1.113-.47a1.52 1.52 0 0 1-.47-1.113V15.7q0-.333.232-.564a.77.77 0 0 1 .562-.232q.33 0 .56.232.23.231.23.564v2.55h12.5V15.7q0-.333.231-.564a.77.77 0 0 1 .563-.232q.33 0 .56.232a.77.77 0 0 1 .228.564v2.55q0 .643-.47 1.113-.471.47-1.113.47zm5.463-12.645L8.926 9.474a.71.71 0 0 1-.55.223.8.8 0 0 1-.552-.244.74.74 0 0 1-.229-.554.78.78 0 0 1 .235-.562l3.615-3.615a.9.9 0 0 1 .26-.183.7.7 0 0 1 .299-.06.7.7 0 0 1 .295.06q.136.06.265.18l3.633 3.634q.225.234.227.555a.74.74 0 0 1-.22.545.8.8 0 0 1-.567.236.76.76 0 0 1-.56-.232l-2.28-2.27v8.05q0 .333-.232.564a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.564z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/upload.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.751 19.833q-.642 0-1.113-.47a1.52 1.52 0 0 1-.47-1.113V15.7q0-.333.232-.564a.77.77 0 0 1 .562-.232q.33 0 .56.232.23.231.23.564v2.55h12.5V15.7q0-.333.231-.564a.77.77 0 0 1 .563-.232q.33 0 .56.232a.77.77 0 0 1 .228.564v2.55q0 .643-.47 1.113-.471.47-1.113.47zm5.463-12.645L8.926 9.474a.71.71 0 0 1-.55.223.8.8 0 0 1-.552-.244.74.74 0 0 1-.229-.554.78.78 0 0 1 .235-.562l3.615-3.615a.9.9 0 0 1 .26-.183.7.7 0 0 1 .299-.06.7.7 0 0 1 .295.06q.136.06.265.18l3.633 3.634q.225.234.227.555a.74.74 0 0 1-.22.545.8.8 0 0 1-.567.236.76.76 0 0 1-.56-.232l-2.28-2.27v8.05q0 .333-.232.564a.77.77 0 0 1-.567.231.75.75 0 0 1-.56-.231.78.78 0 0 1-.224-.564z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/user-empty-icon.tsx",
    "content": "import type { SvgProps } from \"react-native-svg\";\nimport Svg, { G, Mask, Path, Rect } from \"react-native-svg\";\n\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 120 120'>\n    <Mask\n      id='mask0_5082_129886'\n      style={{ maskType: \"alpha\" }}\n      maskUnits='userSpaceOnUse'\n      x='-20'\n      y='-20'\n      width='160'\n      height='160'\n    >\n      <Rect x='-20' y='-20' width='160' height='160' fill='#D9D9D9' />\n    </Mask>\n    <G mask='url(#mask0_5082_129886)'>\n      <Path\n        d='M60 57.9479C53.5644 57.9479 48.0667 55.6679 43.5067 51.1079C38.9467 46.549 36.6667 41.0512 36.6667 34.6146C36.6667 28.179 38.9467 22.6825 43.5067 18.1235C48.0667 13.5644 53.5644 11.2844 60 11.2844C66.4356 11.2844 71.9333 13.5644 76.4933 18.1235C81.0533 22.6825 83.3333 28.179 83.3333 34.6146C83.3333 41.0512 81.0533 46.549 76.4933 51.1079C71.9333 55.6679 66.4356 57.9479 60 57.9479ZM19.3166 105.007C16.1811 105.007 13.4568 103.856 11.1437 101.555C8.8306 99.2539 7.67344 96.5196 7.67344 93.3513V91.0288C7.67344 86.7545 8.70044 82.9018 10.7544 79.4706C12.8084 76.0385 15.6466 73.4095 19.268 71.5826C24.3797 69.1016 29.6071 67.2153 34.95 65.9235C40.2929 64.6317 46.0113 63.9858 52.105 63.9858H67.8949C73.987 63.9858 79.7055 64.6317 85.0491 65.9235C90.3928 67.2153 95.6215 69.1016 100.735 71.5826C104.357 73.4095 107.195 76.0385 109.249 79.4706C111.303 82.9018 112.33 86.7545 112.33 91.0288V93.3513C112.33 96.5196 111.173 99.2539 108.86 101.555C106.547 103.856 103.823 105.007 100.687 105.007H19.3166Z'\n        fill={color}\n      />\n    </G>\n  </Svg>\n);\n\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/video-call-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M9.49 12.75V15q0 .319.216.534a.73.73 0 0 0 .535.216q.318 0 .534-.216A.73.73 0 0 0 10.99 15v-2.25h2.25q.319 0 .534-.216A.73.73 0 0 0 13.99 12a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216h-2.25V9a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216.73.73 0 0 0-.535.216A.73.73 0 0 0 9.49 9v2.25H7.24a.73.73 0 0 0-.534.216.73.73 0 0 0-.216.534q0 .32.216.534a.73.73 0 0 0 .534.216zM4.548 19.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T4.548 4.5h11.385q.757 0 1.282.525t.525 1.283v4.577l2.75-2.75q.218-.217.493-.107.276.111.276.422v7.1q0 .31-.276.422-.275.11-.494-.107l-2.749-2.75v4.577q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/video-call.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M9.295 12.754v2.38q0 .332.228.56.228.227.563.227a.77.77 0 0 0 .564-.227.76.76 0 0 0 .229-.56v-2.38h2.37q.334 0 .565-.228a.76.76 0 0 0 .231-.562.76.76 0 0 0-.23-.564.77.77 0 0 0-.565-.23h-2.371V8.8a.77.77 0 0 0-.232-.565.77.77 0 0 0-.567-.23.75.75 0 0 0-.56.23.78.78 0 0 0-.225.565v2.37H6.916a.75.75 0 0 0-.56.233.78.78 0 0 0-.227.566q0 .335.227.56t.56.225zM3.78 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h12.537q.627 0 1.104.471.475.471.475 1.112v5.1L21.13 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.104.47zm0-1.583h12.533V5.733H3.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/video-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#7C8EEE'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path\n        fill='#fff'\n        d='m21.09 17.753-7.775 4.635a.78.78 0 0 1-1.18-.674v-9.27c0-.595.66-.97 1.18-.673l7.775 4.635c.504.313.504 1.05 0 1.347'\n      />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.176 8.708H20.76L26.8 14.75V9.333z' />\n    <Path fill='#CAD1F8' d='M22.267 9.333h4.534l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.114}\n        x2={28.156}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/videocam-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M4.548 19.5q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.758.525-1.283T4.548 4.5h11.385q.757 0 1.282.525t.525 1.283v4.577l2.746-2.746q.221-.222.497-.111.276.11.276.422v7.1q0 .311-.276.422t-.497-.11l-2.746-2.747v4.577q0 .758-.525 1.283t-1.283.525z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/videocam-off-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m17.768 10.885 2.746-2.747q.221-.22.497-.11t.276.422v7.1q0 .311-.276.422t-.497-.11l-2.854-2.854v.173q-.154.408-.616.526-.463.118-.812-.232L8.793 6.036a.8.8 0 0 1-.254-.472 1.016 1.016 0 0 1 .384-.904.8.8 0 0 1 .518-.16h6.52q.756 0 1.282.525.525.525.525 1.283zm1.677 10.746L2.06 4.246a.73.73 0 0 1-.212-.522.7.7 0 0 1 .212-.532.72.72 0 0 1 .527-.217q.31 0 .527.217L20.5 20.577q.208.207.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217M4.44 4.519l13.308 13.308a1.75 1.75 0 0 1-.559 1.186q-.51.487-1.23.487H4.576q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.72.487-1.23.486-.51 1.186-.559'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/videocam-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m17.768 10.885 2.749-2.75q.219-.217.494-.107.276.111.276.422v7.1q0 .31-.276.422-.276.11-.494-.107l-2.749-2.75v.635q0 .319-.216.534a.73.73 0 0 1-.534.216.73.73 0 0 1-.535-.216.73.73 0 0 1-.215-.534V6.308a.3.3 0 0 0-.087-.222.3.3 0 0 0-.22-.086H8.825q-.375 0-.563-.234a.804.804 0 0 1 0-1.03q.188-.237.563-.236h7.134q.758 0 1.283.525t.525 1.283zm1.677 10.746L2.06 4.246a.73.73 0 0 1-.212-.522.7.7 0 0 1 .212-.532.72.72 0 0 1 .527-.217q.31 0 .527.217L20.5 20.577q.208.207.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217M4.44 4.519 5.921 6H4.577a.3.3 0 0 0-.221.086.3.3 0 0 0-.087.222v11.384a.3.3 0 0 0 .087.221.3.3 0 0 0 .22.087H15.96a.3.3 0 0 0 .221-.087.3.3 0 0 0 .087-.22v-1.347l1.48 1.481a1.75 1.75 0 0 1-.559 1.186 1.72 1.72 0 0 1-1.229.487H4.576q-.758 0-1.283-.525a1.75 1.75 0 0 1-.525-1.283V6.308q0-.719.487-1.23A1.75 1.75 0 0 1 4.44 4.52'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/videocam.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M3.779 19.85q-.64 0-1.112-.471a1.52 1.52 0 0 1-.472-1.112V5.733q0-.64.472-1.112a1.52 1.52 0 0 1 1.112-.471h12.537q.627 0 1.104.471.475.471.475 1.112v5.1L21.13 7.6a.38.38 0 0 1 .433-.09q.25.095.25.357v8.27q0 .257-.25.352a.38.38 0 0 1-.433-.09l-3.234-3.232v5.1q0 .64-.475 1.112-.477.47-1.104.47zm0-1.583h12.533V5.733H3.78z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/visibility-off-fill.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='m19.236 21.342-3.52-3.488a9 9 0 0 1-1.818.494 12 12 0 0 1-1.897.152q-3.158 0-5.784-1.68a12.3 12.3 0 0 1-4.162-4.428 1.74 1.74 0 0 1 .01-1.784q.598-.994 1.27-1.924.674-.93 1.566-1.645L2.643 4.765a.72.72 0 0 1-.212-.514.72.72 0 0 1 .228-.54.72.72 0 0 1 .527-.217q.31 0 .526.218L20.29 20.289q.207.207.22.514a.7.7 0 0 1-.22.54.72.72 0 0 1-.527.216.72.72 0 0 1-.527-.217M12 15.577q.333 0 .636-.04.302-.039.591-.172l-5.092-5.092q-.123.289-.168.591-.045.303-.044.636 0 1.702 1.187 2.89 1.188 1.187 2.89 1.187m0-11.077q3.168 0 5.81 1.688a12.2 12.2 0 0 1 4.17 4.454 1.68 1.68 0 0 1 .186 1.307 1.5 1.5 0 0 1-.17.409 13 13 0 0 1-1.019 1.634 11.5 11.5 0 0 1-1.26 1.444.93.93 0 0 1-.738.287 1.1 1.1 0 0 1-.739-.337L16.145 13.3a.76.76 0 0 1-.205-.36.9.9 0 0 1 .005-.425 3.6 3.6 0 0 0 .133-1.015q0-1.702-1.187-2.89Q13.702 7.424 12 7.424a3.4 3.4 0 0 0-.996.143.86.86 0 0 1-.443.015.76.76 0 0 1-.376-.206l-.931-.93q-.428-.428-.284-.995.144-.568.72-.723.568-.116 1.143-.171Q11.408 4.5 12 4.5m1.937 5.112q.304.335.5.75.198.417.247.87.025.182-.135.252t-.301-.07l-2.146-2.137q-.14-.14-.062-.301a.27.27 0 0 1 .258-.16q.485.039.894.243t.745.553'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/visibility-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M14.878 8.623q.609.61.942 1.482t.252 1.75q0 .29-.208.49a.7.7 0 0 1-.496.2.67.67 0 0 1-.489-.2.67.67 0 0 1-.2-.49 2.637 2.637 0 0 0-.738-2.265 2.8 2.8 0 0 0-1.025-.645 2.5 2.5 0 0 0-1.265-.114.66.66 0 0 1-.498-.193.7.7 0 0 1-.22-.492.67.67 0 0 1 .186-.498.67.67 0 0 1 .484-.219 3.75 3.75 0 0 1 1.754.228q.882.327 1.52.966M12 6q-.533 0-1.046.052a8 8 0 0 0-1.02.171.77.77 0 0 1-.575-.091.8.8 0 0 1-.352-.451.69.69 0 0 1 .064-.578.68.68 0 0 1 .463-.34q.605-.144 1.22-.203.616-.06 1.246-.06 3.222 0 5.92 1.675a11.2 11.2 0 0 1 4.15 4.533 1.6 1.6 0 0 1 .191.792q0 .216-.037.409t-.138.383a9.5 9.5 0 0 1-1.117 1.793 11.7 11.7 0 0 1-1.448 1.528.65.65 0 0 1-.532.173.7.7 0 0 1-.491-.3.79.79 0 0 1 .105-1.075q.678-.614 1.235-1.34a9 9 0 0 0 .962-1.571 9.77 9.77 0 0 0-3.613-4.012A9.55 9.55 0 0 0 12.001 6m0 12.5q-3.159 0-5.789-1.687a12.45 12.45 0 0 1-4.182-4.446 1.4 1.4 0 0 1-.183-.413 1.8 1.8 0 0 1-.058-.454q0-.231.05-.446.05-.216.175-.421.558-1.02 1.264-1.942A9.5 9.5 0 0 1 4.9 7.04L2.643 4.765a.74.74 0 0 1-.205-.53.73.73 0 0 1 .22-.523.72.72 0 0 1 .527-.218q.31 0 .527.218L20.29 20.289q.208.207.22.514a.7.7 0 0 1-.22.54.72.72 0 0 1-.527.216.72.72 0 0 1-.527-.217l-3.519-3.488a9 9 0 0 1-1.818.494A12 12 0 0 1 12 18.5M5.955 8.092a9.7 9.7 0 0 0-1.58 1.536A8.8 8.8 0 0 0 3.2 11.5a9.77 9.77 0 0 0 3.612 4.012A9.54 9.54 0 0 0 12.001 17q.645 0 1.268-.087.624-.085 1.24-.267l-1.266-1.296a2.5 2.5 0 0 1-.607.18 4 4 0 0 1-.635.047q-1.702 0-2.89-1.187-1.187-1.188-1.187-2.89 0-.333.052-.636a3 3 0 0 1 .175-.606z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-down-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M9.212 14.5H6.403a.88.88 0 0 1-.645-.259.88.88 0 0 1-.259-.645v-3.192q0-.387.259-.645a.88.88 0 0 1 .645-.259h2.807l2.993-2.992q.36-.36.828-.165t.468.707v9.9q0 .512-.468.707t-.828-.165zM18 12q0 .934-.389 1.771a4.1 4.1 0 0 1-1.047 1.408.42.42 0 0 1-.446.027.39.39 0 0 1-.233-.375V9.119q0-.262.233-.375a.42.42 0 0 1 .446.027q.66.587 1.047 1.44.39.855.389 1.789'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-down.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M9.05 14.9H5.929a.77.77 0 0 1-.567-.23.77.77 0 0 1-.23-.566V9.888a.76.76 0 0 1 .23-.559.77.77 0 0 1 .567-.229h3.12l3.517-3.517q.375-.375.863-.177.487.198.487.723v11.734q0 .533-.487.73-.488.2-.863-.176zm9.316-2.9q0 1.217-.608 2.227a4 4 0 0 1-1.688 1.548q-.212.104-.391-.019a.39.39 0 0 1-.18-.34v-6.85a.39.39 0 0 1 .18-.339q.18-.123.391-.019 1.08.542 1.688 1.567T18.366 12m-6.033-3.83-2.588 2.513H6.716v2.634h3.03l2.587 2.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-mute-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M11.212 14.5H8.404a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644v-3.192q0-.383.26-.644a.87.87 0 0 1 .644-.26h2.807l2.993-2.992q.36-.36.828-.162t.468.704v9.9q0 .507-.468.704-.468.198-.828-.162z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-mute.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.034 14.9H7.913a.77.77 0 0 1-.567-.23.77.77 0 0 1-.229-.566V9.888a.76.76 0 0 1 .23-.559.77.77 0 0 1 .566-.229h3.12l3.517-3.517q.375-.375.863-.177.487.198.487.723v11.734q0 .533-.487.73-.488.2-.863-.176zM8.7 13.317h3.03l2.587 2.52V8.172l-2.587 2.512H8.7z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-off-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M16.458 19.123a10 10 0 0 1-.502.296q-.255.14-.527.256a.7.7 0 0 1-.58 0 .72.72 0 0 1-.412-.421.64.64 0 0 1 .032-.555.88.88 0 0 1 .423-.403 3 3 0 0 0 .722-.417l-3.825-3.425v2.496q0 .512-.469.707t-.828-.165L7.5 14.5H4.692a.88.88 0 0 1-.645-.259.88.88 0 0 1-.258-.645v-3.192q0-.387.258-.645a.88.88 0 0 1 .645-.259h2.143L2.523 5.188a.73.73 0 0 1-.212-.522.7.7 0 0 1 .212-.531.72.72 0 0 1 .527-.218q.31 0 .527.218L20 20.558q.209.207.213.522a.7.7 0 0 1-.213.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.218zm2.523-7.148q0-2.055-1.105-3.764a6.8 6.8 0 0 0-2.964-2.548.88.88 0 0 1-.425-.407.66.66 0 0 1-.031-.55.72.72 0 0 1 .427-.421.79.79 0 0 1 .62.019 8.43 8.43 0 0 1 3.62 3.082q1.358 2.056 1.358 4.589a7.8 7.8 0 0 1-.864 3.594q-.17.405-.478.5a.87.87 0 0 1-.57-.002.77.77 0 0 1-.408-.344q-.146-.247.006-.558.41-.736.612-1.524.202-.787.202-1.666m-4.12-3.204q.7.535 1.064 1.45.363.915.364 1.779 0 .221-.024.438a2.4 2.4 0 0 1-.088.428.46.46 0 0 1-.345.362q-.276.075-.497-.145l-.89-.89a.9.9 0 0 1-.272-.649V9.13q0-.262.239-.38a.42.42 0 0 1 .45.022m-4.919-1.08a.43.43 0 0 1-.135-.322.46.46 0 0 1 .145-.32l.54-.541q.36-.36.828-.165.47.195.469.707v1.392q0 .312-.276.432t-.498-.11z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-off.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M16.788 19.175q-.354.241-.73.442-.375.2-.778.375a.8.8 0 0 1-.642.006.76.76 0 0 1-.442-.448.68.68 0 0 1 .015-.592.86.86 0 0 1 .456-.416 5 5 0 0 0 .633-.284q.309-.162.588-.4L11.98 14.35v3.513q0 .533-.488.73-.488.2-.863-.176L7.114 14.9h-3.13a.776.776 0 0 1-.788-.796V9.887q.001-.328.226-.558a.76.76 0 0 1 .562-.229h2.73L2.017 4.404a.73.73 0 0 1-.234-.562.81.81 0 0 1 .25-.571.8.8 0 0 1 .567-.234.8.8 0 0 1 .567.234L20.6 20.704a.78.78 0 0 1 .242.571q0 .33-.242.563a.79.79 0 0 1-.575.241.79.79 0 0 1-.575-.242zm2.258-7.196q0-2.175-1.208-3.954a7.1 7.1 0 0 0-3.188-2.613.78.78 0 0 1-.433-.416.7.7 0 0 1 0-.58.77.77 0 0 1 .452-.441.84.84 0 0 1 .656.017 8.6 8.6 0 0 1 3.848 3.166q1.457 2.154 1.457 4.821 0 .917-.182 1.79a8.4 8.4 0 0 1-.544 1.694q-.2.45-.508.545a.83.83 0 0 1-.596-.029.9.9 0 0 1-.44-.368q-.15-.253.011-.565.341-.704.509-1.475a7.5 7.5 0 0 0 .166-1.592m-4.554-3.587q.925.554 1.431 1.545A4.5 4.5 0 0 1 16.43 12q0 .225-.015.431a2.2 2.2 0 0 1-.073.423.4.4 0 0 1-.302.325q-.248.067-.448-.133l-1.463-1.463a.7.7 0 0 1-.233-.554V8.733q0-.232.196-.348a.37.37 0 0 1 .4.007m-4.83-1.275a.377.377 0 0 1 0-.567l.967-.967q.375-.375.863-.177.487.198.487.723v2.354a.35.35 0 0 1-.25.359.38.38 0 0 1-.433-.092zm.734 8.804v-3.138l-2.1-2.1H4.779v2.634h3.004z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-up-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M18.847 11.975a6.8 6.8 0 0 0-1.106-3.768 6.8 6.8 0 0 0-2.964-2.543.88.88 0 0 1-.425-.408.66.66 0 0 1-.03-.55.72.72 0 0 1 .426-.421.79.79 0 0 1 .62.019 8.4 8.4 0 0 1 3.62 3.09q1.358 2.064 1.358 4.581t-1.358 4.58a8.4 8.4 0 0 1-3.62 3.091.79.79 0 0 1-.62.02.72.72 0 0 1-.426-.422.66.66 0 0 1 .03-.55.88.88 0 0 1 .425-.407 6.8 6.8 0 0 0 2.964-2.544 6.8 6.8 0 0 0 1.105-3.768M7.365 14.5H4.558a.88.88 0 0 1-.645-.259.88.88 0 0 1-.259-.645v-3.192q0-.387.259-.645a.88.88 0 0 1 .645-.259h2.808l2.992-2.992q.36-.36.828-.165t.468.707v9.9q0 .512-.468.707t-.828-.165zm8.788-2.5q0 .934-.389 1.771a4.1 4.1 0 0 1-1.048 1.408.42.42 0 0 1-.445.027.39.39 0 0 1-.233-.375V9.119q0-.262.233-.375a.42.42 0 0 1 .445.027q.66.586 1.048 1.44.39.855.389 1.789'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/volume-up.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M19.133 11.98a6.88 6.88 0 0 0-1.208-3.96 7.13 7.13 0 0 0-3.18-2.608.8.8 0 0 1-.437-.416.67.67 0 0 1 .005-.58.74.74 0 0 1 .433-.441.84.84 0 0 1 .641.004 8.64 8.64 0 0 1 3.87 3.183q1.46 2.163 1.46 4.817 0 2.663-1.46 4.825a8.6 8.6 0 0 1-3.87 3.175.82.82 0 0 1-.641.013.74.74 0 0 1-.434-.442.67.67 0 0 1-.004-.58.83.83 0 0 1 .438-.424 7.08 7.08 0 0 0 3.179-2.6q1.208-1.776 1.208-3.967M7.2 14.9H4.08a.77.77 0 0 1-.568-.23.77.77 0 0 1-.229-.566V9.887a.76.76 0 0 1 .23-.558.77.77 0 0 1 .566-.23H7.2l3.517-3.516q.375-.375.862-.177.488.198.488.723v11.733q0 .534-.488.732-.487.198-.862-.177zm9.317-2.9q0 1.216-.609 2.227a4 4 0 0 1-1.687 1.548q-.213.104-.392-.019a.39.39 0 0 1-.179-.34v-6.85a.39.39 0 0 1 .18-.34q.178-.12.39-.018 1.08.542 1.688 1.567A4.3 4.3 0 0 1 16.517 12m-6.034-3.83-2.587 2.513h-3.03v2.634h3.03l2.587 2.52z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/warning-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M3.425 20.5a.876.876 0 0 1-.772-.452.96.96 0 0 1-.129-.437.8.8 0 0 1 .127-.467l8.561-14.788A.9.9 0 0 1 12 3.904q.23 0 .44.11a.9.9 0 0 1 .347.342l8.56 14.788q.14.231.128.467a.96.96 0 0 1-.129.437.88.88 0 0 1-.771.452zm8.574-2.692q.343 0 .576-.232a.78.78 0 0 0 .232-.576.78.78 0 0 0-.232-.575.78.78 0 0 0-.576-.233.78.78 0 0 0-.575.233.78.78 0 0 0-.233.575q0 .343.233.576a.78.78 0 0 0 .575.232m0-2.616q.32 0 .535-.215a.73.73 0 0 0 .215-.535v-3.5a.73.73 0 0 0-.216-.534.73.73 0 0 0-.534-.216.72.72 0 0 0-.534.216.73.73 0 0 0-.216.534v3.5q0 .32.216.535a.73.73 0 0 0 .534.215'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/warning.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M2.595 20.85a.797.797 0 0 1-.694-.4.8.8 0 0 1-.115-.378.74.74 0 0 1 .12-.418L11.322 3.42a.8.8 0 0 1 .299-.302.8.8 0 0 1 .38-.096q.205 0 .382.096a.84.84 0 0 1 .306.3l9.417 16.237a.77.77 0 0 1 .112.418.77.77 0 0 1-.116.378.8.8 0 0 1-.694.4zm1.373-1.583h16.067L12.002 5.4zm8.104-1.3a.76.76 0 0 0 .567-.233.78.78 0 0 0 .23-.571.76.76 0 0 0-.233-.567.78.78 0 0 0-.571-.23.76.76 0 0 0-.567.234.78.78 0 0 0-.23.57q0 .339.233.567.234.23.571.23m.008-2.767q.33 0 .56-.231a.77.77 0 0 0 .228-.565v-3.767a.76.76 0 0 0-.232-.555.76.76 0 0 0-.558-.232.76.76 0 0 0-.564.232.76.76 0 0 0-.229.555v3.767q0 .333.232.565a.77.77 0 0 0 .563.231'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/wifi-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M12 20.712a1.95 1.95 0 0 1-1.434-.586 1.95 1.95 0 0 1-.585-1.434q0-.848.585-1.433A1.95 1.95 0 0 1 12 16.673a1.95 1.95 0 0 1 1.434.586q.586.585.586 1.433a1.95 1.95 0 0 1-.586 1.434 1.95 1.95 0 0 1-1.434.585m0-9.943q1.818 0 3.438.6t2.928 1.66q.375.288.387.747a1.04 1.04 0 0 1-.322.793q-.32.32-.786.322a1.38 1.38 0 0 1-.841-.27 8 8 0 0 0-2.215-1.188A7.7 7.7 0 0 0 12 13q-1.36 0-2.588.433a8 8 0 0 0-2.215 1.188q-.375.273-.842.263a1.1 1.1 0 0 1-.785-.33 1.1 1.1 0 0 1-.315-.793.91.91 0 0 1 .38-.747 10.2 10.2 0 0 1 2.928-1.652A9.9 9.9 0 0 1 12 10.769m0-6q3.087 0 5.782 1.04A15.8 15.8 0 0 1 22.6 8.723q.375.32.395.79.02.472-.314.806a1.05 1.05 0 0 1-.8.317 1.38 1.38 0 0 1-.856-.324A13.74 13.74 0 0 0 12 7a13.73 13.73 0 0 0-9.025 3.312 1.38 1.38 0 0 1-.855.325 1.05 1.05 0 0 1-.8-.318 1.03 1.03 0 0 1-.315-.805 1.06 1.06 0 0 1 .395-.79A15.8 15.8 0 0 1 6.22 5.808Q8.914 4.769 12 4.769'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/wifi.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M11.995 18.879q-.789 0-1.36-.573a1.86 1.86 0 0 1-.573-1.36q0-.78.572-1.357a1.85 1.85 0 0 1 1.36-.576q.789 0 1.36.576.574.577.574 1.357 0 .787-.573 1.36a1.86 1.86 0 0 1-1.36.573m0-9.14q1.628 0 3.062.56a9.8 9.8 0 0 1 2.711 1.62.92.92 0 0 1 .344.692.93.93 0 0 1-.29.723q-.3.3-.714.295a1.23 1.23 0 0 1-.753-.266q-1.032-.831-2.127-1.208a6.8 6.8 0 0 0-4.467 0q-1.096.377-2.127 1.208a1.24 1.24 0 0 1-.753.268.94.94 0 0 1-.714-.3.92.92 0 0 1-.292-.724.9.9 0 0 1 .353-.683 9.6 9.6 0 0 1 2.704-1.627 8.4 8.4 0 0 1 3.063-.557m0-4.939q2.655 0 5.015.97a15 15 0 0 1 4.245 2.635q.318.29.342.71a.91.91 0 0 1-.281.725q-.3.3-.741.3a1.15 1.15 0 0 1-.773-.285 14 14 0 0 0-3.637-2.213 10.8 10.8 0 0 0-4.17-.805q-2.22 0-4.17.805-1.952.806-3.63 2.213-.34.285-.774.286a1 1 0 0 1-.734-.3.94.94 0 0 1-.288-.726.97.97 0 0 1 .342-.71A14.9 14.9 0 0 1 6.98 5.77a13.1 13.1 0 0 1 5.016-.97'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/zip-file-type.tsx",
    "content": "import Svg, { Path, G, Defs, LinearGradient, Stop } from \"react-native-svg\";\n/* SVGR has dropped some elements not supported by react-native-svg: filter */\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 32 32'>\n    <Path fill='#fff' d='M0 4a4 4 0 0 1 4-4h24a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4z' />\n    <G filter='url(#prefix__a)'>\n      <Path\n        fill='#576A95'\n        fillRule='evenodd'\n        d='M7.333 2.667A2.133 2.133 0 0 0 5.199 4.8v22.4c0 1.178.955 2.134 2.134 2.134h17.333a2.133 2.133 0 0 0 2.133-2.134V9.333l-6.666-6.666z'\n        clipRule='evenodd'\n      />\n    </G>\n    <G filter='url(#prefix__b)'>\n      <Path\n        fill='#fff'\n        d='M17.833 19.32v3.62a.906.906 0 0 1-.907.906h-1.853a.91.91 0 0 1-.907-.905V19.32zm-.907 2.074h-1.853v1.819h1.852zm.876-5.696v1.819H15.98v-1.819zM16.003 2.933v1.833h1.83v1.833h-1.83v1.698h1.83v1.833h-1.83v1.811h1.83v1.81h-1.83v1.812h-1.837v-1.811h1.83V11.94h-1.83v-1.81h1.83V8.296h-1.83V6.463h1.83V4.766h-1.83V2.933z'\n      />\n    </G>\n    <Path fill='url(#prefix__c)' d='M26.174 8.708h-5.416l6.041 6.042V9.333z' />\n    <Path fill='#BBC3D4' d='M22.267 9.333h4.534l-6.667-6.667V7.2c0 1.178.955 2.133 2.133 2.133' />\n    <Defs>\n      <LinearGradient\n        id='prefix__c'\n        x1={22.112}\n        x2={28.154}\n        y1={7.354}\n        y2={13.396}\n        gradientUnits='userSpaceOnUse'\n      >\n        <Stop stopOpacity={0.2} />\n        <Stop offset={1} stopOpacity={0} />\n      </LinearGradient>\n    </Defs>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/zoom-in-map-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='m7.5 17.554-2.822 2.823a.73.73 0 0 1-.523.213.7.7 0 0 1-.532-.213.72.72 0 0 1-.217-.527q0-.31.217-.527L6.448 16.5H4.25a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.534A.73.73 0 0 1 4.25 15h3.847q.384 0 .644.26t.26.644v3.846q0 .318-.216.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534zm9 0v2.196q0 .318-.215.534a.73.73 0 0 1-.535.216.73.73 0 0 1-.534-.216.73.73 0 0 1-.215-.534v-3.846q0-.384.26-.644a.88.88 0 0 1 .643-.26h3.846q.32 0 .535.216a.73.73 0 0 1 .215.534q0 .32-.215.535a.73.73 0 0 1-.535.215h-2.196l2.824 2.823q.207.209.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217zM6.447 7.5 3.624 4.677a.73.73 0 0 1-.213-.522.7.7 0 0 1 .212-.532.72.72 0 0 1 .527-.217q.31 0 .527.217l2.824 2.823V4.25q0-.318.215-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .216.534v3.846q0 .383-.26.644a.88.88 0 0 1-.644.26H4.25a.73.73 0 0 1-.535-.216.73.73 0 0 1-.216-.534q0-.32.216-.534A.73.73 0 0 1 4.25 7.5zm11.107 0h2.197q.318 0 .534.216a.73.73 0 0 1 .215.534q0 .32-.215.535A.73.73 0 0 1 19.75 9h-3.846a.88.88 0 0 1-.644-.26.88.88 0 0 1-.26-.644V4.25q0-.318.216-.534a.73.73 0 0 1 .535-.216q.318 0 .534.216a.73.73 0 0 1 .215.534v2.196l2.823-2.823a.73.73 0 0 1 .523-.212.7.7 0 0 1 .532.212q.216.217.217.527a.72.72 0 0 1-.217.527z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/zoom-in-map.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M7.25 17.867 4.316 20.8a.72.72 0 0 1-.55.22.78.78 0 0 1-.55-.237.76.76 0 0 1-.229-.558.76.76 0 0 1 .23-.558l2.916-2.917H3.929a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.567q0-.325.231-.555a.77.77 0 0 1 .565-.23h4.116q.325 0 .557.232.23.232.23.556v4.117q0 .333-.231.564a.76.76 0 0 1-.558.232.76.76 0 0 1-.564-.232.77.77 0 0 1-.23-.564zm9.5 0v2.204q0 .333-.232.564a.76.76 0 0 1-.559.232.76.76 0 0 1-.564-.232.77.77 0 0 1-.229-.564v-4.117q0-.324.232-.556a.77.77 0 0 1 .564-.231h4.117q.325 0 .556.232.231.231.231.558a.76.76 0 0 1-.23.564.76.76 0 0 1-.557.229h-2.213l2.934 2.933a.75.75 0 0 1 .229.55.75.75 0 0 1-.23.55.76.76 0 0 1-.558.23.76.76 0 0 1-.558-.23zM6.133 7.25 3.2 4.317a.73.73 0 0 1-.221-.559.8.8 0 0 1 .237-.558.76.76 0 0 1 .559-.23.76.76 0 0 1 .558.23L7.25 6.133V3.921q0-.325.232-.557a.77.77 0 0 1 .566-.23q.327 0 .556.23.229.232.229.557v4.116q0 .333-.231.565a.76.76 0 0 1-.557.231H3.93a.77.77 0 0 1-.565-.232.77.77 0 0 1-.231-.567q0-.325.231-.555a.77.77 0 0 1 .565-.229zm11.733 0h2.213q.325 0 .556.232a.76.76 0 0 1 .231.558q0 .335-.23.564a.76.76 0 0 1-.557.23h-4.117a.77.77 0 0 1-.564-.232.77.77 0 0 1-.232-.565V3.921q0-.325.232-.557a.77.77 0 0 1 .567-.23q.327 0 .555.23.23.232.23.557v2.212l2.95-2.95a.76.76 0 0 1 .558-.229.76.76 0 0 1 .558.23.76.76 0 0 1 .23.558.76.76 0 0 1-.23.558z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/zoom-out-map-fill.tsx",
    "content": "import Svg, { Mask, Path, G } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Mask\n      id='prefix__a'\n      width={24}\n      height={24}\n      x={0}\n      y={0}\n      maskUnits='userSpaceOnUse'\n      style={{\n        maskType: \"alpha\",\n      }}\n    >\n      <Path fill='#D9D9D9' d='M0 0h24v24H0z' />\n    </Mask>\n    <G mask='url(#prefix__a)'>\n      <Path\n        fill={color}\n        d='M6.054 19H8.25q.319 0 .534.216A.73.73 0 0 1 9 19.75q0 .32-.216.535a.73.73 0 0 1-.534.215H4.404a.87.87 0 0 1-.644-.26.87.87 0 0 1-.26-.644V15.75q0-.319.216-.534A.73.73 0 0 1 4.25 15q.32 0 .535.216A.73.73 0 0 1 5 15.75v2.196l2.823-2.823a.73.73 0 0 1 .522-.212.7.7 0 0 1 .532.212q.217.217.217.527a.72.72 0 0 1-.217.527zm11.892 0-2.823-2.823a.73.73 0 0 1-.212-.522.7.7 0 0 1 .212-.532.72.72 0 0 1 .527-.217q.31 0 .527.217L19 17.946V15.75q0-.319.216-.534A.73.73 0 0 1 19.75 15q.32 0 .535.216a.73.73 0 0 1 .215.534v3.846q0 .383-.26.644a.87.87 0 0 1-.644.26H15.75a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534q0-.32.216-.535A.73.73 0 0 1 15.75 19zM5 6.054V8.25q0 .319-.216.534A.73.73 0 0 1 4.25 9a.73.73 0 0 1-.534-.216.73.73 0 0 1-.216-.534V4.404q0-.383.26-.644a.87.87 0 0 1 .644-.26H8.25q.319 0 .534.216A.73.73 0 0 1 9 4.25q0 .32-.216.535A.73.73 0 0 1 8.25 5H6.054l2.823 2.823q.208.208.212.522a.7.7 0 0 1-.212.532.72.72 0 0 1-.527.217.72.72 0 0 1-.527-.217zm14 0-2.823 2.823a.73.73 0 0 1-.522.212.7.7 0 0 1-.532-.212.72.72 0 0 1-.217-.527q0-.31.217-.527L17.946 5H15.75a.73.73 0 0 1-.534-.216A.73.73 0 0 1 15 4.25q0-.32.216-.534a.73.73 0 0 1 .534-.216h3.846q.383 0 .644.26.26.26.26.644V8.25q0 .319-.216.534A.73.73 0 0 1 19.75 9a.73.73 0 0 1-.535-.216A.73.73 0 0 1 19 8.25z'\n      />\n    </G>\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/components/zoom-out-map.tsx",
    "content": "import Svg, { Path } from \"react-native-svg\";\nimport type { SvgProps } from \"react-native-svg\";\nconst SvgComponent = ({ height, width, color }: SvgProps) => (\n  <Svg width={width} height={height} fill='none' viewBox='0 0 24 24'>\n    <Path\n      fill={color}\n      d='M5.85 19.333H8q.354 0 .594.24.24.242.24.596 0 .356-.24.593A.8.8 0 0 1 8 21H3.833a.8.8 0 0 1-.594-.24.8.8 0 0 1-.239-.593V16q0-.354.24-.594a.8.8 0 0 1 .596-.24q.356 0 .593.24a.8.8 0 0 1 .238.594v2.15l2.916-2.917a.8.8 0 0 1 .592-.241q.35 0 .592.241a.8.8 0 0 1 .241.592q0 .35-.241.592zm12.3 0-2.9-2.9a.78.78 0 0 1-.233-.591.84.84 0 0 1 .25-.592.8.8 0 0 1 .591-.242q.35 0 .592.242l2.883 2.9V16q0-.354.24-.594a.8.8 0 0 1 .596-.24q.356 0 .593.24A.8.8 0 0 1 21 16v4.167q0 .354-.24.593a.8.8 0 0 1-.593.24H16a.8.8 0 0 1-.594-.24.8.8 0 0 1-.24-.596q0-.356.24-.593a.8.8 0 0 1 .594-.238zM4.667 5.85V8q0 .354-.24.594a.8.8 0 0 1-.596.24.8.8 0 0 1-.594-.24A.8.8 0 0 1 3 8V3.833q0-.354.24-.594T3.833 3H8q.354 0 .594.24.24.241.24.596 0 .356-.24.593A.8.8 0 0 1 8 4.667H5.85l2.9 2.9a.8.8 0 0 1 .242.583q0 .342-.242.583a.8.8 0 0 1-.592.242.8.8 0 0 1-.591-.242zm14.666 0-2.9 2.9a.77.77 0 0 1-.583.233.82.82 0 0 1-.583-.25.8.8 0 0 1-.242-.591q0-.35.242-.592l2.883-2.883H16a.8.8 0 0 1-.594-.24.8.8 0 0 1-.24-.596q0-.356.24-.594A.8.8 0 0 1 16 3h4.167q.354 0 .593.24.24.24.24.593V8q0 .354-.24.594a.8.8 0 0 1-.596.24.8.8 0 0 1-.593-.24.8.8 0 0 1-.238-.594z'\n    />\n  </Svg>\n);\nexport default SvgComponent;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/icons/icon-mapping.tsx",
    "content": "import Code from \"./components/code\";\nimport CodeBlocks from \"./components/code-blocks\";\nimport AccountCircle from \"./components/account-circle\";\nimport AccountCircleFill from \"./components/account-circle-fill\";\nimport Activity from \"./components/activity\";\nimport Add from \"./components/add\";\nimport AddAPhoto from \"./components/add-a-photo\";\nimport AddAPhotoFill from \"./components/add-a-photo-fill\";\nimport AddBox from \"./components/add-box\";\nimport AddBoxFill from \"./components/add-box-fill\";\nimport AddCall from \"./components/add-call\";\nimport AddCallFill from \"./components/add-call-fill\";\nimport AddCircle from \"./components/add-circle\";\nimport AddCircleFill from \"./components/add-circle-fill\";\nimport AddComment from \"./components/add-comment\";\nimport AddCommentFill from \"./components/add-comment-fill\";\nimport AddFill from \"./components/add-fill\";\nimport AddReaction from \"./components/add-reaction\";\nimport AddReactionFill from \"./components/add-reaction-fill\";\nimport Ai from \"./components/ai\";\nimport AiConversationSummary from \"./components/ai-conversation-summary\";\nimport AiConversationSummaryFill from \"./components/ai-conversation-summary-fill\";\nimport AiFill from \"./components/ai-fill\";\nimport AiSuggestReply from \"./components/ai-suggest-reply\";\nimport AiSuggestReplyFill from \"./components/ai-suggest-reply-fill\";\nimport AlternateEmail from \"./components/alternate-email\";\nimport AlternateEmailFill from \"./components/alternate-email-fill\";\nimport Animals from \"./components/animals-&-nature\";\nimport Archive from \"./components/archive\";\nimport ArchiveFill from \"./components/archive-fill\";\nimport ArrowBack from \"./components/arrow-back\";\nimport ArrowBackFill from \"./components/arrow-back-fill\";\nimport ArrowForward from \"./components/arrow-forward\";\nimport ArrowForwardFill from \"./components/arrow-forward-fill\";\nimport AttachFile from \"./components/attach-file\";\nimport AttachFileFill from \"./components/attach-file-fill\";\nimport BarChart from \"./components/bar-chart\";\nimport BarChartFill from \"./components/bar-chart-fill\";\nimport BaseIcon from \"./components/base-icon\";\nimport Bedtime from \"./components/bedtime\";\nimport BedtimeFill from \"./components/bedtime-fill\";\nimport Block from \"./components/block\";\nimport BlockFill from \"./components/block-fill\";\nimport Bookmark from \"./components/bookmark\";\nimport BookmarkAdd from \"./components/bookmark-add\";\nimport BookmarkAddFill from \"./components/bookmark-add-fill\";\nimport BookmarkFill from \"./components/bookmark-fill\";\nimport CalendarAddOn from \"./components/calendar-add-on\";\nimport CalendarAddOnFill from \"./components/calendar-add-on-fill\";\nimport CalendarToday from \"./components/calendar-today\";\nimport CalendarTodayFill from \"./components/calendar-today-fill\";\nimport Call from \"./components/call\";\nimport CallEnd from \"./components/call-end\";\nimport CallEndFill from \"./components/call-end-fill\";\nimport CallFill from \"./components/call-fill\";\nimport CallLog from \"./components/call-log\";\nimport CallLogFill from \"./components/call-log-fill\";\nimport CallMade from \"./components/call-made\";\nimport CallMadeFill from \"./components/call-made-fill\";\nimport CallMissed from \"./components/call-missed\";\nimport CallMissedFill from \"./components/call-missed-fill\";\nimport CallMissedOutgoing from \"./components/call-missed-outgoing\";\nimport CallMissedOutgoingFill from \"./components/call-missed-outgoing-fill\";\nimport CallReceived from \"./components/call-received\";\nimport CallReceivedFill from \"./components/call-received-fill\";\nimport Cancel from \"./components/cancel\";\nimport CancelFill from \"./components/cancel-fill\";\nimport ChangeCircle from \"./components/change-circle\";\nimport ChangeCircleFill from \"./components/change-circle-fill\";\nimport Chat from \"./components/chat\";\nimport ChatBot from \"./components/chat-bot\";\nimport ChatBotFill from \"./components/chat-bot-fill\";\nimport ChatFill from \"./components/chat-fill\";\nimport Check from \"./components/check\";\nimport CheckCircle from \"./components/check-circle\";\nimport CheckCircleFill from \"./components/check-circle-fill\";\nimport CheckFill from \"./components/check-fill\";\nimport ChevronLeft from \"./components/chevron-left\";\nimport ChevronLeftFill from \"./components/chevron-left-fill\";\nimport ChevronRight from \"./components/chevron-right\";\nimport ChevronRightFill from \"./components/chevron-right-fill\";\nimport Close from \"./components/close\";\nimport CloseFill from \"./components/close-fill\";\nimport CollaborativeDocument from \"./components/collaborative-document\";\nimport CollaborativeDocumentFill from \"./components/collaborative-document-fill\";\nimport CollabDocumentIcon from \"./components/collaborative-document-icon\";\nimport CollaborativeWhiteboard from \"./components/collaborative-whiteboard\";\nimport CollaborativeWhiteboardFill from \"./components/collaborative-whiteboard-fill\";\nimport CollabWhiteBoardIcon from \"./components/collaborative-whiteboard-icon\";\nimport ContentCopy from \"./components/content-copy\";\nimport ContentCopyFill from \"./components/content-copy-fill\";\nimport Crop from \"./components/crop\";\nimport CropFill from \"./components/crop-fill\";\nimport Delete from \"./components/delete\";\nimport DeleteFill from \"./components/delete-fill\";\nimport Description from \"./components/description\";\nimport DescriptionFill from \"./components/description-fill\";\nimport DoneAll from \"./components/done-all\";\nimport DoneAllFill from \"./components/done-all-fill\";\nimport Download from \"./components/download\";\nimport DownloadFill from \"./components/download-fill\";\nimport Edit from \"./components/edit\";\nimport EditCalendar from \"./components/edit-calendar\";\nimport EditCalendarFill from \"./components/edit-calendar-fill\";\nimport EditFill from \"./components/edit-fill\";\nimport EditSquare from \"./components/edit-square\";\nimport EditSquareFill from \"./components/edit-square-fill\";\nimport EmptyState from \"./components/empty-state\";\nimport Error from \"./components/error\";\nimport ErrorFill from \"./components/error-fill\";\nimport ErrorState from \"./components/error-state\";\nimport Event from \"./components/event\";\nimport EventAvailable from \"./components/event-available\";\nimport EventAvailableFill from \"./components/event-available-fill\";\nimport EventFill from \"./components/event-fill\";\nimport Favorite from \"./components/favorite\";\nimport FavoriteFill from \"./components/favorite-fill\";\nimport FilterList from \"./components/filter-list\";\nimport FilterListFill from \"./components/filter-list-fill\";\nimport FilterListOff from \"./components/filter-list-off\";\nimport FilterListOffFill from \"./components/filter-list-off-fill\";\nimport Flags from \"./components/flags\";\nimport FormatBold from \"./components/format-bold\";\nimport FormatItalic from \"./components/format-italic\";\nimport FormatUnderlined from \"./components/format-underlined\";\nimport FormatStrikethrough from \"./components/format-strikethrough\";\nimport FormatListNumbered from \"./components/format-list-numbered\";\nimport FormatListBulleted from \"./components/format-list-bulleted\";\nimport FormatQuote from \"./components/format-quote\";\nimport FlipCameraIos from \"./components/flip-camera-ios\";\nimport FlipCameraIosFill from \"./components/flip-camera-ios-fill\";\nimport Food from \"./components/food-&-drink\";\nimport Forward from \"./components/forward\";\nimport ForwardFill from \"./components/forward-fill\";\nimport Gif from \"./components/gif\";\nimport GifFill from \"./components/gif-fill\";\nimport GraphicEq from \"./components/graphic-eq\";\nimport GraphicEqFill from \"./components/graphic-eq-fill\";\nimport Group from \"./components/group\";\nimport GroupAdd from \"./components/group-add\";\nimport GroupFill from \"./components/group-fill\";\nimport Help from \"./components/help\";\nimport HelpFill from \"./components/help-fill\";\nimport History from \"./components/history\";\nimport HistoryFill from \"./components/history-fill\";\nimport IncomingVideo from \"./components/incoming-video\";\nimport IncomingVideoFill from \"./components/incoming-video-fill\";\nimport IncomingAudio from \"./components/incoming-audio\";\nimport IncomingAudioFill from \"./components/incoming-audio-fill\";\nimport Info from \"./components/info\";\nimport InfoFill from \"./components/info-fill\";\nimport Keep from \"./components/keep\";\nimport KeepFill from \"./components/keep-fill\";\nimport KeepOff from \"./components/keep-off\";\nimport KeepOffFill from \"./components/keep-off-fill\";\nimport Keyboard from \"./components/keyboard\";\nimport KeyboardArrowDown from \"./components/keyboard-arrow-down\";\nimport KeyboardArrowDownFill from \"./components/keyboard-arrow-down-fill\";\nimport KeyboardArrowUp from \"./components/keyboard-arrow-up\";\nimport KeyboardArrowUpFill from \"./components/keyboard-arrow-up-fill\";\nimport KeyboardFill from \"./components/keyboard-fill\";\nimport Language from \"./components/language\";\nimport LanguageFill from \"./components/language-fill\";\nimport Link from \"./components/link\";\nimport LinkFill from \"./components/link-fill\";\nimport Loading from \"./components/loading\";\nimport LoadingFill from \"./components/loading-fill\";\nimport LocationOn from \"./components/location-on\";\nimport LocationOnFill from \"./components/location-on-fill\";\nimport Lock from \"./components/lock\";\nimport LockFill from \"./components/lock-fill\";\nimport LockOpen from \"./components/lock-open\";\nimport LockOpenFill from \"./components/lock-open-fill\";\nimport Logout from \"./components/logout\";\nimport LogoutFill from \"./components/logout-fill\";\nimport PersonOff from './components/person-off';\nimport Mail from \"./components/mail\";\nimport MailFill from \"./components/mail-fill\";\nimport Mic from \"./components/mic\";\nimport MicFill from \"./components/mic-fill\";\nimport MicOff from \"./components/mic-off\";\nimport MicOffFill from \"./components/mic-off-fill\";\nimport MissedVideoCall from \"./components/missed-video-call\";\nimport MissedVideoCallFill from \"./components/missed-video-call-fill\";\nimport Mood from \"./components/mood\";\nimport MoodFill from \"./components/mood-fill\";\nimport MoreVert from \"./components/more-vert\";\nimport MoreVertFill from \"./components/more-vert-fill\";\nimport NearMe from \"./components/near-me\";\nimport NearMeFill from \"./components/near-me-fill\";\nimport NoPhotography from \"./components/no-photography\";\nimport NoPhotographyFill from \"./components/no-photography-fill\";\nimport Notifications from \"./components/notifications\";\nimport NotificationsFill from \"./components/notifications-fill\";\nimport NotificationsOff from \"./components/notifications-off\";\nimport NotificationsOffFill from \"./components/notifications-off-fill\";\nimport Objects from \"./components/objects\";\nimport OutgoingVideo from \"./components/outgoing-video\";\nimport OutgoingVideoFill from \"./components/outgoing-video-fill\";\nimport OutgoingAudio from \"./components/outgoing-audio\";\nimport OutgoingAudioFill from \"./components/outgoing-audio-fill\";\nimport Pause from \"./components/pause\";\nimport PauseFill from \"./components/pause-fill\";\nimport Person from \"./components/person\";\nimport PersonAdd from \"./components/person-add\";\nimport PersonAddFill from \"./components/person-add-fill\";\nimport PersonFill from \"./components/person-fill\";\nimport PhoneInTalk from \"./components/phone-in-talk\";\nimport PhoneInTalkFill from \"./components/phone-in-talk-fill\";\nimport PhoneIncoming from \"./components/phone-incoming\";\nimport PhoneIncomingFill from \"./components/phone-incoming-fill\";\nimport PhoneMissed from \"./components/phone-missed\";\nimport PhoneMissedFill from \"./components/phone-missed-fill\";\nimport PhoneOutgoing from \"./components/phone-outgoing\";\nimport PhoneOutgoingFill from \"./components/phone-outgoing-fill\";\nimport Photo from \"./components/photo\";\nimport PhotoCamera from \"./components/photo-camera\";\nimport PhotoCameraFill from \"./components/photo-camera-fill\";\nimport PhotoFill from \"./components/photo-fill\";\nimport PlayArrow from \"./components/play-arrow\";\nimport PlayArrowFill from \"./components/play-arrow-fill\";\nimport PlayCircle from \"./components/play-circle\";\nimport PlayCircleFill from \"./components/play-circle-fill\";\nimport Poll from \"./components/poll\";\nimport PollFill from \"./components/poll-fill\";\nimport PollIcon from \"./components/poll-Icon\";\nimport Rearrange from \"./components/rearrange\";\nimport RearrangeFill from \"./components/rearrange-fill\";\nimport Refresh from \"./components/refresh\";\nimport RefreshFill from \"./components/refresh-fill\";\nimport Reply from \"./components/reply\";\nimport ReplyAll from \"./components/reply-all\";\nimport ReplyAllFill from \"./components/reply-all-fill\";\nimport ReplyFill from \"./components/reply-fill\";\nimport Retry from \"./components/retry\";\nimport RetryFill from \"./components/retry-fill\";\nimport Schedule from \"./components/schedule\";\nimport ScheduleFill from \"./components/schedule-fill\";\nimport ScreenShare from \"./components/screen-share\";\nimport ScreenShareFill from \"./components/screen-share-fill\";\nimport Search from \"./components/search\";\nimport SearchFill from \"./components/search-fill\";\nimport Send from \"./components/send\";\nimport SendFill from \"./components/send-fill\";\nimport Settings from \"./components/settings\";\nimport SettingsFill from \"./components/settings-fill\";\nimport Share from \"./components/share\";\nimport ShareFill from \"./components/share-fill\";\nimport Shield from \"./components/shield\";\nimport ShieldFill from \"./components/shield-fill\";\nimport Smileys from \"./components/smileys-&-people\";\nimport Star from \"./components/star\";\nimport StarFill from \"./components/star-fill\";\nimport Sticker from \"./components/sticker\";\nimport StickerFill from \"./components/sticker-fill\";\nimport StickerBase from \"./components/StickerBase-icon\";\nimport Stop from \"./components/stop\";\nimport StopCircle from \"./components/stop-circle\";\nimport StopCircleFill from \"./components/stop-circle-fill\";\nimport StopFill from \"./components/stop-fill\";\nimport StopScreenShare from \"./components/stop-screen-share\";\nimport StopScreenShareFill from \"./components/stop-screen-share-fill\";\nimport SubdirectoryArrowLeft from \"./components/subdirectory-arrow-left\";\nimport SubdirectoryArrowLeftFill from \"./components/subdirectory-arrow-left-fill\";\nimport SubdirectoryArrowRight from \"./components/subdirectory-arrow-right\";\nimport SubdirectoryArrowRightFill from \"./components/subdirectory-arrow-right-fill\";\nimport rightArrow from \"./components/right-arrow\";\nimport Sunny from \"./components/sunny\";\nimport SunnyFill from \"./components/sunny-fill\";\nimport Symbols from \"./components/symbols\";\nimport ThumbDown from \"./components/thumb-down\";\nimport ThumbDownFill from \"./components/thumb-down-fill\";\nimport ThumbUp from \"./components/thumb-up\";\nimport ThumbUpFill from \"./components/thumb-up-fill\";\nimport Translate from \"./components/translate\";\nimport TranslateFill from \"./components/translate-fill\";\nimport Travel from \"./components/travel-&-places\";\nimport Upload from \"./components/upload\";\nimport UploadFill from \"./components/upload-fill\";\nimport UserEmptyIcon from \"./components/user-empty-icon\";\nimport VideoCall from \"./components/video-call\";\nimport VideoCallFill from \"./components/video-call-fill\";\nimport Videocam from \"./components/videocam\";\nimport VideocamFill from \"./components/videocam-fill\";\nimport VideocamOff from \"./components/videocam-off\";\nimport VideocamOffFill from \"./components/videocam-off-fill\";\nimport VisibilityOff from \"./components/visibility-off\";\nimport VisibilityOffFill from \"./components/visibility-off-fill\";\nimport VolumeDown from \"./components/volume-down\";\nimport VolumeDownFill from \"./components/volume-down-fill\";\nimport VolumeMute from \"./components/volume-mute\";\nimport VolumeMuteFill from \"./components/volume-mute-fill\";\nimport VolumeOff from \"./components/volume-off\";\nimport VolumeOffFill from \"./components/volume-off-fill\";\nimport VolumeUp from \"./components/volume-up\";\nimport VolumeUpFill from \"./components/volume-up-fill\";\nimport Warning from \"./components/warning\";\nimport WarningFill from \"./components/warning-fill\";\nimport Wifi from \"./components/wifi\";\nimport WifiFill from \"./components/wifi-fill\";\nimport ZoomInMap from \"./components/zoom-in-map\";\nimport ZoomInMapFill from \"./components/zoom-in-map-fill\";\nimport ZoomOutMap from \"./components/zoom-out-map\";\nimport ZoomOutMapFill from \"./components/zoom-out-map-fill\";\n\n/******File Bubble Type Preview Icons*****/\nimport AudioFileType from \"./components/audio-file-type\";\nimport DocumentFileType from \"./components/document-file-type\";\nimport ImageFileType from \"./components/image-file-type\";\nimport ListItemCheck from \"./components/ListItemCheck\";\nimport PdfFileType from \"./components/pdf-file-type\";\nimport PresentationFileType from \"./components/presentation-file-type\";\nimport SpreadsheetFileType from \"./components/spreadsheet-file-type\";\nimport TextFileType from \"./components/text-file-type\";\nimport UnknownFileType from \"./components/unknown-file-type\";\nimport VideoFileType from \"./components/video-file-type\";\nimport ZipFileType from \"./components/zip-file-type\";\nimport AISendButton from \"./components/ai-send-button\";\nimport AINewChatButton from \"./components/ai-new-chat\";\nimport AIChatHistory from \"./components/ai-chat-history\";\nimport AICopyOption from \"./components/ai-copy-option\";\nimport EmptySearch from \"./components/empty-search\";\nimport Unread from \"./components/unread\";\nimport DocumentIcon from \"./components/documents\";\n\nimport MessageBlocked from \"./components/message-blocked\";\n\n/**************************************** */\n\nexport const ICONS = {\n  \"account-circle\": AccountCircle,\n  \"account-circle-fill\": AccountCircleFill,\n  add: Add,\n  \"add-fill\": AddFill,\n  \"add-a-photo\": AddAPhoto,\n  \"add-a-photo-fill\": AddAPhotoFill,\n  \"add-box\": AddBox,\n  \"add-box-fill\": AddBoxFill,\n  \"add-call\": AddCall,\n  \"add-call-fill\": AddCallFill,\n  \"add-circle\": AddCircle,\n  \"add-circle-fill\": AddCircleFill,\n  \"add-comment\": AddComment,\n  \"add-comment-fill\": AddCommentFill,\n  \"add-reaction\": AddReaction,\n  \"add-reaction-fill\": AddReactionFill,\n  \"ai-conversation-summary\": AiConversationSummary,\n  \"ai-conversation-summary-fill\": AiConversationSummaryFill,\n  \"ai-suggest-reply\": AiSuggestReply,\n  \"ai-suggest-reply-fill\": AiSuggestReplyFill,\n  ai: Ai,\n  \"ai-fill\": AiFill,\n  \"alternate-email\": AlternateEmail,\n  \"alternate-email-fill\": AlternateEmailFill,\n  archive: Archive,\n  \"archive-fill\": ArchiveFill,\n  \"arrow-back\": ArrowBack,\n  \"arrow-back-fill\": ArrowBackFill,\n  \"arrow-forward\": ArrowForward,\n  \"arrow-forward-fill\": ArrowForwardFill,\n  \"attach-file\": AttachFile,\n  \"attach-file-fill\": AttachFileFill,\n  \"bar-chart\": BarChart,\n  \"bar-chart-fill\": BarChartFill,\n  bedtime: Bedtime,\n  \"bedtime-fill\": BedtimeFill,\n  block: Block,\n  \"block-fill\": BlockFill,\n  bookmark: Bookmark,\n  \"bookmark-fill\": BookmarkFill,\n  \"bookmark-add\": BookmarkAdd,\n  \"bookmark-add-fill\": BookmarkAddFill,\n  \"calendar-add-on\": CalendarAddOn,\n  \"calendar-add-on-fill\": CalendarAddOnFill,\n  \"calendar-today\": CalendarToday,\n  \"calendar-today-fill\": CalendarTodayFill,\n  call: Call,\n  \"call-fill\": CallFill,\n  \"call-end\": CallEnd,\n  \"call-end-fill\": CallEndFill,\n  \"call-log\": CallLog,\n  \"call-log-fill\": CallLogFill,\n  \"call-made\": CallMade,\n  \"call-made-fill\": CallMadeFill,\n  \"call-missed\": CallMissed,\n  \"call-missed-fill\": CallMissedFill,\n  \"call-missed-outgoing\": CallMissedOutgoing,\n  \"call-missed-outgoing-fill\": CallMissedOutgoingFill,\n  \"call-received\": CallReceived,\n  \"call-received-fill\": CallReceivedFill,\n  cancel: Cancel,\n  \"cancel-fill\": CancelFill,\n  \"change-circle\": ChangeCircle,\n  \"change-circle-fill\": ChangeCircleFill,\n  chat: Chat,\n  \"chat-fill\": ChatFill,\n  \"chat-bot\": ChatBot,\n  \"chat-bot-fill\": ChatBotFill,\n  check: Check,\n  \"check-fill\": CheckFill,\n  \"check-circle\": CheckCircle,\n  \"check-circle-fill\": CheckCircleFill,\n  \"chevron-left\": ChevronLeft,\n  \"chevron-left-fill\": ChevronLeftFill,\n  \"chevron-right\": ChevronRight,\n  \"chevron-right-fill\": ChevronRightFill,\n  close: Close,\n  \"close-fill\": CloseFill,\n  \"collaborative-whiteboard\": CollaborativeWhiteboard,\n  \"collaborative-whiteboard-fill\": CollaborativeWhiteboardFill,\n  \"collaborative-document\": CollaborativeDocument,\n  \"collaborative-document-fill\": CollaborativeDocumentFill,\n  \"content-copy\": ContentCopy,\n  \"content-copy-fill\": ContentCopyFill,\n  crop: Crop,\n  \"crop-fill\": CropFill,\n  delete: Delete,\n  \"delete-fill\": DeleteFill,\n  description: Description,\n  \"description-fill\": DescriptionFill,\n  \"done-all\": DoneAll,\n  \"done-all-fill\": DoneAllFill,\n  download: Download,\n  \"download-fill\": DownloadFill,\n  \"edit-square\": EditSquare,\n  \"edit-square-fill\": EditSquareFill,\n  edit: Edit,\n  \"edit-fill\": EditFill,\n  \"edit-calendar\": EditCalendar,\n  \"edit-calendar-fill\": EditCalendarFill,\n  error: Error,\n  \"error-fill\": ErrorFill,\n  \"error-state\": ErrorState,\n  \"empty-state\": EmptyState,\n  \"event-available\": EventAvailable,\n  \"event-available-fill\": EventAvailableFill,\n  event: Event,\n  \"event-fill\": EventFill,\n  favorite: Favorite,\n  \"favorite-fill\": FavoriteFill,\n  \"filter-list\": FilterList,\n  \"filter-list-fill\": FilterListFill,\n  \"filter-list-off\": FilterListOff,\n  \"filter-list-off-fill\": FilterListOffFill,\n  \"flip-camera-ios\": FlipCameraIos,\n  \"flip-camera-ios-fill\": FlipCameraIosFill,\n  \"format-bold\": FormatBold,\n  \"format-italic\": FormatItalic,\n  \"format-underlined\": FormatUnderlined,\n  \"format-strikethrough\": FormatStrikethrough,\n  \"format-list-numbered\": FormatListNumbered,\n  \"format-list-bulleted\": FormatListBulleted,\n  \"format-quote\": FormatQuote,\n  code: Code,\n  \"code-blocks\": CodeBlocks,\n  forward: Forward,\n  \"forward-fill\": ForwardFill,\n  gif: Gif,\n  \"gif-fill\": GifFill,\n  \"graphic-eq\": GraphicEq,\n  \"graphic-eq-fill\": GraphicEqFill,\n  group: Group,\n  \"group-fill\": GroupFill,\n  help: Help,\n  \"help-fill\": HelpFill,\n  history: History,\n  \"history-fill\": HistoryFill,\n  \"incoming-video\": IncomingVideo,\n  \"incoming-video-fill\": IncomingVideoFill,\n  \"incoming-audio-fill\": IncomingAudioFill,\n  \"incoming-audio\": IncomingAudio,\n  info: Info,\n  \"info-fill\": InfoFill,\n  keep: Keep,\n  \"keep-fill\": KeepFill,\n  \"keep-off\": KeepOff,\n  \"keep-off-fill\": KeepOffFill,\n  keyboard: Keyboard,\n  \"keyboard-fill\": KeyboardFill,\n  \"keyboard-arrow-down\": KeyboardArrowDown,\n  \"keyboard-arrow-down-fill\": KeyboardArrowDownFill,\n  \"keyboard-arrow-up\": KeyboardArrowUp,\n  \"keyboard-arrow-up-fill\": KeyboardArrowUpFill,\n  language: Language,\n  \"language-fill\": LanguageFill,\n  link: Link,\n  \"link-fill\": LinkFill,\n  loading: Loading,\n  \"loading-fill\": LoadingFill,\n  \"location-on\": LocationOn,\n  \"location-on-fill\": LocationOnFill,\n  lock: Lock,\n  \"lock-fill\": LockFill,\n  \"lock-open\": LockOpen,\n  \"lock-open-fill\": LockOpenFill,\n  logout: Logout,\n  \"logout-fill\": LogoutFill,\n  mail: Mail,\n  \"mail-fill\": MailFill,\n  \"group-add\": GroupAdd,\n  mic: Mic,\n  \"mic-fill\": MicFill,\n  \"mic-off\": MicOff,\n  \"mic-off-fill\": MicOffFill,\n  \"missed-video-call\": MissedVideoCall,\n  \"missed-video-call-fill\": MissedVideoCallFill,\n  mood: Mood,\n  \"mood-fill\": MoodFill,\n  \"more-vert\": MoreVert,\n  \"more-vert-fill\": MoreVertFill,\n  \"near-me\": NearMe,\n  \"near-me-fill\": NearMeFill,\n  notifications: Notifications,\n  \"notifications-fill\": NotificationsFill,\n  \"notifications-off\": NotificationsOff,\n  \"notifications-off-fill\": NotificationsOffFill,\n  \"no-photography\": NoPhotography,\n  \"no-photography-fill\": NoPhotographyFill,\n  \"outgoing-video\": OutgoingVideo,\n  \"outgoing-video-fill\": OutgoingVideoFill,\n  \"outgoing-audio\": OutgoingAudio,\n  \"outgoing-audio-fill\": OutgoingAudioFill,\n  pause: Pause,\n  \"pause-fill\": PauseFill,\n  person: Person,\n  \"person-fill\": PersonFill,\n  \"person-add\": PersonAdd,\n  \"person-add-fill\": PersonAddFill,\n  \"phone-incoming\": PhoneIncoming,\n  \"phone-incoming-fill\": PhoneIncomingFill,\n  \"phone-in-talk\": PhoneInTalk,\n  \"phone-in-talk-fill\": PhoneInTalkFill,\n  \"phone-missed\": PhoneMissed,\n  \"phone-missed-fill\": PhoneMissedFill,\n  \"phone-outgoing\": PhoneOutgoing,\n  \"phone-outgoing-fill\": PhoneOutgoingFill,\n  \"list-item-check\": ListItemCheck,\n  photo: Photo,\n  \"photo-fill\": PhotoFill,\n  'person-off':PersonOff,\n  \"photo-camera\": PhotoCamera,\n  \"photo-camera-fill\": PhotoCameraFill,\n  \"play-arrow\": PlayArrow,\n  \"play-arrow-fill\": PlayArrowFill,\n  \"play-circle\": PlayCircle,\n  \"play-circle-fill\": PlayCircleFill,\n  poll: Poll,\n  \"poll-fill\": PollFill,\n  rearrange: Rearrange,\n  \"rearrange-fill\": RearrangeFill,\n  refresh: Refresh,\n  \"refresh-fill\": RefreshFill,\n  reply: Reply,\n  \"reply-fill\": ReplyFill,\n  \"reply-all\": ReplyAll,\n  \"reply-all-fill\": ReplyAllFill,\n  retry: Retry,\n  \"retry-fill\": RetryFill,\n  schedule: Schedule,\n  \"schedule-fill\": ScheduleFill,\n  \"screen-share\": ScreenShare,\n  \"screen-share-fill\": ScreenShareFill,\n  search: Search,\n  \"search-fill\": SearchFill,\n  send: Send,\n  \"send-fill\": SendFill,\n  settings: Settings,\n  \"settings-fill\": SettingsFill,\n  share: Share,\n  \"share-fill\": ShareFill,\n  shield: Shield,\n  \"shield-fill\": ShieldFill,\n  star: Star,\n  \"star-fill\": StarFill,\n  sticker: Sticker,\n  \"sticker-fill\": StickerFill,\n  stop: Stop,\n  \"stop-fill\": StopFill,\n  \"stop-circle\": StopCircle,\n  \"stop-circle-fill\": StopCircleFill,\n  \"stop-screen-share\": StopScreenShare,\n  \"stop-screen-share-fill\": StopScreenShareFill,\n  \"subdirectory-arrow-left\": SubdirectoryArrowLeft,\n  \"subdirectory-arrow-left-fill\": SubdirectoryArrowLeftFill,\n  \"subdirectory-arrow-right\": SubdirectoryArrowRight,\n  \"subdirectory-arrow-right-fill\": SubdirectoryArrowRightFill,\n  \"right-arrow\": rightArrow,\n  sunny: Sunny,\n  \"sunny-fill\": SunnyFill,\n  \"thumb-down\": ThumbDown,\n  \"thumb-down-fill\": ThumbDownFill,\n  \"thumb-up\": ThumbUp,\n  \"thumb-up-fill\": ThumbUpFill,\n  translate: Translate,\n  \"translate-fill\": TranslateFill,\n  upload: Upload,\n  \"upload-fill\": UploadFill,\n  videocam: Videocam,\n  \"videocam-fill\": VideocamFill,\n  \"videocam-off\": VideocamOff,\n  \"videocam-off-fill\": VideocamOffFill,\n  \"video-call\": VideoCall,\n  \"video-call-fill\": VideoCallFill,\n  \"visibility-off\": VisibilityOff,\n  \"visibility-off-fill\": VisibilityOffFill,\n  \"volume-down\": VolumeDown,\n  \"volume-down-fill\": VolumeDownFill,\n  \"volume-mute\": VolumeMute,\n  \"volume-mute-fill\": VolumeMuteFill,\n  \"volume-off\": VolumeOff,\n  \"volume-off-fill\": VolumeOffFill,\n  \"volume-up\": VolumeUp,\n  \"volume-up-fill\": VolumeUpFill,\n  warning: Warning,\n  \"warning-fill\": WarningFill,\n  wifi: Wifi,\n  \"user-empty-icon\": UserEmptyIcon,\n  \"wifi-fill\": WifiFill,\n  \"zoom-in-map\": ZoomInMap,\n  \"zoom-in-map-fill\": ZoomInMapFill,\n  \"zoom-out-map\": ZoomOutMap,\n  \"zoom-out-map-fill\": ZoomOutMapFill,\n  \"base-icon\": BaseIcon,\n  \"sticker-base\": StickerBase,\n  \"activity-emoji\": Activity,\n  \"animals-nature-emoji\": Animals,\n  \"flags-emoji\": Flags,\n  \"food-drink-emoji\": Food,\n  \"objects-emoji\": Objects,\n  \"smiley-emoji\": Smileys,\n  \"symbols-emoji\": Symbols,\n  \"travel-emoji\": Travel,\n  poll_icon: PollIcon,\n  \"collaborative-document-icon\": CollabDocumentIcon,\n  \"collaborative-whiteboard-icon\": CollabWhiteBoardIcon,\n\n  /******File Bubble Type Preview Icons*****/\n  \"audio-file-type\": AudioFileType,\n  \"image-file-type\": ImageFileType,\n  \"pdf-file-type\": PdfFileType,\n  \"presentation-file-type\": PresentationFileType,\n  \"spreadsheet-file-type\": SpreadsheetFileType,\n  \"text-file-type\": TextFileType,\n  \"unknown-file-type\": UnknownFileType,\n  \"video-file-type\": VideoFileType,\n  \"document-file-type\": DocumentFileType,\n  \"zip-file-type\": ZipFileType,\n  /***************************************/\n\n  /******AI Agent Icons******/\n  \"ai-send-button\": AISendButton,\n  \"ai-new-chat\": AINewChatButton,\n  \"ai-chat-history\": AIChatHistory,\n  \"ai-copy-option\": AICopyOption,\n\n  \"empty-search\": EmptySearch,\n  \"unread\":Unread,\n  \"documents\":DocumentIcon,\n  \"message-blocked\": MessageBlocked,\n\n} as const;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/index.ts",
    "content": "import * as CometChatUiKitConstants from \"./constants/UIKitConstants\";\nexport { CometChatUiKitConstants };\n\nexport { Icon } from \"./icons/Icon\";\n\nexport type {\n  AdditionalParams,\n  ConversationType,\n  MessageBubbleAlignmentType,\n  MessageListAlignmentType,\n  MessageTimeAlignmentType,\n  SelectionMode,\n} from \"./base\";\n\nexport {\n  CometChatConversationEvents,\n  CometChatGroupsEvents,\n  CometChatUIEventHandler,\n  CometChatUIEvents,\n  MessageEvents,\n} from \"./events\";\n\nexport type { DataSource } from \"./framework\";\n\nexport {\n  ChatConfigurator,\n  DataSourceDecorator,\n  ExtensionsDataSource,\n  MessageDataSource,\n} from \"./framework\";\n\nexport type {\n  CometChatMessageOption,\n} from \"./modals\";\n\nexport {\n  CometChatMessageTemplate,\n} from \"./modals\";\n\nexport {\n  CometChatConversationUtils,\n  CometChatMessagePreview,\n  CometChatSoundManager,\n} from \"./utils\";\n\nexport {\n  CometChatActionSheet,\n  CometChatAudioBubble,\n  CometChatAvatar,\n  CometChatBadge,\n  CometChatBottomSheet,\n  CometChatConfirmDialog,\n  CometChatReportDialog,\n  CometChatDate,\n  CometChatEmojiKeyboard,\n  CometChatFileBubble,\n  CometChatImageBubble,\n  CometChatInlineAudioRecorder,\n  CometChatList,\n  CometChatListItem,\n  CometChatMediaRecorder,\n  CometChatMessageInput,\n  CometChatQuickReactions,\n  CometChatReactionList,\n  CometChatReactions,\n  CometChatRetryButton,\n  CometChatStatusIndicator,\n  CometChatSuggestionList,\n  CometChatTextBubble,\n  CometChatVideoBubble,\n  SuggestionItem,\n  CometChatReceipt,\n} from \"./views\";\n\nexport type{\n  ActionItemInterface,\n  AudioWaveformVisualizerProps,\n  BadgeStyle,\n  CometChatAudioBubbleInterface,\n  CometChatBottomSheetInterface,\n  CometChatConfirmDialogInterface,\n  CometChatDateInterface,\n  CometChatFileBubbleInterface,\n  CometChatImageBubbleInterface,\n  CometChatInlineAudioRecorderProps,\n  CometChatInlineAudioRecorderStyle,\n  CometChatListActionsInterface,\n  CometChatListItemInterface,\n  CometChatListProps,\n  CometChatListStylesInterface,\n  CometChatMediaRecorderInterface,\n  CometChatMessageInputInterface,\n  CometChatReactionListInterface,\n  CometChatReactionsInterface,\n  CometChatRetryButtonProps,\n  CometChatStatusIndicatorInterface,\n  CometChatSuggestionListInterface,\n  CometChatTextBubbleInterface,\n  CometChatVideoBubbleInterface,\n  DateStyle,\n  MenuItemInterface,\n  RecorderState,\n  UseAudioRecorderReturn,\n  WaveformStyle,\n} from \"./views\";\n\nexport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUrlsFormatter,\n  CometChatRichTextFormatter,\n  MentionTextStyle,\n  RichTextStyle,\n} from \"./formatters\";\n\nexport { CometChatUIKit, CometChatUIKitHelper, UIKitSettings } from \"./CometChatUiKit\";\n\nexport type { CometChatMessageComposerAction } from \"./helper/types\";\n\nexport { messageStatus } from \"./utils/CometChatMessageHelper/index\";\n\nexport { getCometChatTranslation, getCurrentLanguage} from \"./resources/CometChatLocalizeNew/LocalizationManager\";\nexport { useLocalizedDate } from \"./helper/useLocalizedDateHook\";\nexport { LocalizedDateHelper } from \"./helper/LocalizedDateHelper\";\nexport { useCometChatTranslation } from \"./resources/CometChatLocalizeNew/useCometChatTranslationHook\";\nexport {getLastSeenTime} from \"./helper/helperFunctions\";\n\n// Composer helper utilities for shared functionality between message composers\nexport {\n  // Agentic user helpers\n  isAgenticUser,\n  deriveHideButton,\n  deriveDisableFeature,\n  applyAgenticSendDelay,\n  trackAgenticParentMessageId,\n  getParentMessageId,\n  // Reply message helpers\n  setQuotedMessageSafe,\n  getReplyMessageId,\n  hasActiveReply,\n  handleReplyOnSend,\n  // Send button state helpers\n  isSendButtonDisabled,\n  getSendButtonTint,\n  // Message creation helpers\n  createTextMessage,\n  createMediaMessage,\n  // Mention helpers\n  parseMentionKey,\n  calcDeletionRange,\n  collectOverlappingMentions,\n  shiftRemainingMentionKeys,\n  // Utility helpers\n  generateMessageMuid,\n  isTextEmpty,\n  getMessageIdSafe,\n} from \"./helper/composerHelpers\";\n\nexport type {\n  ReplyMessageState,\n  MessagePreviewState,\n  AgenticSendDelayConfig,\n  AgenticParentMessageConfig,\n  ReplyMessageSendConfig,\n  SendButtonStateConfig,\n  CreateTextMessageConfig,\n  CreateMediaMessageConfig,\n  MentionOverlap,\n} from \"./helper/composerHelpers\";"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/ImageZoom/ReactNativeZoomableView.tsx",
    "content": "import React, { Component, createRef, RefObject } from 'react';\nimport {\n  Animated,\n  Easing,\n  GestureResponderEvent,\n  InteractionManager,\n  PanResponder,\n  PanResponderGestureState,\n  StyleSheet,\n  View,\n} from 'react-native';\n\nimport {\n  Vec2D,\n  ReactNativeZoomableViewProps,\n  ReactNativeZoomableViewState,\n  TouchPoint,\n  ZoomableViewEvent,\n} from './types';\nimport { applyPanBoundariesToOffset, calcGestureTouchDistance, calcNewScaledOffsetForZoomCentering, getBoundaryCrossedAnim, getPanMomentumDecayAnim, getZoomToAnimation } from './helper';\n\n\nconst initialState = {\n  originalWidth: null,\n  originalHeight: null,\n  originalPageX: null,\n  originalPageY: null,\n} as unknown as ReactNativeZoomableViewState;\nfunction calcGestureCenterPoint(\n  e: GestureResponderEvent,\n  gestureState: PanResponderGestureState\n): Vec2D | null {\n  const touches = e?.nativeEvent?.touches;\n  if (!touches[0]) return null;\n\n  if (gestureState.numberActiveTouches === 2) {\n    if (!touches[1]) return null;\n    return {\n      x: (touches[0].pageX + touches[1].pageX) / 2,\n      y: (touches[0].pageY + touches[1].pageY) / 2,\n    };\n  }\n  if (gestureState.numberActiveTouches === 1) {\n    return {\n      x: touches[0].pageX,\n      y: touches[0].pageY,\n    };\n  }\n\n  return null;\n}\n\nclass ReactNativeZoomableView extends Component<\n  ReactNativeZoomableViewProps,\n  ReactNativeZoomableViewState\n> {\n  zoomSubjectWrapperRef: RefObject<View | null>;\n  gestureHandlers: any;\n  doubleTapFirstTapReleaseTimestamp!: number | any;\n\n  static defaultProps = {\n    zoomEnabled: true,\n    panEnabled: true,\n    initialZoom: 1,\n    initialOffsetX: 0,\n    initialOffsetY: 0,\n    maxZoom: 3.5,\n    minZoom: 1,\n    pinchToZoomInSensitivity: 1,\n    pinchToZoomOutSensitivity: 1,\n    movementSensibility: 1,\n    doubleTapDelay: 300,\n    bindToBorders: true,\n    zoomStep: 0.5,\n    onLongPress: null,\n    longPressDuration: 700,\n    contentWidth: undefined,\n    contentHeight: undefined,\n    panBoundaryPadding: 0,\n    disablePanOnInitialZoom: false,\n  };\n\n  private panAnim = new Animated.ValueXY({ x: 0, y: 0 });\n  private zoomAnim = new Animated.Value(1);\n\n  private __offsets = {\n    x: {\n      value: 0,\n      boundaryCrossedAnimInEffect: false,\n    },\n    y: {\n      value: 0,\n      boundaryCrossedAnimInEffect: false,\n    },\n  };\n\n  private zoomLevel: any = 1;\n  private lastGestureCenterPosition: { x: number; y: number } | null = null;\n  private lastGestureTouchDistance: number | any;\n  private gestureType: 'pinch' | 'shift' | 'null' | any;\n  private gestureStarted = false;\n\n  /**\n   * Last press time (used to evaluate whether user double tapped)\n   * @type {number}\n   */\n  private longPressTimeout: any = null;\n  private onTransformInvocationInitialized: boolean | any;\n  private singleTapTimeoutId: any;\n  private touches: TouchPoint[] = [];\n  private doubleTapFirstTap: TouchPoint | any;\n  private measureZoomSubjectInterval: any;\n\n  constructor(props: any) {\n    super(props);\n\n    this.gestureHandlers = PanResponder.create({\n      onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,\n      onPanResponderGrant: this._handlePanResponderGrant,\n      onPanResponderMove: this._handlePanResponderMove,\n      onPanResponderRelease: this._handlePanResponderEnd,\n      onPanResponderTerminate: (evt, gestureState) => {\n        // We should also call _handlePanResponderEnd\n        // to properly perform cleanups when the gesture is terminated\n        // (aka gesture handling responsibility is taken over by another component).\n        // This also fixes a weird issue where\n        // on real device, sometimes onPanResponderRelease is not called when you lift 2 fingers up,\n        // but onPanResponderTerminate is called instead for no apparent reason.\n        this._handlePanResponderEnd(evt, gestureState);\n        this.props.onPanResponderTerminate?.(\n          evt,\n          gestureState,\n          this._getZoomableViewEventObject()\n        );\n      },\n      onPanResponderTerminationRequest: (evt, gestureState) =>\n        !!this.props.onPanResponderTerminationRequest?.(\n          evt,\n          gestureState,\n          this._getZoomableViewEventObject()\n        ),\n      // Defaults to true to prevent parent components, such as React Navigation's tab view, from taking over as responder.\n      onShouldBlockNativeResponder: (evt, gestureState) =>\n        this.props.onShouldBlockNativeResponder?.(\n          evt,\n          gestureState,\n          this._getZoomableViewEventObject()\n        ) ?? true,\n      onStartShouldSetPanResponderCapture: (evt, gestureState) =>\n        this.props.onStartShouldSetPanResponderCapture?.(evt, gestureState),\n      onMoveShouldSetPanResponderCapture: (evt, gestureState) =>\n        this.props.onMoveShouldSetPanResponderCapture?.(evt, gestureState),\n    });\n\n    this.zoomSubjectWrapperRef = createRef<View>();\n\n    if (this.props.zoomAnimatedValue)\n      this.zoomAnim = this.props.zoomAnimatedValue;\n    if (this.props.panAnimatedValueXY)\n      this.panAnim = this.props.panAnimatedValueXY;\n\n    this.zoomLevel = props.initialZoom;\n    this.offsetX = props.initialOffsetX;\n    this.offsetY = props.initialOffsetY;\n\n    this.panAnim.setValue({ x: this.offsetX, y: this.offsetY });\n    this.zoomAnim.setValue(this.zoomLevel);\n    this.panAnim.addListener(({ x, y }) => {\n      this.offsetX = x;\n      this.offsetY = y;\n    });\n    this.zoomAnim.addListener(({ value }) => {\n      this.zoomLevel = value;\n    });\n\n    this.state = {\n      ...initialState,\n    };\n\n    this.lastGestureTouchDistance = 150;\n\n    this.gestureType = null;\n  }\n\n  private set offsetX(x: any) {\n    this.__setOffset('x', x);\n  }\n  private set offsetY(y: any) {\n    this.__setOffset('y', y);\n  }\n  private get offsetX() {\n    return this.__getOffset('x');\n  }\n  private get offsetY() {\n    return this.__getOffset('y');\n  }\n  private __setOffset(axis: 'x' | 'y', offset: number) {\n    const offsetState = this.__offsets[axis];\n    const animValue = this.panAnim?.[axis];\n\n    if (this.props.bindToBorders) {\n      const containerSize =\n        axis === 'x' ? this.state?.originalWidth : this.state?.originalHeight;\n      const contentSize =\n        axis === 'x'\n          ? this.props.contentWidth || this.state?.originalWidth\n          : this.props.contentHeight || this.state?.originalHeight;\n\n      const boundOffset =\n        contentSize && containerSize\n          ? applyPanBoundariesToOffset(\n              offset,\n              containerSize,\n              contentSize,\n              this.zoomLevel,\n              this.props.panBoundaryPadding\n            )\n          : offset;\n\n      if (\n        animValue &&\n        !this.gestureType &&\n        !offsetState.boundaryCrossedAnimInEffect\n      ) {\n        const boundariesApplied =\n          boundOffset !== offset &&\n          boundOffset.toFixed(3) !== offset.toFixed(3);\n        if (boundariesApplied) {\n          offsetState.boundaryCrossedAnimInEffect = true;\n          getBoundaryCrossedAnim(this.panAnim[axis], boundOffset).start(() => {\n            offsetState.boundaryCrossedAnimInEffect = false;\n          });\n          return;\n        }\n      }\n    }\n\n    offsetState.value = offset;\n  }\n  private __getOffset(axis: 'x' | 'y') {\n    return this.__offsets[axis].value;\n  }\n\n  componentDidUpdate(\n    prevProps: ReactNativeZoomableViewProps,\n    prevState: ReactNativeZoomableViewState\n  ) {\n    const { zoomEnabled, initialZoom } = this.props;\n    if (prevProps.zoomEnabled && !zoomEnabled) {\n      this.zoomLevel = initialZoom;\n      this.zoomAnim.setValue(this.zoomLevel);\n    }\n    if (\n      !this.onTransformInvocationInitialized &&\n      this._invokeOnTransform().successful\n    ) {\n      this.panAnim.addListener(() => this._invokeOnTransform());\n      this.zoomAnim.addListener(() => this._invokeOnTransform());\n      this.onTransformInvocationInitialized = true;\n    }\n\n    const currState = this.state;\n    const originalMeasurementsChanged =\n      currState.originalHeight !== prevState.originalHeight ||\n      currState.originalWidth !== prevState.originalWidth ||\n      currState.originalPageX !== prevState.originalPageX ||\n      currState.originalPageY !== prevState.originalPageY;\n\n    if (this.onTransformInvocationInitialized && originalMeasurementsChanged) {\n      this._invokeOnTransform();\n    }\n  }\n\n  componentDidMount() {\n    this.grabZoomSubjectOriginalMeasurements();\n    // We've already run `grabZoomSubjectOriginalMeasurements` at various events\n    // to make sure the measurements are promptly updated.\n    // However, there might be cases we haven't accounted for, especially when\n    // native processes are involved. To account for those cases,\n    // we'll use an interval here to ensure we're always up-to-date.\n    // The `setState` in `grabZoomSubjectOriginalMeasurements` won't trigger a rerender\n    // if the values given haven't changed, so we're not running performance risk here.\n    this.measureZoomSubjectInterval = setInterval(\n      this.grabZoomSubjectOriginalMeasurements,\n      1e3\n    );\n  }\n\n  componentWillUnmount() {\n    clearInterval(this.measureZoomSubjectInterval);\n  }\n\n  /**\n   * try to invoke onTransform\n   * @private\n   */\n  _invokeOnTransform() {\n    const zoomableViewEvent = this._getZoomableViewEventObject();\n\n    if (!zoomableViewEvent.originalWidth || !zoomableViewEvent.originalHeight)\n      return { successful: false };\n\n    this.props.onTransform?.(zoomableViewEvent);\n\n    return { successful: true };\n  }\n\n  /**\n   * Returns additional information about components current state for external event hooks\n   *\n   * @returns {{}}\n   * @private\n   */\n  _getZoomableViewEventObject(overwriteObj = {}): ZoomableViewEvent {\n    return {\n      zoomLevel: this.zoomLevel,\n      offsetX: this.offsetX,\n      offsetY: this.offsetY,\n      originalHeight: this.state.originalHeight,\n      originalWidth: this.state.originalWidth,\n      originalPageX: this.state.originalPageX,\n      originalPageY: this.state.originalPageY,\n      ...overwriteObj,\n    } as ZoomableViewEvent;\n  }\n\n  /**\n   * Get the original box dimensions and save them for later use.\n   * (They will be used to calculate boxBorders)\n   *\n   * @private\n   */\n  private grabZoomSubjectOriginalMeasurements = () => {\n    // make sure we measure after animations are complete\n    InteractionManager.runAfterInteractions(() => {\n      // this setTimeout is here to fix a weird issue on iOS where the measurements are all `0`\n      // when navigating back (react-navigation stack) from another view\n      // while closing the keyboard at the same time\n      setTimeout(() => {\n        // In normal conditions, we're supposed to measure zoomSubject instead of its wrapper.\n        // However, our zoomSubject may have been transformed by an initial zoomLevel or offset,\n        // in which case these measurements will not represent the true \"original\" measurements.\n        // We just need to make sure the zoomSubjectWrapper perfectly aligns with the zoomSubject\n        // (no border, space, or anything between them)\n        const zoomSubjectWrapperRef = this.zoomSubjectWrapperRef;\n        // we don't wanna measure when zoomSubjectWrapperRef is not yet available or has been unmounted\n        zoomSubjectWrapperRef.current?.measureInWindow(\n          (x, y, width, height) => {\n            this.setState({\n              originalWidth: width,\n              originalHeight: height,\n              originalPageX: x,\n              originalPageY: y,\n            });\n          }\n        );\n      });\n    });\n  };\n\n  /**\n   * Handles the start of touch events and checks for taps\n   *\n   * @param e\n   * @param gestureState\n   * @returns {boolean}\n   *\n   * @private\n   */\n  _handleStartShouldSetPanResponder = (\n    e: GestureResponderEvent,\n    gestureState: PanResponderGestureState\n  ) => {\n    if (this.props.onStartShouldSetPanResponder) {\n      this.props.onStartShouldSetPanResponder(\n        e,\n        gestureState,\n        this._getZoomableViewEventObject(),\n        false\n      );\n    }\n\n    // Always set pan responder on start\n    // of gesture so we can handle tap.\n    // \"Pan threshold validation\" will be handled\n    // in `onPanResponderMove` instead of in `onMoveShouldSetPanResponder`\n    return true;\n  };\n\n  /**\n   * Calculates pinch distance\n   *\n   * @param e\n   * @param gestureState\n   * @private\n   */\n  _handlePanResponderGrant = (e: GestureResponderEvent, gestureState: PanResponderGestureState) => {\n    if (this.props.onLongPress) {\n      this.longPressTimeout = setTimeout(() => {\n        this.props.onLongPress?.(\n          e,\n          gestureState,\n          this._getZoomableViewEventObject()\n        );\n        this.longPressTimeout = null;\n      }, this.props.longPressDuration);\n    }\n\n    this.props.onPanResponderGrant?.(\n      e,\n      gestureState,\n      this._getZoomableViewEventObject()\n    );\n\n    this.panAnim.stopAnimation();\n    this.zoomAnim.stopAnimation();\n    this.gestureStarted = true;\n  };\n\n  /**\n   * Handles the end of touch events\n   *\n   * @param e\n   * @param gestureState\n   *\n   * @private\n   */\n  _handlePanResponderEnd = (e: GestureResponderEvent, gestureState: PanResponderGestureState) => {\n    if (!this.gestureType) {\n      this._resolveAndHandleTap(e);\n    }\n\n\n    this.lastGestureCenterPosition = null;\n\n    // Trigger final shift animation unless panEnabled is false or disablePanOnInitialZoom is true and we're on the initial zoom level\n    if (\n      this.props.panEnabled &&\n      !(\n        this.gestureType === 'shift' &&\n        this.props.disablePanOnInitialZoom &&\n        this.zoomLevel === this.props.initialZoom\n      )\n    ) {\n      getPanMomentumDecayAnim(this.panAnim, {\n        x: gestureState.vx / this.zoomLevel,\n        y: gestureState.vy / this.zoomLevel,\n      }).start();\n    }\n\n    if (this.longPressTimeout) {\n      clearTimeout(this.longPressTimeout);\n      this.longPressTimeout = null;\n    }\n\n    this.props.onPanResponderEnd?.(\n      e,\n      gestureState,\n      this._getZoomableViewEventObject()\n    );\n\n    if (this.gestureType === 'pinch') {\n      this.props.onZoomEnd?.(\n        e,\n        gestureState,\n        this._getZoomableViewEventObject()\n      );\n    } else if (this.gestureType === 'shift') {\n      this.props.onShiftingEnd?.(\n        e,\n        gestureState,\n        this._getZoomableViewEventObject()\n      );\n    }\n\n    this.gestureType = null;\n    this.gestureStarted = false;\n  };\n\n  /**\n   * Handles the actual movement of our pan responder\n   *\n   * @param e\n   * @param gestureState\n   *\n   * @private\n   */\n  _handlePanResponderMove = (\n    e: GestureResponderEvent,\n    gestureState: PanResponderGestureState\n  ) => {\n    if (this.props.onPanResponderMove) {\n      if (\n        this.props.onPanResponderMove(\n          e,\n          gestureState,\n          this._getZoomableViewEventObject()\n        )\n      ) {\n        return false;\n      }\n    }\n\n    // Only supports 2 touches and below,\n    // any invalid number will cause the gesture to end.\n    if (gestureState.numberActiveTouches <= 2) {\n      if (!this.gestureStarted) {\n        this._handlePanResponderGrant(e, gestureState);\n      }\n    } else {\n      if (this.gestureStarted) {\n        this._handlePanResponderEnd(e, gestureState);\n      }\n      return true;\n    }\n\n    if (gestureState.numberActiveTouches === 2) {\n      if (this.longPressTimeout) {\n        clearTimeout(this.longPressTimeout);\n        this.longPressTimeout = null;\n      }\n\n      // change some measurement states when switching gesture to ensure a smooth transition\n      if (this.gestureType !== 'pinch') {\n        this.lastGestureCenterPosition = calcGestureCenterPoint(\n          e,\n          gestureState\n        );\n        this.lastGestureTouchDistance = calcGestureTouchDistance(\n          e,\n          gestureState\n        );\n      }\n      this.gestureType = 'pinch';\n      this._handlePinching(e, gestureState);\n    } else if (gestureState.numberActiveTouches === 1) {\n      if (\n        this.longPressTimeout &&\n        (Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5)\n      ) {\n        clearTimeout(this.longPressTimeout);\n        this.longPressTimeout = null;\n      }\n      // change some measurement states when switching gesture to ensure a smooth transition\n      if (this.gestureType !== 'shift') {\n        this.lastGestureCenterPosition = calcGestureCenterPoint(\n          e,\n          gestureState\n        );\n      }\n\n      const { dx, dy } = gestureState;\n      const isShiftGesture = Math.abs(dx) > 2 || Math.abs(dy) > 2;\n      if (isShiftGesture) {\n        this.gestureType = 'shift';\n        this._handleShifting(gestureState);\n      }\n    }\n    return false;\n  };\n\n  /**\n   * Handles the pinch movement and zooming\n   *\n   * @param e\n   * @param gestureState\n   *\n   * @private\n   */\n  _handlePinching(\n    e: GestureResponderEvent,\n    gestureState: PanResponderGestureState\n  ) {\n    if (!this.props.zoomEnabled) return;\n\n    const {\n      maxZoom,\n      minZoom,\n      pinchToZoomInSensitivity,\n      pinchToZoomOutSensitivity,\n    }: any = this.props;\n\n    const distance: any = calcGestureTouchDistance(e, gestureState);\n\n    if (\n      this.props.onZoomBefore &&\n      this.props.onZoomBefore(\n        e,\n        gestureState,\n        this._getZoomableViewEventObject()\n      )\n    ) {\n      return;\n    }\n\n    // define the new zoom level and take zoom level sensitivity into consideration\n    const zoomGrowthFromLastGestureState =\n      distance / this.lastGestureTouchDistance;\n    this.lastGestureTouchDistance = distance;\n\n    const pinchToZoomSensitivity: any =\n      zoomGrowthFromLastGestureState < 1\n        ? pinchToZoomOutSensitivity\n        : pinchToZoomInSensitivity;\n\n    const deltaGrowth = zoomGrowthFromLastGestureState - 1;\n    // 0 - no resistance\n    // 10 - 90% resistance\n    const deltaGrowthAdjustedBySensitivity =\n      deltaGrowth * (1 - (pinchToZoomSensitivity * 9) / 100);\n\n    let newZoomLevel = this.zoomLevel * (1 + deltaGrowthAdjustedBySensitivity);\n\n    // make sure max and min zoom levels are respected\n    if (maxZoom !== null && newZoomLevel > maxZoom) {\n      newZoomLevel = maxZoom;\n    }\n\n    if (newZoomLevel < minZoom) {\n      newZoomLevel = minZoom;\n    }\n\n    const gestureCenterPoint = calcGestureCenterPoint(e, gestureState);\n\n    if (!gestureCenterPoint) return;\n\n    const zoomCenter = {\n      x: gestureCenterPoint.x - this.state.originalPageX,\n      y: gestureCenterPoint.y - this.state.originalPageY,\n    };\n\n    const { originalHeight, originalWidth } = this.state;\n\n    const oldOffsetX = this.offsetX;\n    const oldOffsetY = this.offsetY;\n    const oldScale = this.zoomLevel;\n    const newScale = newZoomLevel;\n\n    let offsetY = calcNewScaledOffsetForZoomCentering(\n      oldOffsetY,\n      originalHeight,\n      oldScale,\n      newScale,\n      zoomCenter.y\n    );\n    let offsetX = calcNewScaledOffsetForZoomCentering(\n      oldOffsetX,\n      originalWidth,\n      oldScale,\n      newScale,\n      zoomCenter.x\n    );\n\n    const offsetShift: any =\n      this._calcOffsetShiftSinceLastGestureState(gestureCenterPoint);\n    if (offsetShift) {\n      offsetX += offsetShift.x;\n      offsetY += offsetShift.y;\n    }\n\n    this.offsetX = offsetX;\n    this.offsetY = offsetY;\n    this.zoomLevel = newScale;\n\n    this.panAnim.setValue({ x: this.offsetX, y: this.offsetY });\n    this.zoomAnim.setValue(this.zoomLevel);\n\n    this.props.onZoomAfter?.(\n      e,\n      gestureState,\n      this._getZoomableViewEventObject()\n    );\n  }\n\n\n  /**\n   * Calculates the amount the offset should shift since the last position during panning\n   *\n   * @param {Vec2D} gestureCenterPoint\n   *\n   * @private\n   */\n  _calcOffsetShiftSinceLastGestureState(gestureCenterPoint: Vec2D) {\n    const { movementSensibility }: any = this.props;\n\n    let shift: any = null;\n\n    if (this.lastGestureCenterPosition) {\n      const dx = gestureCenterPoint.x - this.lastGestureCenterPosition.x;\n      const dy = gestureCenterPoint.y - this.lastGestureCenterPosition.y;\n\n      const shiftX = dx / this.zoomLevel / movementSensibility;\n      const shiftY = dy / this.zoomLevel / movementSensibility;\n\n      shift = {\n        x: shiftX,\n        y: shiftY,\n      };\n    }\n\n    this.lastGestureCenterPosition = gestureCenterPoint;\n\n    return shift;\n  }\n\n  /**\n   * Handles movement by tap and move\n   *\n   * @param gestureState\n   *\n   * @private\n   */\n  _handleShifting(gestureState: PanResponderGestureState) {\n    // Skips shifting if panEnabled is false or disablePanOnInitialZoom is true and we're on the initial zoom level\n    if (\n      !this.props.panEnabled ||\n      (this.props.disablePanOnInitialZoom &&\n        this.zoomLevel === this.props.initialZoom)\n    ) {\n      return;\n    }\n    const shift: any = this._calcOffsetShiftSinceLastGestureState({\n      x: gestureState.moveX,\n      y: gestureState.moveY,\n    });\n    if (!shift) return;\n\n    const offsetX = this.offsetX + shift.x;\n    const offsetY = this.offsetY + shift.y;\n\n\n    this._setNewOffsetPosition(offsetX, offsetY);\n  }\n\n  /**\n   * Set the state to offset moved\n   *\n   * @param {number} newOffsetX\n   * @param {number} newOffsetY\n   * @returns\n   */\n  async _setNewOffsetPosition(newOffsetX: number, newOffsetY: number) {\n    const { onShiftingBefore, onShiftingAfter }:any = this.props;\n\n    if (onShiftingBefore?.(null, null, this._getZoomableViewEventObject())) {\n      return;\n    }\n\n    this.offsetX = newOffsetX;\n    this.offsetY = newOffsetY;\n\n    this.panAnim.setValue({ x: this.offsetX, y: this.offsetY });\n    this.zoomAnim.setValue(this.zoomLevel);\n\n    onShiftingAfter?.(null, null, this._getZoomableViewEventObject());\n  }\n\n  /**\n   * Check whether the press event is double tap\n   * or single tap and handle the event accordingly\n   *\n   * @param e\n   *\n   * @private\n   */\n  private _resolveAndHandleTap = (e: GestureResponderEvent) => {\n    const now = Date.now();\n    if (\n      this.doubleTapFirstTapReleaseTimestamp &&\n      now - this.doubleTapFirstTapReleaseTimestamp < this.props.doubleTapDelay\n    ) {\n      this._addTouch({\n        ...this.doubleTapFirstTap,\n        id: now.toString(),\n        isSecondTap: true,\n      });\n      clearTimeout(this.singleTapTimeoutId);\n      delete this.doubleTapFirstTapReleaseTimestamp;\n      delete this.singleTapTimeoutId;\n      delete this.doubleTapFirstTap;\n      this._handleDoubleTap(e);\n    } else {\n      this.doubleTapFirstTapReleaseTimestamp = now;\n      this.doubleTapFirstTap = {\n        id: now.toString(),\n        x: e.nativeEvent.pageX - this.state.originalPageX,\n        y: e.nativeEvent.pageY - this.state.originalPageY,\n      };\n      this._addTouch(this.doubleTapFirstTap);\n\n      // persist event so e.nativeEvent is preserved after a timeout delay\n      e.persist();\n      this.singleTapTimeoutId = setTimeout(() => {\n        delete this.doubleTapFirstTapReleaseTimestamp;\n        delete this.singleTapTimeoutId;\n        this.props.onSingleTap?.(e, this._getZoomableViewEventObject());\n      }, this.props.doubleTapDelay);\n    }\n  };\n\n  private _addTouch(touch: TouchPoint) {\n    this.touches.push(touch);\n    this.setState({ touches: [...this.touches] });\n  }\n\n  private _removeTouch(touch: TouchPoint) {\n    this.touches.splice(this.touches.indexOf(touch), 1);\n    this.setState({ touches: [...this.touches] });\n  }\n\n  /**\n   * Handles the double tap event\n   *\n   * @param e\n   *\n   * @private\n   */\n  _handleDoubleTap(e: GestureResponderEvent) {\n    const { onDoubleTapBefore, onDoubleTapAfter, doubleTapZoomToCenter } =\n      this.props;\n\n    onDoubleTapBefore?.(e, this._getZoomableViewEventObject());\n\n    const nextZoomStep: any = this._getNextZoomStep();\n    const { originalPageX, originalPageY } = this.state;\n\n    // define new zoom position coordinates\n    const zoomPositionCoordinates = {\n      x: e.nativeEvent.pageX - originalPageX,\n      y: e.nativeEvent.pageY - originalPageY,\n    };\n\n    // if doubleTapZoomToCenter enabled -> always zoom to center instead\n    if (doubleTapZoomToCenter) {\n      zoomPositionCoordinates.x = 0;\n      zoomPositionCoordinates.y = 0;\n    }\n\n    this._zoomToLocation(\n      zoomPositionCoordinates.x,\n      zoomPositionCoordinates.y,\n      nextZoomStep\n    ).then(() => {\n      onDoubleTapAfter?.(\n        e,\n        this._getZoomableViewEventObject({ zoomLevel: nextZoomStep })\n      );\n    });\n  }\n\n  /**\n   * Returns the next zoom step based on current step and zoomStep property.\n   * If we are zoomed all the way in -> return to initialzoom\n   *\n   * @returns {*}\n   */\n  _getNextZoomStep() {\n    const { zoomStep, maxZoom, initialZoom }: any = this.props;\n    const { zoomLevel } = this;\n\n    if (zoomLevel.toFixed(2) === maxZoom.toFixed(2)) {\n      return initialZoom;\n    }\n\n    const nextZoomStep = zoomLevel * (1 + zoomStep);\n    if (nextZoomStep > maxZoom) {\n      return maxZoom;\n    }\n\n    return nextZoomStep;\n  }\n\n  /**\n   * Zooms to a specific location in our view\n   *\n   * @param x\n   * @param y\n   * @param newZoomLevel\n   *\n   * @private\n   */\n  async _zoomToLocation(x: number, y: number, newZoomLevel: number) {\n    if (!this.props.zoomEnabled) return;\n\n    this.props.onZoomBefore?.(null, null, this._getZoomableViewEventObject());\n\n    // == Perform Zoom Animation ==\n    // Calculates panAnim values based on changes in zoomAnim.\n    let prevScale = this.zoomLevel;\n    // Since zoomAnim is calculated in native driver,\n    //  it will jitter panAnim once in a while,\n    //  because here panAnim is being calculated in js.\n    // However the jittering should mostly occur in simulator.\n    const listenerId = this.zoomAnim.addListener(({ value: newScale }) => {\n      this.panAnim.setValue({\n        x: calcNewScaledOffsetForZoomCentering(\n          this.offsetX,\n          this.state.originalWidth,\n          prevScale,\n          newScale,\n          x\n        ),\n        y: calcNewScaledOffsetForZoomCentering(\n          this.offsetY,\n          this.state.originalHeight,\n          prevScale,\n          newScale,\n          y\n        ),\n      });\n      prevScale = newScale;\n    });\n    getZoomToAnimation(this.zoomAnim, newZoomLevel).start(() => {\n      this.zoomAnim.removeListener(listenerId);\n    });\n    // == Zoom Animation Ends ==\n\n    this.props.onZoomAfter?.(null, null, this._getZoomableViewEventObject());\n  }\n\n  /**\n   * Zooms to a specificied zoom level.\n   * Returns a promise if everything was updated and a boolean, whether it could be updated or if it exceeded the min/max zoom limits.\n   *\n   * @param {number} newZoomLevel\n   *\n   * @return {Promise<bool>}\n   */\n  async zoomTo(newZoomLevel: number): Promise<boolean> {\n    if (\n      // if we would go out of our min/max limits -> abort\n      newZoomLevel > this.props.maxZoom ||\n      newZoomLevel < this.props.minZoom\n    )\n      return false;\n\n    await this._zoomToLocation(0, 0, newZoomLevel);\n    return true;\n  }\n\n  /**\n   * Zooms in or out by a specified change level\n   * Use a positive number for `zoomLevelChange` to zoom in\n   * Use a negative number for `zoomLevelChange` to zoom out\n   *\n   * Returns a promise if everything was updated and a boolean, whether it could be updated or if it exceeded the min/max zoom limits.\n   *\n   * @param {number | null} zoomLevelChange\n   *\n   * @return {Promise<bool>}\n   */\n  zoomBy(zoomLevelChange: number | null | undefined = null): Promise<boolean> {\n    // if no zoom level Change given -> just use zoom step\n    if (!zoomLevelChange) {\n      zoomLevelChange = this.props.zoomStep;\n    }\n\n    return this.zoomTo(this.zoomLevel + zoomLevelChange);\n  }\n\n  /**\n   * Moves the zoomed view to a specified position\n   * Returns a promise when finished\n   *\n   * @param {number} newOffsetX the new position we want to move it to (x-axis)\n   * @param {number} newOffsetY the new position we want to move it to (y-axis)\n   *\n   * @return {Promise<bool>}\n   */\n  moveTo(newOffsetX: number, newOffsetY: number): Promise<void> {\n    const { originalWidth, originalHeight } = this.state;\n\n    const offsetX = (newOffsetX - originalWidth / 2) / this.zoomLevel;\n    const offsetY = (newOffsetY - originalHeight / 2) / this.zoomLevel;\n\n    return this._setNewOffsetPosition(-offsetX, -offsetY);\n  }\n\n  /**\n   * Moves the zoomed view by a certain amount.\n   *\n   * Returns a promise when finished\n   *\n   * @param {number} offsetChangeX the amount we want to move the offset by (x-axis)\n   * @param {number} offsetChangeY the amount we want to move the offset by (y-axis)\n   *\n   * @return {Promise<bool>}\n   */\n  moveBy(offsetChangeX: number, offsetChangeY: number): Promise<void> {\n    const offsetX =\n      (this.offsetX * this.zoomLevel - offsetChangeX) / this.zoomLevel;\n    const offsetY =\n      (this.offsetY * this.zoomLevel - offsetChangeY) / this.zoomLevel;\n\n    return this._setNewOffsetPosition(offsetX, offsetY);\n  }\n\n  render() {\n    return (\n      <View\n        style={styles.container}\n        {...this.gestureHandlers.panHandlers}\n        ref={this.zoomSubjectWrapperRef}\n        onLayout={this.grabZoomSubjectOriginalMeasurements}\n      >\n        {\n          <Animated.View\n            style={[\n              styles.zoomSubject,\n              this.props.style,\n              {\n                transform: [\n                  { scale: this.zoomAnim },\n                  ...this.panAnim.getTranslateTransform(),\n                ],\n              },\n            ]}\n          >\n            {this.props.children}\n          </Animated.View>\n        }\n      </View>\n    );\n  }\n}\n\nconst styles = StyleSheet.create({\n  zoomSubject: {\n    flex: 1,\n    width: '100%',\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  container: {\n    flex: 1,\n    justifyContent: 'center',\n    alignItems: 'center',\n    position: 'relative',\n    overflow: 'hidden',\n  },\n});\n\nexport default ReactNativeZoomableView;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/ImageZoom/ReactNativeZoomableViewWithGestures.tsx",
    "content": "import {\n  ReactNativeZoomableViewState,\n  ReactNativeZoomableViewWithGesturesProps,\n  ZoomableViewEvent,\n} from \"./types\";\nimport React from \"react\";\nimport ReactNativeZoomableView from \"./ReactNativeZoomableView\";\nimport { GestureResponderEvent, PanResponderGestureState } from \"react-native\";\n\nexport const swipeDirections = {\n  SWIPE_UP: \"SWIPE_UP\",\n  SWIPE_DOWN: \"SWIPE_DOWN\",\n  SWIPE_LEFT: \"SWIPE_LEFT\",\n  SWIPE_RIGHT: \"SWIPE_RIGHT\",\n};\n\nclass ReactNativeZoomableViewWithGestures extends React.Component<\n  ReactNativeZoomableViewWithGesturesProps,\n  ReactNativeZoomableViewState\n> {\n  zoomableViewRef: React.RefObject<ReactNativeZoomableView | null>;\n\n  constructor(props: ReactNativeZoomableViewWithGesturesProps) {\n    super(props);\n    this.zoomableViewRef = React.createRef<ReactNativeZoomableView>();\n  }\n\n  _onShiftingEnd = (\n    e: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewState: ZoomableViewEvent\n  ) => {\n    if (this.props.onShiftingEnd) {\n      this.props.onShiftingEnd(e, gestureState, zoomableViewState);\n    }\n\n    if (!this._couldCallSwipeEvent(zoomableViewState)) {\n      return;\n    }\n\n    const swipeDirection = this._getSwipeDirection(gestureState);\n    this._triggerSwipeHandlers(swipeDirection, gestureState);\n  };\n\n  /**\n   * Checks if current config options make it possible to process a swipe or if is not necessary\n   *\n   * @returns {*}\n   * @private\n   */\n  _couldCallSwipeEvent(zoomableViewState: { zoomLevel: number }) {\n    const {\n      onSwipe,\n      onSwipeUp,\n      onSwipeDown,\n      onSwipeLeft,\n      onSwipeRight,\n      swipeMaxZoom,\n      swipeMinZoom,\n    } = this.props;\n\n    if (swipeMaxZoom && zoomableViewState.zoomLevel > swipeMaxZoom) {\n      return false;\n    }\n\n    if (swipeMinZoom && zoomableViewState.zoomLevel < swipeMinZoom) {\n      return false;\n    }\n\n    return onSwipe || onSwipeUp || onSwipeDown || onSwipeLeft || onSwipeRight;\n  }\n\n  /**\n   * Checks the swipe and validates whether we should process it or not\n   *\n   * @param gestureState\n   * @returns {*|boolean}\n   *\n   * @private\n   */\n  _validateSwipe(gestureState: any) {\n    const { onSwipeUp, onSwipeDown, onSwipeLeft, onSwipeRight } = this.props;\n    const swipeDirection = this._getSwipeDirection(gestureState);\n    const { SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN } = swipeDirections;\n\n    return (\n      (onSwipeUp && swipeDirection === SWIPE_UP) ||\n      (onSwipeDown && swipeDirection === SWIPE_DOWN) ||\n      (onSwipeLeft && swipeDirection === SWIPE_LEFT) ||\n      (onSwipeRight && swipeDirection === SWIPE_RIGHT)\n    );\n  }\n\n  /**\n   * Triggers the correct directional callback\n   *\n   * @param swipeDirection\n   * @param gestureState\n   * @private\n   */\n  _triggerSwipeHandlers(swipeDirection: any, gestureState: PanResponderGestureState) {\n    const { onSwipe, onSwipeUp, onSwipeDown, onSwipeLeft, onSwipeRight } = this.props;\n    const { SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN } = swipeDirections;\n\n    // trigger the general onswipe callback\n    if (onSwipe) {\n      onSwipe(swipeDirection, gestureState);\n    }\n\n    // trigger the direction specific swipe callback\n    switch (swipeDirection) {\n      case SWIPE_LEFT:\n        onSwipeLeft && onSwipeLeft(gestureState);\n        break;\n      case SWIPE_RIGHT:\n        onSwipeRight && onSwipeRight(gestureState);\n        break;\n      case SWIPE_UP:\n        onSwipeUp && onSwipeUp(gestureState);\n        break;\n      case SWIPE_DOWN:\n        onSwipeDown && onSwipeDown(gestureState);\n        break;\n    }\n  }\n\n  /**\n   * Calculates what direction the user swiped\n   *\n   * @param gestureState\n   * @returns {*}\n   * @private\n   */\n  _getSwipeDirection(gestureState: any) {\n    const { swipeLengthThreshold } = this.props;\n    const { SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN } = swipeDirections;\n    const { dx, dy } = gestureState;\n\n    if (!swipeLengthThreshold) {\n      return null;\n    }\n\n    if (this._isValidHorizontalSwipe(gestureState)) {\n      if (Math.abs(dx) > swipeLengthThreshold) {\n        return dx > 0 ? SWIPE_RIGHT : SWIPE_LEFT;\n      }\n    } else if (this._isValidVerticalSwipe(gestureState)) {\n      if (Math.abs(dy) > swipeLengthThreshold) {\n        return dy > 0 ? SWIPE_DOWN : SWIPE_UP;\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * Checks, whether the swipe was done in a horizontal fashion, respecting swipeVelocityThreshold limits\n   *\n   * @param gestureState\n   * @returns {*}\n   *\n   * @private\n   */\n  _isValidHorizontalSwipe(gestureState: { vx: any; dy: any }) {\n    const { vx, dy } = gestureState;\n    const { swipeVelocityThreshold, swipeDirectionalThreshold } = this.props;\n    return this._isValidSwipe(vx, swipeVelocityThreshold, dy, swipeDirectionalThreshold);\n  }\n\n  /**\n   * Checks, whether the swipe was done in a vertical fashion, respecting swipeVelocityThreshold limits\n   *\n   * @param gestureState\n   * @returns {*}\n   *\n   * @private\n   */\n  _isValidVerticalSwipe(gestureState: { vy: any; dx: any }) {\n    const { vy, dx } = gestureState;\n    const { swipeVelocityThreshold, swipeDirectionalThreshold } = this.props;\n    return this._isValidSwipe(vy, swipeVelocityThreshold, dx, swipeDirectionalThreshold);\n  }\n\n  /**\n   * Checks the sipw against velocity and directional offset to make sure it only gets activated, when we actually need it\n   *\n   * @param velocity\n   * @param swipeVelocityThreshold\n   * @param directionalOffset\n   * @param swipeDirectionalThreshold\n   *\n   * @returns {boolean}\n   *\n   * @private\n   */\n  _isValidSwipe(\n    velocity: number,\n    swipeVelocityThreshold: any,\n    directionalOffset: number,\n    swipeDirectionalThreshold: any\n  ) {\n    return (\n      Math.abs(velocity) > swipeVelocityThreshold &&\n      Math.abs(directionalOffset) < swipeDirectionalThreshold\n    );\n  }\n\n  render() {\n    return (\n      <ReactNativeZoomableView\n        {...this.props}\n        ref={(ref) => {\n          this.zoomableViewRef.current = ref;\n        }}\n        onShiftingEnd={this._onShiftingEnd}\n      />\n    );\n  }\n}\nexport default ReactNativeZoomableViewWithGestures;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/ImageZoom/helper.ts",
    "content": "import { Animated, Easing, GestureResponderEvent, PanResponderGestureState } from \"react-native\";\nimport { Vec2D } from \"./types\";\n\nexport function calcGestureTouchDistance(\n  e: GestureResponderEvent,\n  gestureState: PanResponderGestureState\n): number | null {\n  const touches = e?.nativeEvent?.touches;\n  if (gestureState.numberActiveTouches !== 2 || !touches[0] || !touches[1]) return null;\n\n  const dx = Math.abs(touches[0].pageX - touches[1].pageX);\n  const dy = Math.abs(touches[0].pageY - touches[1].pageY);\n  return Math.sqrt(dx * dx + dy * dy);\n}\n\nexport function calcNewScaledOffsetForZoomCentering(\n  oldOffsetXOrYScaled: number,\n  zoomSubjectOriginalWidthOrHeight: number,\n  oldScale: number,\n  newScale: number,\n  zoomCenterXOrY: number\n) {\n  const oldOffSetUnscaled = oldOffsetXOrYScaled * oldScale;\n  const growthRate = newScale / oldScale;\n\n  // these act like namespaces just for the sake of readability\n  const zoomSubjectOriginalCenter = {} as Center;\n  const zoomSubjectCurrentCenter = {} as Center;\n  const zoomSubjectNewCenter = {} as Center;\n\n  zoomSubjectOriginalCenter.xOrY = zoomSubjectOriginalWidthOrHeight / 2;\n  zoomSubjectCurrentCenter.xOrY = zoomSubjectOriginalCenter.xOrY + oldOffSetUnscaled;\n  zoomSubjectCurrentCenter.distanceToZoomCenter = zoomSubjectCurrentCenter.xOrY - zoomCenterXOrY;\n\n  zoomSubjectNewCenter.distanceToZoomCenter =\n    zoomSubjectCurrentCenter.distanceToZoomCenter * growthRate;\n  zoomSubjectNewCenter.xOrY = zoomSubjectNewCenter.distanceToZoomCenter + zoomCenterXOrY;\n\n  const newOffsetUnscaled = zoomSubjectNewCenter.xOrY - zoomSubjectOriginalCenter.xOrY;\n\n  return newOffsetUnscaled / newScale;\n}\n\ninterface Center {\n  xOrY: number;\n  distanceToZoomCenter: number;\n}\nexport function applyPanBoundariesToOffset(\n  offsetScaled: number,\n  containerSize: number,\n  contentSize: number,\n  scale: number,\n  boundaryPadding: number\n) {\n  const contentSizeUnscaled = contentSize * scale;\n  const offsetUnscaled = offsetScaled * scale;\n\n  const contentStartBorderUnscaled = containerSize / 2 + offsetUnscaled - contentSizeUnscaled / 2;\n  const contentEndBorderUnscaled = contentStartBorderUnscaled + contentSizeUnscaled;\n\n  const containerStartBorder = 0;\n  const containerEndBorder = containerStartBorder + containerSize;\n\n  // do not let boundary padding be greater than the container size or less than 0\n  if (!boundaryPadding || boundaryPadding < 0) boundaryPadding = 0;\n  if (boundaryPadding > containerSize) boundaryPadding = containerSize;\n\n  // Calculate container's measurements with boundary padding applied.\n  // this should shrink the container's size by the amount of the boundary padding,\n  // so that the content inside can be panned a bit further away from the original container's boundaries.\n  const paddedContainerSize = containerSize - boundaryPadding * 2;\n  const paddedContainerStartBorder = containerStartBorder + boundaryPadding;\n  const paddedContainerEndBorder = containerEndBorder - boundaryPadding;\n\n  // if content is smaller than the padded container,\n  // don't let the content move\n  if (contentSizeUnscaled < paddedContainerSize) {\n    return 0;\n  }\n\n  // if content is larger than the padded container,\n  // don't let the padded container go outside of content\n\n  // maximum distance the content's center can move from its original position.\n  // assuming the content original center is the container's center.\n  const contentMaxOffsetScaled = (paddedContainerSize / 2 - contentSizeUnscaled / 2) / scale;\n\n  if (\n    // content reaching the end boundary\n    contentEndBorderUnscaled < paddedContainerEndBorder\n  ) {\n    return contentMaxOffsetScaled;\n  }\n  if (\n    // content reaching the start boundary\n    contentStartBorderUnscaled > paddedContainerStartBorder\n  ) {\n    return -contentMaxOffsetScaled;\n  }\n\n  return offsetScaled;\n}\nexport function getBoundaryCrossedAnim(animValue: Animated.Value, toValue: number) {\n  return Animated.spring(animValue, {\n    overshootClamping: true,\n    toValue,\n    useNativeDriver: true,\n  });\n}\n\nexport function getPanMomentumDecayAnim(\n  animValue: Animated.Value | Animated.ValueXY,\n  velocity: number | Vec2D\n) {\n  return Animated.decay(animValue, {\n    velocity,\n    deceleration: 0.994,\n    useNativeDriver: true,\n  });\n}\n\nexport function getZoomToAnimation(animValue: Animated.Value, toValue: number) {\n  return Animated.timing(animValue, {\n    easing: Easing.out(Easing.ease),\n    toValue,\n    useNativeDriver: true,\n  });\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/ImageZoom/index.tsx",
    "content": "import ReactNativeZoomableView from \"./ReactNativeZoomableView\";\nimport ReactNativeZoomableViewWithGestures from \"./ReactNativeZoomableViewWithGestures\";\nimport type { ReactNativeZoomableViewProps, ZoomableViewEvent } from \"./types\";\n\nexport {\n  ReactNativeZoomableView,\n  ReactNativeZoomableViewProps,\n  ReactNativeZoomableViewWithGestures,\n  ZoomableViewEvent,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/ImageZoom/types/index.ts",
    "content": "import { Animated, GestureResponderEvent, PanResponderGestureState, ViewProps } from \"react-native\";\n\nexport enum SwipeDirection {\n  SWIPE_UP = \"SWIPE_UP\",\n  SWIPE_DOWN = \"SWIPE_DOWN\",\n  SWIPE_LEFT = \"SWIPE_LEFT\",\n  SWIPE_RIGHT = \"SWIPE_RIGHT\",\n}\n\nexport interface ZoomableViewEvent {\n  zoomLevel: number;\n  offsetX: number;\n  offsetY: number;\n  originalHeight: number;\n  originalWidth: number;\n  originalPageX: number;\n  originalPageY: number;\n}\n\nexport interface ReactNativeZoomableViewProps extends ViewProps {\n  // options\n  zoomEnabled?: boolean;\n  panEnabled?: boolean;\n  initialZoom?: number;\n  initialOffsetX?: number;\n  initialOffsetY?: number;\n  contentWidth?: number;\n  contentHeight?: number;\n  panBoundaryPadding?: number | any;\n  maxZoom?: number | any;\n  minZoom?: number | any;\n  doubleTapDelay?: number | any;\n  doubleTapZoomToCenter?: boolean;\n  bindToBorders?: boolean;\n  zoomStep?: number;\n  pinchToZoomInSensitivity?: number;\n  pinchToZoomOutSensitivity?: number;\n  movementSensibility?: number;\n  longPressDuration?: number;\n  visualTouchFeedbackEnabled?: boolean;\n  disablePanOnInitialZoom?: boolean;\n  style?: any;\n\n  // Zoom animated value ref\n  zoomAnimatedValue?: Animated.Value;\n  panAnimatedValueXY?: Animated.ValueXY;\n\n  // debug\n  debug?: boolean;\n\n  // callbacks\n  onTransform?: (zoomableViewEventObject: ZoomableViewEvent) => void;\n  onSingleTap?: (\n    event: GestureResponderEvent,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onDoubleTapBefore?: (\n    event: GestureResponderEvent,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onDoubleTapAfter?: (\n    event: GestureResponderEvent,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onShiftingBefore?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => boolean;\n  onShiftingAfter?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => boolean;\n  onShiftingEnd?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onZoomBefore?: (\n    event: GestureResponderEvent | any,\n    gestureState: PanResponderGestureState | any,\n    zoomableViewEventObject: ZoomableViewEvent | any\n  ) => boolean | void;\n  onZoomAfter?: (\n    event: GestureResponderEvent | any,\n    gestureState: PanResponderGestureState | any,\n    zoomableViewEventObject: ZoomableViewEvent | any\n  ) => void;\n  onZoomEnd?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onLongPress?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onStartShouldSetPanResponder?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent,\n    baseComponentResult: boolean\n  ) => boolean;\n  onPanResponderGrant?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onPanResponderEnd?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onPanResponderMove?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => boolean;\n  onPanResponderTerminate?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => void;\n  onPanResponderTerminationRequest?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => boolean;\n  onShouldBlockNativeResponder?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState,\n    zoomableViewEventObject: ZoomableViewEvent\n  ) => boolean;\n  onStartShouldSetPanResponderCapture?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState\n  ) => boolean | any;\n  onMoveShouldSetPanResponderCapture?: (\n    event: GestureResponderEvent,\n    gestureState: PanResponderGestureState\n  ) => boolean | any;\n}\nexport interface Vec2D {\n  x: number;\n  y: number;\n}\n\nexport interface TouchPoint extends Vec2D {\n  id: string;\n  isSecondTap?: boolean;\n}\n\nexport interface ReactNativeZoomableViewState {\n  touches: TouchPoint[];\n  originalWidth: number;\n  originalHeight: number;\n  originalPageX: number;\n  originalPageY: number;\n  debugPoints: undefined | Vec2D[];\n}\n\nexport interface ReactNativeZoomableViewWithGesturesProps extends ReactNativeZoomableViewProps {\n  swipeLengthThreshold?: number;\n  swipeVelocityThreshold?: number;\n  swipeDirectionalThreshold?: number;\n  swipeMinZoom?: number;\n  swipeMaxZoom?: number;\n  swipeDisabled?: boolean;\n  onSwipe?: (swipeDirection: SwipeDirection, gestureState: PanResponderGestureState) => void;\n  onSwipeUp?: (gestureState: PanResponderGestureState) => void;\n  onSwipeDown?: (gestureState: PanResponderGestureState) => void;\n  onSwipeLeft?: (gestureState: PanResponderGestureState) => void;\n  onSwipeRight?: (gestureState: PanResponderGestureState) => void;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/VideoPlayer/DRMType.js",
    "content": "export default {\n    WIDEVINE: 'widevine',\n    PLAYREADY: 'playready',\n    CLEARKEY: 'clearkey',\n    FAIRPLAY: 'fairplay'\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/VideoPlayer/FilterType.js",
    "content": "export default {\n    NONE: '',\n    INVERT: 'CIColorInvert',\n    MONOCHROME: 'CIColorMonochrome',\n    POSTERIZE: 'CIColorPosterize',\n    FALSE: 'CIFalseColor',\n    MAXIMUMCOMPONENT: 'CIMaximumComponent',\n    MINIMUMCOMPONENT: 'CIMinimumComponent',\n    CHROME: 'CIPhotoEffectChrome',\n    FADE: 'CIPhotoEffectFade',\n    INSTANT: 'CIPhotoEffectInstant',\n    MONO: 'CIPhotoEffectMono',\n    NOIR: 'CIPhotoEffectNoir',\n    PROCESS: 'CIPhotoEffectProcess',\n    TONAL: 'CIPhotoEffectTonal',\n    TRANSFER: 'CIPhotoEffectTransfer',\n    SEPIA: 'CISepiaTone',\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/VideoPlayer/TextTrackType.js",
    "content": "export default {\n  SRT: 'application/x-subrip',\n  TTML: 'application/ttml+xml',\n  VTT: 'text/vtt',\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/libs/VideoPlayer/VideoResizeMode.js",
    "content": "export default {\n  contain: 'contain',\n  cover: 'cover',\n  stretch: 'stretch',\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/modals/CometChatAIAssistantTools.ts",
    "content": "/**\n * Type definition for toolkit action functions\n * Each function can return any value or a Promise for async operations\n */\ntype CometChatAIAssistantToolsFunction = (args: any) => void;\n\n/**\n * Interface for the toolkit actions map\n * Key: string (function name)\n * Value: CometChatAIAssistantToolsFunction\n */\ninterface ICometChatAIAssistantToolsMap {\n  [functionName: string]: CometChatAIAssistantToolsFunction;\n}\n\n/**\n * CometChatAIAssistantTools class for managing action functions\n * \n * Usage:\n * ```typescript\n * const toolkit = new CometChatAIAssistantTools({\n *   // Weather related functions\n *   getCurrentWeather: (params: {location: string}) => {\n *     // Implementation for getting current weather\n *     fetch(`/api/weather?location=${params.location}`);\n *   },\n * });\n * \n * ```\n */\nclass CometChatAIAssistantTools {\n  private actionsMap: ICometChatAIAssistantToolsMap;\n\n  [functionName: string]: any;\n  constructor(actions: ICometChatAIAssistantToolsMap) {\n    this.actionsMap = actions;\n\n    // Make functions directly accessible as properties\n    Object.keys(actions).forEach(functionName => {\n      this[functionName] = actions[functionName];\n    });\n  }\n\n  /**\n   * Get the implementation of a specific action\n   * \n   * @param functionName - Name of the function\n   * @returns The function implementation or undefined if not found\n   */\n  getAction(functionName: string): CometChatAIAssistantToolsFunction | undefined {\n    return this.actionsMap[functionName];\n  }\n\n\n  /**\n   * Get a copy of all actions\n   * \n   * @returns A copy of the actions map\n   */\n  getActions(): ICometChatAIAssistantToolsMap {\n    return { ...this.actionsMap };\n  }\n}\n\nexport { CometChatAIAssistantTools, type ICometChatAIAssistantToolsMap, type CometChatAIAssistantToolsFunction };"
  },
  {
    "path": "packages/ChatUiKit/src/shared/modals/CometChatMessageOption.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { ImageSourcePropType, ImageStyle, TextStyle, ViewStyle } from \"react-native\";\nimport { ActionItemInterface } from \"../views\";\nimport { JSX } from \"react\";\n\nexport type CometChatMessageOption = ActionItemInterface & {\n  id: string;\n  title: string;\n  icon?: JSX.Element | ImageSourcePropType;\n  CustomView?: (message: CometChat.BaseMessage) => JSX.Element;\n  onPress?: (message: CometChat.BaseMessage) => void;\n  style?: Partial<{\n    containerStyle: ViewStyle;\n    iconStyle: ImageStyle;\n    iconContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n  }>;\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/modals/CometChatMessageTemplate.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { MessageBubbleAlignmentType } from \"../base/Types\";\nimport { CometChatMessageOption } from \"./CometChatMessageOption\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { JSX } from \"react\";\n\n/**\n * Represents the interface for a message template.\n */\ninterface MessageTemplateInterface {\n  /**\n   * The category of the message template.\n   */\n  category: string;\n\n  /**\n   * The type of the message template.\n   */\n  type: (typeof CometChat.MESSAGE_TYPE)[keyof typeof CometChat.MESSAGE_TYPE];\n\n  /**\n   * The content view of the message template.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the content view.\n   */\n  ContentView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  /**\n   * The bubble view of the message template.\n   * @param messageObject - The message object.\n   * @returns The JSX element representing the bubble view.\n   */\n  BubbleView?: (messageObject: CometChat.BaseMessage) => JSX.Element | null;\n\n  /**\n   * The bottom view of the message template.\n   * @param messageObject - The message object.\n   * @returns The JSX element representing the bottom view.\n   */\n  BottomView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  /**\n   * The header view of the message template.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the header view.\n   */\n  HeaderView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  LeadingView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  /**\n   * The status info view of the message template for DateTime and Receipt.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the status info view.\n   */\n  StatusInfoView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  /**\n   * The footer view of the message template.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the footer view.\n   */\n  FooterView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  /**\n   * The reply view of the message template for displaying quoted messages.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the reply view.\n   */\n  ReplyView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  /**\n   * The options of the message template.\n   * @param loggedInUser - The logged in user.\n   * @param messageObject - The message object.\n   * @param group - The group.\n   * @returns The array of CometChatMessageOption.\n   */\n  options?: (\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group,\n  ) => CometChatMessageOption[];\n}\n\n/**\n * Represents a message template for CometChat.\n */\nexport class CometChatMessageTemplate implements MessageTemplateInterface {\n  /**\n   * The category of the message template.\n   */\n  category: string;\n  /**\n   * The type of the message template.\n   */\n  type: (typeof CometChat.MESSAGE_TYPE)[keyof typeof CometChat.MESSAGE_TYPE];\n  /**\n   * The content view of the message template.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the content view.\n   */\n  ContentView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n  /**\n   * The bubble view of the message template.\n   * @param messageObject - The message object.\n   * @returns The JSX element representing the bubble view.\n   */\n  BubbleView?: (messageObject: CometChat.BaseMessage) => JSX.Element | null;\n  /**\n   * The bottom view of the message template.\n   * @param messageObject - The message object.\n   * @returns The JSX element representing the bottom view.\n   */\n  BottomView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n  /**\n   * The header view of the message template.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the header view.\n   */\n  HeaderView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n\n  LeadingView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n  /**\n   * The status info view of the message template for DateTime and Receipt.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the status info view.\n   */\n  StatusInfoView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n  /**\n   * The footer view of the message template.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the footer view.\n   */\n  FooterView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n  /**\n   * The reply view of the message template for displaying quoted messages.\n   * @param messageObject - The message object.\n   * @param alignment - The alignment of the message bubble.\n   * @returns The JSX element representing the reply view.\n   */\n  ReplyView?: (\n    messageObject: CometChat.BaseMessage,\n    alignment: MessageBubbleAlignmentType\n  ) => JSX.Element | null;\n  /**\n   * The options of the message template.\n   * @param loggedInUser - The logged in user.\n   * @param messageObject - The message object.\n   * @param group - The group.\n   * @returns The array of CometChatMessageOption.\n   */\n  options?: (\n    loggedInUser: CometChat.User,\n    messageObject: CometChat.BaseMessage,\n    theme: CometChatTheme,\n    group?: CometChat.Group\n  ) => CometChatMessageOption[];\n\n  /**\n   * Constructs a new instance of the CometChatMessageTemplate class.\n   * @param {MessageTemplateInterface} options - The options for the message template.\n   */\n  constructor({\n    category = \"MESSAGE\",\n    type = CometChat.MESSAGE_TYPE.TEXT,\n    ContentView,\n    BottomView,\n    BubbleView,\n    HeaderView,\n    LeadingView,\n    StatusInfoView,\n    FooterView,\n    ReplyView,\n    options,\n  }: MessageTemplateInterface) {\n    this.category = category;\n    this.type = type;\n    this.ContentView = ContentView;\n    this.BottomView = BottomView;\n    this.BubbleView = BubbleView;\n    this.HeaderView = HeaderView;\n    this.LeadingView = LeadingView;\n    this.StatusInfoView = StatusInfoView;\n    this.FooterView = FooterView;\n    this.ReplyView = ReplyView;\n    this.options = options;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/modals/StreamMessage.ts",
    "content": "import { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { MessageCategoryConstants, streamMessageTypes } from '../constants/UIKitConstants';\n\nexport class StreamMessage extends CometChat.AIAssistantMessage {\n\n  constructor(receiverId: string, receiverType: string, text: string, category?: string) {\n    super(receiverId, receiverType);\n    this.setType(streamMessageTypes.run_started);\n    this.category = (category ?? MessageCategoryConstants.stream) as CometChat.MessageCategory;\n    (this as any).text = text;\n  }\n}"
  },
  {
    "path": "packages/ChatUiKit/src/shared/modals/index.ts",
    "content": "export type { CometChatMessageOption } from \"./CometChatMessageOption\";\nimport { CometChatMessageTemplate } from \"./CometChatMessageTemplate\";\nimport { CometChatAIAssistantTools } from \"./CometChatAIAssistantTools\";\nimport { StreamMessage } from \"./StreamMessage\";\n\nexport {\n  CometChatMessageTemplate,\n  CometChatAIAssistantTools,\n  StreamMessage\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/CometChatI18nProvider.tsx",
    "content": "import React, { ReactNode, useState, useEffect, useMemo, useCallback } from \"react\";\nimport { getAvailableLanguages, translate } from \"./CometChatLocalizationHelper\";\nimport { CometChatLocalizeContext } from \"./CometChatLocalizeContext\";\nimport { setGlobalLanguage } from './LocalizationManager';\nimport * as RNLocalize from 'react-native-localize';\nimport { Language } from \"./type\";\n\ninterface CometChatI18nProviderProps {\n    children: ReactNode;\n    selectedLanguage?: Language;\n    autoDetectLanguage?: boolean;\n    fallbackLanguage?: Language;\n    translations?: {\n        [languageCode: string]: {\n            [key: string]: string;\n        };\n    };\n}\n\nconst getDeviceLanguage = (): Language => {\n    try {\n        const locales = RNLocalize.getLocales();\n        if (Array.isArray(locales) && locales.length > 0) {\n            const code = locales[0].languageCode as Language;\n            console.log(locales[0], \" device language detected\");\n            return code;\n        }\n    } catch (error) {\n        console.warn('Error getting device language:', error);\n    }\n    return 'en';\n};\n\n\n\nexport const CometChatI18nProvider = ({\n    children,\n    selectedLanguage,\n    autoDetectLanguage = true,\n    fallbackLanguage = 'en',\n    translations\n}: CometChatI18nProviderProps) => {\n    const [language, setLanguage] = useState<Language>(() => {\n        if (selectedLanguage) {\n            console.log(selectedLanguage, \" selected language provided\");\n            const availableLanguages = getAvailableLanguages();\n            const customLanguages = translations ? Object.keys(translations) : [];\n            const allAvailableLanguages = [...availableLanguages, ...customLanguages];\n\n\n            if (allAvailableLanguages.includes(selectedLanguage)) {\n                return selectedLanguage;\n            } else {\n\n                console.warn(`Language '${selectedLanguage}' not found. Using fallback: ${fallbackLanguage}`);\n                return fallbackLanguage;\n            }\n        } else if (autoDetectLanguage) {\n            console.log(\"Using device language\");\n            return getDeviceLanguage();\n        }\n        return fallbackLanguage;\n    });\n\n    useEffect(() => {\n        if (selectedLanguage) {\n            console.log(selectedLanguage, \" selected language changed via props\");\n            const availableLanguages = getAvailableLanguages();\n            const customLanguages = translations ? Object.keys(translations) : [];\n            const allAvailableLanguages = [...availableLanguages, ...customLanguages];\n\n            if (allAvailableLanguages.includes(selectedLanguage)) {\n                setLanguage(selectedLanguage);\n                console.log(selectedLanguage, \" language set from props\");\n            } else {\n                console.warn(`Language '${selectedLanguage}' not found (set via prop). Using fallback: ${fallbackLanguage}`);\n                setLanguage(fallbackLanguage);\n            }\n        } else if (autoDetectLanguage) {\n            console.log(\"Auto-detecting device language\");\n            setLanguage(getDeviceLanguage());\n        }\n    }, [selectedLanguage, autoDetectLanguage, fallbackLanguage, translations]);\n\n\n    useEffect(() => {\n        setGlobalLanguage(language, translations, fallbackLanguage);\n    }, [language, translations, fallbackLanguage]);\n\n\n    const t = useCallback((key: string): string => {\n        return translate(language, key, translations, fallbackLanguage);\n    }, [language, translations, fallbackLanguage]);\n\n\n    const contextValue = useMemo(() => ({\n        language,\n        t,\n    }), [language, t]);\n\n    return (\n        <CometChatLocalizeContext.Provider value={contextValue}>\n            {children}\n        </CometChatLocalizeContext.Provider>\n    );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/CometChatLocalizationHelper.ts",
    "content": "import de from './resources/de/translation.json';\nimport en from './resources/en/translation.json';\nimport es from './resources/es/translation.json';\nimport fr from './resources/fr/translation.json';\nimport hi from './resources/hi/translation.json';\nimport hu from './resources/hu/translation.json';\nimport lt from './resources/lt/translation.json';\nimport ms from './resources/ms/translation.json';\nimport pt from './resources/pt/translation.json';\nimport ru from './resources/ru/translation.json';\nimport sv from './resources/sv/translation.json';\nimport zh from './resources/zh/translation.json';\nimport zhTw from './resources/zh-tw/translation.json'; \nimport ja from './resources/ja/translation.json';\nimport ko from './resources/ko/translation.json';\nimport tr from './resources/tr/translation.json';\nimport nl from './resources/nl/translation.json';\nimport it from './resources/it/translation.json';   \nimport { Language } from './type';\n\n\nexport type TranslationKey = keyof typeof en;\n\n\nconst defaultTranslations = {\n    de,\n    en,\n    es,\n    fr,\n    hi,\n    hu,\n    lt,\n    ms,\n    pt,\n    ru,\n    sv,\n    zh,\n    ja,\n    ko,\n    tr,\n    nl,\n    it,\n    \"zh-tw\":zhTw,\n    \"en-IN\" : en,\n    \"en-GB\" : en,\n    \"en-US\" : en,\n} as const;\n\n\nexport type SupportedLanguage = keyof typeof defaultTranslations;\n\nexport interface CustomTranslations {\n    [languageCode: string]: {\n        [key: string]: string;\n    };\n}\n\n\nexport const translate = (\n    language: Language,\n    key: string,\n    customTranslations?: CustomTranslations,\n    fallbackLanguage: Language = 'en'\n): string => {\n\n    if (customTranslations?.[language]?.[key]) {\n        return customTranslations[language][key];\n    }\n\n\n    const defaultTranslation: { [key: string]: string } = defaultTranslations[language as SupportedLanguage] as { [key: string]: string };\n    if (defaultTranslation && key in defaultTranslation) {\n        return defaultTranslation[key];\n    }\n\n\n    if (language !== fallbackLanguage && customTranslations?.[fallbackLanguage]?.[key]) {\n        return customTranslations[fallbackLanguage][key];\n    }\n\n\n    if (language !== fallbackLanguage) {\n        const fallbackTranslation: { [key: string]: string } = defaultTranslations[fallbackLanguage as SupportedLanguage] as { [key: string]: string };\n        if (fallbackTranslation && key in fallbackTranslation) {\n            return fallbackTranslation[key];\n        }\n    }\n\n\n    console.warn(`Missing translation for key: ${key} in language: ${language} and fallback: ${fallbackLanguage}`);\n    return key;\n};\n\n\nexport const getAvailableLanguages = (): SupportedLanguage[] => {\n    return Object.keys(defaultTranslations) as SupportedLanguage[];\n};\n\nexport default defaultTranslations;"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/CometChatLocalizeContext.tsx",
    "content": "import React, { createContext } from 'react';\nimport { CometChatLocalizeContextType } from './type';\nimport { translate } from './CometChatLocalizationHelper';\n\nconst fallbackTranslate = (key: string): string => {\n    console.warn(`Translation function called outside of CometChatI18nProvider for key: ${key}. Using fallback.`);\n    return translate('en', key, undefined, 'en');\n};\n\nconst defaultContextValue: CometChatLocalizeContextType = {\n    language: 'en',\n    t: fallbackTranslate, \n};\n\nexport const CometChatLocalizeContext = createContext<CometChatLocalizeContextType>(defaultContextValue);"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/LocalizationManager.ts",
    "content": "import { translate } from './CometChatLocalizationHelper';\nimport { Language } from './type';\n\n\nclass LocalizationManager {\n    private static instance: LocalizationManager;\n    private currentLanguage: Language = 'en';\n    private customTranslations?: any;\n    private fallbackLanguage: Language = 'en';\n    private isProviderActive: boolean = false;\n\n    static getInstance(): LocalizationManager {\n        if (!LocalizationManager.instance) {\n            LocalizationManager.instance = new LocalizationManager();\n        }\n        return LocalizationManager.instance;\n    }\n\n\n    setLanguage(language: Language, translations?: any, fallback?: Language) {\n        this.currentLanguage = language;\n        this.customTranslations = translations;\n        this.isProviderActive = true; \n        if (fallback) {\n            this.fallbackLanguage = fallback;\n        }\n    }\n\n    getCurrentLanguage(): Language {\n        return this.currentLanguage;\n    }\n\n\n    translate(key: string): string {\n\n        if (!this.isProviderActive) {\n            return translate('en', key, undefined, 'en');\n        }\n\n\n        return translate(this.currentLanguage, key, this.customTranslations, this.fallbackLanguage);\n    }\n\n    markProviderInactive() {\n        this.isProviderActive = false;\n    }\n}\n\n\nexport const t = (key: string): string => {\n    return LocalizationManager.getInstance().translate(key);\n};\n\n\nexport const setGlobalLanguage = (language: Language, translations?: any, fallback?: Language) => {\n    LocalizationManager.getInstance().setLanguage(language, translations, fallback);\n};\n\nexport const getCometChatTranslation = () => {\n    return t\n}\n\nexport const getCurrentLanguage = (): Language => {\n    return LocalizationManager.getInstance().getCurrentLanguage();\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/index.ts",
    "content": "import { getCometChatTranslation, getCurrentLanguage } from './LocalizationManager'\nimport { CometChatI18nProvider } from './CometChatI18nProvider';\nimport { useCometChatTranslation } from './useCometChatTranslationHook';\nimport {\n    translate,\n    getAvailableLanguages,\n    type TranslationKey,\n    type SupportedLanguage,\n    type CustomTranslations\n} from './CometChatLocalizationHelper';\nimport type { CometChatLocalizeContextType, Language } from './type';\n\nexport {\n    getCometChatTranslation,\n    getCurrentLanguage,\n    CometChatI18nProvider,\n    useCometChatTranslation,\n    translate,\n    getAvailableLanguages,\n    type TranslationKey,\n    type SupportedLanguage,\n    type CustomTranslations,\n    type CometChatLocalizeContextType,\n    type Language\n} "
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/de/translation.json",
    "content": "{\n\t\"INCOMING_CALL\": \"Eingehender Anruf\",\n\t\"OUTGOING_CALL\": \"Ausgehender Anruf\",\n\t\"CALL_REJECTED\": \"Anruf abgelehnt\",\n\t\"CALL_ANSWERED\": \"Anruf beantwortet\",\n\t\"CALL_CANCELLED\": \"Anruf storniert\",\n\t\"MISSED_CALL\": \"Verpasster Anruf\",\n\t\"CALL_UNANSWERED\": \"Unbeantworteter Anruf\",\n\t\"INFO\": \"Informationen\",\n\t\"REACT\": \"Reagieren\",\n\t\"EDIT\": \"Bearbeiten\",\n\t\"MESSAGE_PRIVATELY\": \"Private Nachricht\",\n\t\"TRANSLATE\": \"Übersetzen\",\n\t\"USERS\": \"Nutzer\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Alle\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Alle in dieser Gruppe benachrichtigen\",\n\t\"CONVERSATION_DELETED\":\"Konversation gelöscht\",\n\t\"MESSAGE_COPIED\": \"Nachricht wurde in die Zwischenablage kopiert.\",\n\t\"MESSAGE_EDITED\": \"Nachricht wurde erfolgreich aktualisiert.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Nachricht wurde erfolgreich gelöscht.\",\n\t\"MESSAGE_TRANSLATED\": \"Nachricht wurde erfolgreich übersetzt.\",\n\t\"CHATS\": \"Chats\",\n\t\"LAST_SEEN\":\"Zuletzt gesehen\",\n\t\"AT\":\"beim\",\n\t\"SEARCH_EMOJI\": \"Suche Emoji\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Möchten Sie diese Konversation löschen? Diese Konversation wird von all Ihren Geräten gelöscht.\",\n\t\"GROUPS\": \"Gruppen\",\n\t\"WRONG_FILE_TYPE\":\"Sie haben einen anderen Dateityp ausgewählt. Bitte wählen Sie die entsprechende Datei aus.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Dieser Dateityp ist nicht erlaubt.\",\n\t\"SHARED\": \"Geteilt\",\n\t\"SOUND_MANAGER\": \"Soundmanager\",\n\t\"THEME\": \"Thema\",\n\t\"DELETE_MSG_TEXT\": \"Diese Nachricht wurde gelöscht\",\n\t\"LOCALIZE\": \"Lokalisieren\",\n\t\"CONVERSATION_LIST_ITEM\": \"Element der Konversationsliste\",\n\t\"DATA_ITEM\": \"Datenelement\",\n\t\"STATUS_INDICATOR\": \"Statusanzeige\",\n\t\"BADGE_COUNT\": \"Anzahl der Abzeichen\",\n\t\"MESSAGE_RECEIPT\": \"Empfang der Nachricht\",\n\t\"MESSAGE\": \"Nachricht\",\n\t\"RECEIPT_INFORMATION\": \"Informationen zur Quittung\",\n\t\"NO_RECIPIENT\": \"Kein Empfänger\",\n\t\"NO_RECIPIENTS\": \"Keine Empfänger\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Konversationen mit Nachrichten\",\n\t\"CONVERSATIONS\": \"Konversationen\",\n\t\"CONVERSATION_LIST\": \"Liste der Konversationen\",\n\t\"MESSAGES\": \"Nachrichten\",\n\t\"MESSAGE_HEADER\": \"Nachrichten-Header\",\n\t\"MESSAGE_LIST\": \"Liste der Nachrichten\",\n\t\"MESSAGE_COMPOSER\": \"NachrichtenverFASSER\",\n\t\"USERS_WITH_MESSAGES\": \"Benutzer mit Nachrichten\",\n\t\"USER_LIST\": \"Liste der Benutzer\",\n\t\"GROUP_LIST\": \"Liste der Gruppen\",\n\t\"GROUPS_WITH_MESSAGES\": \"Gruppen mit Nachrichten\",\n\t\"NEW__GROUP\": \"Neue Gruppe\",\n\t\"PASSWORD\": \"Passwort\",\n\t\"CONTINUE\": \"Weiter\",\n\t\"NO_CHATS_SELECTED\": \"Keine Chats ausgewählt\",\n\t\"NO_USERS_SELECTED\": \"Keine Benutzer ausgewählt\",\n\t\"NO_GROUPS_SELECTED\": \"Keine Gruppen ausgewählt\",\n\t\"SELECT_DAY\": \"Wählen Sie einen Tag\",\n\t\"SELECT_TIME\": \"Wählen Sie eine Uhrzeit\",\n\t\"NO_CALLS_SELECTED\": \"Keine Anrufe ausgewählt\",\n\t\"SELECT__GROUP\": \"Wählen Sie eine Gruppe aus, um mit dem Messaging zu beginnen\",\n\t\"SELECT__USER\": \"Wählen Sie einen Benutzer aus, um mit dem Messaging zu beginnen\",\n\t\"GROUP_PASSWORD_BLANK\": \"Das Gruppenpasswort darf nicht leer sein\",\n\t\"GROUP_NAME_BLANK\": \"Der Gruppenname darf nicht leer sein\",\n\t\"GROUP_TYPE_BLANK\": \"Der Gruppentyp darf nicht leer sein\",\n\t\"DELETE_CONVERSATION\": \"Konversation löschen?\",\n\t\"ADD_TO_CHAT\": \"Zum Chat hinzufügen\",\n\t\"MORE\": \"Mehr\",\n\t\"COPY\": \"Kopieren\",\n\t\"VOICE_RECORDING\": \"Sprachaufzeichnung\",\n\t\"MESSAGE_IMAGE\": \"Bild\",\n\t\"MESSAGE_FILE\": \"Datei\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"CUSTOM_MESSAGE\": \"Du hast eine Nachricht\",\n\t\"SELECT_A_DATE\": \"Wählen Sie ein Datum\",\n\t\"TIME_ZONE\": \"Zeitzone\",\n\t\"MEETING_SCHEDULED\": \"Ihr Meeting wurde geplant.\",\n\t\"SOMETHING_WRONG\": \"Etwas ist schief gelaufen. Bitte versuchen Sie es erneut.\",\n\t\"MEETING_SLOT_BOOK\": \"Das Zeitfenster ist nicht mehr verfügbar. Bitte wählen Sie ein anderes.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Neuen Slot buchen\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Für dieses Datum ist kein Zeitfenster verfügbar. Bitte versuchen Sie es mit einem anderen Datum.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Bitte versuchen Sie es mit einem anderen Datum.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Keine Zeitfenster verfügbar\",\n\t\"SCHEDULE\": \"Zeitplan\",\n\t\"MORE_TIMES\": \"Mehrmals\",\n\t\"MISSED_VOICE_CALL\": \"Verpasster Sprachanruf\",\n\t\"MEET_WITH\": \"Treffen Sie sich mit\",\n\t\"MEETING_SCHEDULER\": \"Planer für Besprechungen\",\n\t\"MISSED_VIDEO_CALL\": \"Videoanruf verpasst\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Umfrage\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Aufkleber\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Dokument\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Whiteboard\",\n\t\"ONLINE\": \"Online\",\n\t\"ADMINISTRATOR\": \"Administrator\",\n\t\"ADMIN\": \"Admin\",\n\t\"MODERATOR\": \"Moderator\",\n\t\"PARTICIPANT\": \"Teilnehmer\",\n\t\"PUBLIC\": \"Öffentlich\",\n\t\"PRIVATE\": \"Privat\",\n\t\"PASSWORD_PROTECTED\": \"Passwortgeschützt\",\n\t\"PRIVACY_AND_SECURITY\": \"Datenschutz und Sicherheit\",\n\t\"PREFERENCES\": \"Präferenzen\",\n\t\"MEMBERS\": \"Mitglieder\",\n\t\"MEMBER\": \"Mitglied\",\n\t\"EDITED\": \"Bearbeitet\",\n\t\"TODAY\": \"Heute\",\n\t\"YESTERDAY\": \"Gestern\",\n\t\"SUNDAY\": \"Sonntag\",\n\t\"MONDAY\": \"Montag\",\n\t\"TUESDAY\": \"Dienstag\",\n\t\"WEDNESDAY\": \"Mittwoch\",\n\t\"THURSDAY\": \"Donnerstag\",\n\t\"FRIDAY\": \"Freitag\",\n\t\"SATURDAY\": \"Samstag\",\n\t\"TYPING\": \"Tippen...\",\n\t\"IS_TYPING\": \"tippt...\",\n\t\"CLOSE\": \"Schliessen\",\n\t\"ENTER_GROUP_NAME\": \"Geben Sie den Gruppennamen ein\",\n\t\"ADD_MEMBERS\": \"Mitglieder hinzufügen\",\n\t\"SEND_MESSAGE\": \"Nachricht senden\",\n\t\"UNBLOCK_USER\": \"Benutzer entsperren\",\n\t\"BLOCK_USER\": \"Benutzer blockieren\",\n\t\"DELETE_AND_EXIT\": \"Löschen und beenden\",\n\t\"LEAVE_GROUP\": \"Gruppe verlassen\",\n\t\"CREATE_GROUP\": \"Gruppe erstellen\",\n\t\"SHARED_MEDIA\": \"Geteilte Medien\",\n\t\"SHARED_FILE\": \"Geteilte Datei\",\n\t\"VIDEO_CALL\": \"Videoanruf\",\n\t\"AUDIO_CALL\": \"Audioanruf\",\n\t\"LOADING\": \"Wird geladen...\",\n\t\"REPLY\": \"Antwort\",\n\t\"REPLIES\": \"Antworten\",\n\t\"AI\": \"AI\",\n\t\"SMART_REPLIES\": \"Intelligente Antworten\",\n\t\"CONVERSATION_STARTER\": \"Gesprächsstarter\",\n\t\"LAUNCH\": \"Starten\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"hat ein kollaboratives Dokument geteilt\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"hat ein kollaboratives Whiteboard geteilt\",\n\t\"CREATED_WHITEBOARD\": \"Sie haben ein neues kollaboratives Whiteboard erstellt\",\n\t\"CREATED_DOCUMENT\": \"Sie haben ein neues kollaboratives Dokument erstellt\",\n\t\"PHOTOS\": \"Fotos\",\n\t\"VIDEOS\": \"Videos\",\n\t\"DOCUMENT\": \"Dokument\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Du hast diese Nachricht gelöscht\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Diese Nachricht wurde gelöscht\",\n\t\"MESSAGE_IS_DELETED\": \"Nachricht ist gelöscht\",\n\t\"VIEW_ON_YOUTUBE\": \"Auf Youtube ansehen\",\n\t\"SEARCH\": \"Suche\",\n\t\"ERROR\": \"Fehler\",\n\t\"ERROR_TEXT\": \"Sieht aus, als ob etwas schief gelaufen ist. Bitte versuchen Sie es erneut.\",\n\t\"NO_GROUPS_FOUND\": \"Keine Gruppen gefunden\",\n\t\"NO_CHATS_FOUND\": \"Keine Chats gefunden\",\n\t\"CANT__LOAD__CHATS\": \"Chats können nicht geladen werden\",\n\t\"MEDIA_MESSAGE\": \"Botschaft der Medien\",\n\t\"INCOMING_AUDIO_CALL\": \"Eingehender Audioanruf\",\n\t\"INCOMING_VIDEO_CALL\": \"Eingehender Videoanruf\",\n\t\"DECLINE\": \"Rückgang\",\n\t\"ACCEPT\": \"Akzeptieren\",\n\t\"CALL_INITIATED\": \"Anruf initiiert\",\n\t\"OUTGOING_AUDIO_CALL\": \"Ausgehender Audioanruf\",\n\t\"OUTGOING_VIDEO_CALL\": \"Ausgehender Videoanruf\",\n\t\"REJECTED_CALL\": \"abgelehnter Anruf\",\n\t\"CALL_ACCEPTED\": \"Anruf angenommen\",\n\t\"JOINED\": \"verbunden\",\n\t\"LEFT_THE_CALL\": \"hat den Anruf verlassen\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Unbeantworteter Audioanruf\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Unbeantworteter Videoanruf\",\n\t\"CALL_ENDED\": \"Anruf wurde beendet\",\n\t\"CALL_BUSY\": \"Rufe Busy an\",\n\t\"CALLING\": \"Ich rufe an...\",\n\t\"ADD\": \"Hinzufügen\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Keine gesperrten Mitglieder gefunden\",\n\t\"BANNED_MEMBERS\": \"Gesperrte Mitglieder\",\n\t\"NAME\": \"Name\",\n\t\"SCOPE\": \"Geltungsbereich\",\n\t\"UNBAN\": \"Sperre aufheben\",\n\t\"SELECT_GROUP_TYPE\": \"Wählen Sie den Gruppentyp\",\n\t\"ENTER_GROUP_PASSWORD\": \"Geben Sie das Gruppenpasswort ein\",\n\t\"CREATE\": \"Erstellen\",\n\t\"CREATE_POLL\": \"Umfrage\",\n\t\"QUESTION\": \"Frage\",\n\t\"ENTER_YOUR_QUESTION\": \"Gib deine Frage ein\",\n\t\"OPTIONS\": \"Optionen\",\n\t\"ENTER_YOUR_OPTION\": \"Geben Sie Ihre Option ein\",\n\t\"ADD_NEW_OPTION\": \"Neue Option hinzufügen\",\n\t\"VIEW_MEMBERS\": \"Mitglieder ansehen\",\n\t\"DETAILS\": \"Einzelheiten\",\n\t\"NOTIFICATIONS\": \"Benachrichtigungen\",\n\t\"OTHER\": \"Andere\",\n\t\"HELP\": \"Hilfe\",\n\t\"REPORT_PROBLEM\": \"Ein Problem melden\",\n\t\"GROUP_MEMBERS\": \"Mitglieder der Gruppe\",\n\t\"BAN\": \"Bann\",\n\t\"KICK\": \"Tritt\",\n\t\"PICK_YOUR_EMOJI\": \"Wähle dein Emoji\",\n\t\"PRIVATE_GROUP\": \"Private Gruppe\",\n\t\"PROTECTED_GROUP\": \"Geschützte Gruppe\",\n\t\"VISIT\": \"Besuch\",\n\t\"ATTACH\": \"Anhängen\",\n\t\"OPEN_WHITEBOARD\": \"Whiteboard öffnen\",\n\t\"ATTACH_FILE\": \"Datei anhängen\",\n\t\"GENERATING_REPLIES\": \"Antworten generieren\",\n\t\"ATTACH_VIDEO\": \"Video anhängen\",\n\t\"ATTACH_AUDIO\": \"Audio anhängen\",\n\t\"ATTACH_IMAGE\": \"Bild anhängen\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Mithilfe eines Dokuments zusammenarbeiten\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Arbeiten Sie mit einem Whiteboard zusammen\",\n\t\"SUGGEST_A_REPLY\": \"Schlage eine Antwort vor\",\n\t\"GENERATING_ICEBREAKERS\": \"Eisbrecher generieren\",\n\t\"EMOJI\": \"Emoji\",\n\t\"NO_REPLIES_FOUND\": \"Keine Antworten gefunden\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Tragen Sie hier Ihre Nachricht ein\",\n\t\"NO_MESSAGES_FOUND\": \"Keine Nachrichten gefunden\",\n\t\"THREAD\": \"Gewinde\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Kollaboratives Dokument\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Kollaboratives Whiteboard\",\n\t\"ADD_REACTION\": \"Reaktion hinzufügen\",\n\t\"NO_STICKERS_FOUND\": \"Keine Sticker gefunden\",\n\t\"REPLY_TO_THREAD\": \"Auf Thread antworten\",\n\t\"REPLY_IN_THREAD\": \"Im Thread antworten\",\n\t\"VIEW\": \"Ansicht\",\n\t\"DELETE_MESSAGE\": \"Nachricht löschen\",\n\t\"EDIT_MESSAGE\": \"Nachricht bearbeiten\",\n\t\"OWNER\": \"Besitzer\",\n\t\"CHANGE_SCOPE\": \"Umfang ändern\",\n\t\"STICKER\": \"Aufkleber\",\n\t\"LAST_ACTIVE_AT\": \"Zuletzt aktiv am\",\n\t\"VOICE_CALL\": \"Sprachanruf\",\n\t\"VIEW_DETAIL\": \"Detail ansehen\",\n\t\"VOTES\": \"Wahlen\",\n\t\"VOTE\": \"Abstimmung\",\n\t\"NO_VOTE\": \"Keine Abstimmung\",\n\t\"REACTED\": \"reagiert\",\n\t\"ADDED\": \"hinzugefügt\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Möchten Sie wirklich unsichere Inhalte sehen?\",\n\t\"REACT_TO_MESSAGE\": \"Auf eine Nachricht reagieren\",\n\t\"UNBANNED\": \"nicht gesperrt\",\n\t\"MADE\": \"hergestellt\",\n\t\"MISSED_AUDIO_CALL\": \"Verpasster Audioanruf\",\n\t\"ENTER_YOUR_PASSWORD\": \"Gib dein Passwort ein\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Dokument öffnen, um Inhalte gemeinsam zu bearbeiten\",\n\t\"DOCS\": \"Dokumente\",\n\t\"NO_RECORDS_FOUND\": \"Keine Datensätze gefunden\",\n\t\"LIVE_REACTION\": \"Live-Reaktion\",\n\t\"SMILEY_PEOPLE\": \"Smileys und Leute\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Whiteboard öffnen, um gemeinsam zu zeichnen\",\n\t\"ANIMALES_NATURE\": \"Tiere und Natur\",\n\t\"FOOD_DRINK\": \"Essen & Trinken\",\n\t\"OPEN_DOCUMENT\": \"Dokument öffnen\",\n\t\"ACTIVITY\": \"Aktivität\",\n\t\"TRAVEL_PLACES\": \"Reisen und Orte\",\n\t\"OBJECTS\": \"Objekte\",\n\t\"SYMBOLS\": \"Symbole\",\n\t\"FLAGS\": \"Flaggen\",\n\t\"SENT\": \"Gesendet\",\n\t\"SEEN\": \"Gesehen\",\n\t\"DELIVERED\": \"Geliefert\",\n\t\"READ\": \"Lesen\",\n\t\"MESSAGE_INFORMATION\": \"Informationen zur Nachricht\",\n\t\"TRANSLATE_MESSAGE\": \"Nachricht übersetzen\",\n\t\"TRANSLATED_MESSAGE\": \"Übersetzte Nachricht\",\n\t\"LEFT\": \"links\",\n\t\"KICKED\": \"getreten\",\n\t\"BANNED\": \"unerlaubt\",\n\t\"NEW_MESSAGES\": \"neue Nachrichten\",\n\t\"NEW_MESSAGE\": \"neue Nachricht\",\n\t\"JUMP\": \"Springen\",\n\t\"SELECT_VIDEO_SOURCE\": \"Wählen Sie die Videoquelle\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Wählen Sie die Eingangs-Audioquelle\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Wählen Sie die Ausgangs-Audioquelle\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"hat einen Audioanruf initiiert\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"hat einen Videoanruf initiiert\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Sie haben einen Audioanruf initiiert\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Sie haben einen Videoanruf initiiert\",\n\t\"IGNORE\": \"Ignorieren\",\n\t\"ON_ANOTHER_CALL\": \"ist in einem anderen Gespräch\",\n\t\"CREATING\": \"Erstellen\",\n\t\"AVATAR\": \"Avatar\",\n\t\"ONGOING_CALL\": \"Laufender Anruf\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Sie befinden sich bereits in einem laufenden Gespräch\",\n\t\"RESIZE\": \"Größe ändern\",\n\t\"READ_MORE\": \"Lesen Sie mehr\",\n\t\"SHOW_LESS\": \"Zeige weniger\",\n\t\"SETTINGS\": \"Einstellungen\",\n\t\"ACTIONS\": \"Aktionen\",\n\t\"VIEW_PROFILE\": \"Profil ansehen\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Nachricht privat senden\",\n\t\"DELETE\": \"Löschen\",\n\t\"DELETE_CONFIRM\": \"Bist du sicher, dass du löschen möchtest?\",\n\t\"DELETE_CHAT\": \"Diesen Chat löschen?\",\n\t\"SURE_TO_DELETE_CHAT\": \"Bist du sicher, dass du diesen Chat löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.\",\n\t\"CANCEL\": \"Stornieren\",\n\t\"LEAVE_CONFIRM\": \"Bist du sicher, dass du die Gruppe verlassen willst?\",\n\t\"TRANSFER_CONFIRM\": \"Du bist der Gruppenbesitzer; bitte übertrage das Eigentum an ein Mitglied, bevor du die Gruppe verlässt\",\n\t\"ADDING\": \"Wird hinzugefügt...\",\n\t\"TRANSFER\": \"Übertragung\",\n\t\"TRANSFER_OWNERSHIP\": \"Inhaberschaft übertragen\",\n\t\"TRANSFERRING\": \"Übertragung\",\n\t\"YES\": \"Ja\",\n\t\"NO\": \"Nein\",\n\t\"ANSWER\": \"Antwort\",\n\t\"ADD_ANOTHER_ANSWER\": \"Eine weitere Antwort hinzufügen\",\n\t\"SET_THE_ANSWERS\": \"STELLE DIE ANTWORTEN EIN\",\n\t\"TRY_AGAIN\": \"Versuche es noch einmal\",\n\t\"INVALID_GROUP_NAME\": \"Bitte geben Sie einen gültigen Namen für die Gruppe ein und versuchen Sie es erneut\",\n\t\"INVALID_PASSWORD\": \"Bitte geben Sie ein gültiges Passwort für die Gruppe ein und versuchen Sie es erneut\",\n\t\"INVALID_GROUP_TYPE\": \"Bitte geben Sie einen gültigen Typ für die Gruppe ein und versuchen Sie es erneut\",\n\t\"WRONG_PASSWORD\": \"Bitte geben Sie das richtige Passwort ein und versuchen Sie es erneut\",\n\t\"INVALID_POLL_QUESTION\": \"Bitte geben Sie die erforderliche Frage ein, bevor Sie eine Umfrage erstellen\",\n\t\"INVALID_POLL_OPTION\": \"Bitte geben Sie die erforderliche Antwort ein, bevor Sie eine Umfrage erstellen\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Die gewählte Sprache für die Übersetzung ähnelt der Sprache der Originalnachricht\",\n\t\"LEAVE\": \"Verlassen\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Klicken Sie hier, um das Gespräch zu starten\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Standort\",\n\t\"SHARED_LOCATION\": \"Gemeinsamer Standort\",\n\t\"IN_A_THREAD\": \"In einem Thread\",\n\t\"CALLS\": \"Anrufe\",\n\t\"CALL_DETAILS\": \"Einzelheiten zum Anruf\",\n\t\"OFFLINE\": \"Offline\",\n\t\"POLLS\": \"Umfragen\",\n\t\"YOU\": \"Du\",\n\t\"PRIVACY\": \"Datenschutz\",\n\t\"BLOCKED_USERS\": \"Gesperrte Nutzer\",\n\t\"YOU'VE_BLOCKED\": \"Du hast geblockt\",\n\t\"NO_PHOTOS\": \"Keine Fotos\",\n\t\"NO_VIDEOS\": \"Keine Videos\",\n\t\"NO_DOCUMENTS\": \"Keine Dokumente\",\n\t\"JOIN\": \"Beitreten\",\n\t\"REMOVE\": \"entfernen\",\n\t\"BLOCK\": \"Blockieren\",\n\t\"CHANGE_ROLE\": \"Rolle ändern\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Sie können Rollen ändern, um Gruppenberechtigungen und Verantwortlichkeiten zu verwalten.\",\n\t\"CHANGE_TO\": \"Wechseln zu\",\n\t\"NEW_CHAT\": \"Neuer Chat\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Vielen Dank für das Ausfüllen des Formulars.\",\n\t\"HISTORY\": \"Geschichte\",\n\t\"CANCELLED_AUDIO_CALL\": \"Abgebrochener Audioanruf\",\n\t\"CANCELLED_VIDEO_CALL\": \"Videoanruf abgesagt\",\n\t\"REJECTED_AUDIO_CALL\": \"Abgelehnter Audioanruf\",\n\t\"REJECTED_VIDEO_CALL\": \"Abgelehnter Videoanruf\",\n\t\"CANCELLED_CALL\": \"Anruf storniert\",\n\t\"UNANSWERED_CALL\": \"Unbeantworteter Anruf\",\n\t\"RECORDING\": \"Aufnahme\",\n\t\"PARTICIPANTS\": \"Teilnehmer\",\n\t\"CALL_HISTORY\": \"Anrufliste\",\n\t\"NO_CALLS_FOUND\": \"Keine Anrufe gefunden\",\n\t\"ONGOING_AUDIO_CALL\": \"Laufender Audioanruf\",\n\t\"ONGOING_VIDEO_CALL\": \"Laufender Videoanruf\",\n\t\"CALL_DETAIL\": \"Detail des Anrufs\",\n\t\"FORM\": \"Formular\",\n\t\"CARD\": \"Karte\",\n\t\"GENERATING_SUMMARY\": \"Zusammenfassung wird generiert\",\n\t\"CONVERSATION_SUMMARY\": \"Zusammenfassung der Konversation\",\n\t\"GENERATE_SUMMARY\": \"Generieren Sie eine Zusammenfassung\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Wie kann ich dir bei diesem Gespräch helfen? Bitte stell mir eine Frage und ich berate dich 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"Frag\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Fragen Sie KI-Bots\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"KI-Bot\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Sie können bis zu 10 Benutzer gleichzeitig erwähnen.\",\n\t\"ALL\": \"Alle\",\n\t\"CLICK_TO_REMOVE\": \"Zum Entfernen hier klicken\",\n\t\"OTHERS\": \"andere\",\n\t\"AND\": \"ein\",\n\t\"ASK_QUESTION\": \"Frage stellen\",\n\t\"ADD_OPTION\": \"Option hinzufügen\",\n\t\"SEND\": \"Senden\",\n\t\"FULL_SCREEN_VIEWER\": \"Vollbildanzeige\",\n\t\"TEXT_TRANSLATE\": \"Text übersetzen\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Sieht aus, als ob etwas schief gelaufen ist\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Keine Zusammenfassung verfügbar\",\n\t\"QUESTIONS\": \"Fragen\",\n\t\"NO_STICKERS_AVAILABLE\": \"Keine Sticker verfügbar\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Keine Vorschläge verfügbar\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Erstelle Gruppen oder trete ihnen bei, um sie hier aufgelistet zu sehen und mit der Zusammenarbeit zu beginnen\",\n\t\"NO_USERS_AVAILABLE\": \"Keine Benutzer verfügbar\",\n\t\"NO_GROUPS_AVAILABLE\": \"Keine Gruppen verfügbar\",\n\t\"NO_CALL_LOGS\": \"Noch keine Anrufprotokolle\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Tätigen oder empfangen Sie Anrufe, um Ihre Anrufliste hier aufgelistet zu sehen\",\n\t\"NO_CONVERSATIONS\": \"Noch keine Gespräche“;  \",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Starte einen neuen Chat oder lade andere ein, an der Unterhaltung teilzunehmen.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Kein Gruppenmitglied verfügbar\",\n\t\"OOPS\": \"HOPPLA!\",\n\t\"OOPS!\": \"HOPPLA!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Warten, bis die Empfänger die Nachricht erhalten und ansehen\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Fügen Sie Kontakte hinzu, um sie hier aufgelistet zu sehen.\",\n\t\"REACHED_MAX_LIMIT\": \"Du hast das Limit erreicht. Sie können bis zu 12 Optionen hinzufügen.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Bitte füllen Sie alle Pflichtfelder aus, bevor Sie eine Umfrage erstellen\",\n\t\"NOT_SUPPORTED\":\"Dieser Nachrichtentyp wird nicht unterstützt\",\n\t\"NO_USERS_FOUND\": \"Keine Benutzer gefunden\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Wir konnten keine Benutzer finden, die Ihrer Suche entsprechen. Versuchen Sie, Ihre Suche anzupassen.\",\n\t\"SAVE\": \"Speichern\",\n\t\"RETRY\": \"Erneut versuchen\",\n\t\"TAP_TO_START_CONVERSATION\" : \"Tippen, um das Gespräch zu starten\",\n\t\"ATTACH_DOCUMENT\": \"Dokument anhängen\",\n\t\"CAMERA\": \"Kamera\",\n\t\"TEXT_TRANSLATED\":\"Text übersetzt\",\n\t\"SHARE\": \"Teilen\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Sind Sie sicher, dass Sie entfernen möchten\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Tragen Sie hier Ihre Nachricht ein\",\n\t\"TAP_TO_REMOVE\": \"Zum Entfernen antippen\",\n\t\"ADD_CONTACTS\": \"Fügen Sie Kontakte hinzu, um Gespräche zu starten und sie hier aufgelistet zu sehen.\",\n\t\"SCOPE_CHANGE_INFO\": \"Sie können Rollen ändern, um Gruppenberechtigungen und Verantwortlichkeiten zu verwalten\",\n\t\"SOMETHING_WENT_WRONG\": \"Es scheint, dass etwas schiefgelaufen ist.\",\n\t\"MENTION_UPTO\": \"Sie können bis zu erwähnen\",\n\t\"TIME\": \"Zeit\",\n\t\"TIMES\": \"mal\",\n\t\"AT_A_TIME\": \"zu einer Zeit\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Für diese Funktion sind spezielle Ressourcenberechtigungen erforderlich. Bitte aktivieren Sie die erforderlichen Berechtigungen in Ihren Geräteeinstellungen\",\n\t\"CHAT_PRIVATELY\": \"Privat chatten\",\n\t\"MICROPHONE_PERMISSION\": \"Um eine Sprachnachricht aufzunehmen, benötigen wir Zugriff auf Ihr Mikrofon. Tippe auf Einstellungen und schalte das Mikrofon ein.\",\n\t\"CAMERA_PERMISSION\": \"Wir haben keinen Zugriff auf Ihre Kamera. Um den Zugriff zu aktivieren, tippen Sie auf Einstellungen und schalten Sie die Kamera ein.\",\n\t\"ADD_OPTIONS\": \"Optionen hinzufügen\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Dokument zum Zusammenzeichnen öffnen\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Whiteboard öffnen, um gemeinsam zu zeichnen\",\n\t\"TEXT\": \"Text\",\n\t\"BAN_MEMBER_CONFIRM\": \"Sind Sie sicher, dass Sie bannen möchten \",\n\t\"LOGIN\": \"Anmelden\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Mit Google fortfahren\",\n\t\"JOIN_GROUP\": \"Gruppe beitreten\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Passwort für die Gruppe ist falsch!\",\n\t\"ENTER_PASSWORD\": \"Passwort eingeben\",\n\t\"TYPE\": \"Typ\",\n\t\"USER_INFO\": \"Benutzerinformationen\",\n\t\"DELETE_CHAT_TEXT\": \"Chat löschen\",\n\t\"UNBLOCK\": \"Entsperren\",\n\t\"UNBLOCK_CONTACT\": \"Diesen Kontakt entsperren\",\n\t\"BLOCK_CONTACT\": \"Diesen Kontakt entsperren\",\n\t\"UNBLOCK_SURE\": \"Sind Sie sicher, dass Sie diesen Kontakt entsperren möchten? Sie werden wieder Nachrichten von ihnen erhalten.\",\n\t\"BLOCK_SURE\": \"Sind Sie sicher, dass Sie diesen Kontakt blockieren möchten? Sie werden keine Nachrichten mehr von ihnen erhalten.\",\n\t\"VOICE\": \"Stimme\",\n\t\"DELETE_AND_EXIT_SURE\": \"Sind Sie sicher, dass Sie diesen Chat löschen und die Gruppe verlassen möchten? Diese Aktion kann nicht rückgängig gemacht werden.\",\n\t\"TRANSFER_SURE\": \"Sind Sie sicher, dass Sie die Eigentümerschaft übertragen möchten? Dies kann nicht rückgängig gemacht werden, und der neue Eigentümer wird die volle Kontrolle übernehmen.\",\n\t\"GROUP_INFO\": \"Gruppeninfo\",\n\t\"LEAVE_GROUP_TEXT\": \"Diese Gruppe verlassen\",\n\t\"LEAVE_SURE\": \"Sind Sie sicher, dass Sie die Gruppe verlassen möchten? Sie werden keine weiteren Nachrichten von diesem Chat erhalten.\",\n\t\"UNBAN_SURE\": \"Sind Sie sicher, dass Sie diesen Benutzer entsperren möchten?\",\n\t\"DELETE_THIS_CONVERSATION\": \"Diese Unterhaltung löschen?\",\n\t\"LINK\": \"Link\",\n\t\"DELETE_THIS_MESSAGE\": \"Diese Nachricht löschen?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Bist du sicher, dass du diese Nachricht löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.\",\n\t\"APP_CREDENTIALS\": \"App-Anmeldeinformationen\",\n\t\"REGION\": \"Region\",\n\t\"WRONG_TEXT\": \"Sieht so aus, als ob etwas schiefgelaufen ist.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Bitte versuchen Sie es noch einmal.\",\n\t\"BLOCKED_USER_DESC\": \"Nachricht kann nicht gesendet werden, da der Benutzer blockiert ist\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} hat ${onName} zur Gruppe hinzugefügt\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} ist der Gruppe beigetreten\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} hat die Gruppe verlassen\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} hat ${onName} aus der Gruppe entfernt\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} hat ${onName} aus der Gruppe verbannt\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} hat die Verbannung von ${onName} aufgehoben\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} hat ${onName} zum ${role} ernannt\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Du hast noch keine Sticker.\",\n\t\"meeting\": \"Besprechung\",\n\t\"Flag_Message_Title\": \"Nachricht melden\",\n\t\"Flag_Message_Subtitle\": \"Melden Sie diesen Chat, wenn er gegen unsere Gemeinschaftsstandards verstößt. Wir informieren den Account nicht, den Sie gemeldet haben.\",\n\t\"Flag_Message_Remark_Label\": \"Grund\",\n\t\"Flag_Message_Remark_Optional\": \"Optional\",\n\t\"Flag_Message_Remark_Placeholder\": \"Geben Sie zusätzlichen Kontext für Ihre Meldung an…\",\n\t\"Flag_Message_Confirm_Yes\": \"Melden\",\n\t\"Flag_Message_Confirm_No\": \"Abbrechen\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / Unerwünschte Inhalte\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Explizite oder unangemessene Inhalte\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Beleidigendes oder bedrohliches Verhalten\",\n\t\"Message_List_Option_Flag_Message\": \"Melden\",\n\t\"Flag_Error_Text\":\"Etwas ist schiefgelaufen. Bitte überprüfen Sie es und versuchen Sie es erneut.\",\n\t\"Flag_Empty_Submit_Text\":\"Senden nicht möglich. Bitte wählen Sie einen Grund aus, bevor Sie diese Nachricht melden.\",\n\t\"START_REPORTING\": \"Bericht starten\",\n\t\"LOGOUT\": \"Abmelden\",\n\t\"AI_ASSISTANTS\": \"KI-Assistenten\",\n\t\"CREATE_CONVERSATION\": \"Konversation erstellen\",\n\t\"EDIT_MODERATION\": \"Bearbeitung fehlgeschlagen. Ihre Nachricht wurde aufgrund der Moderationsrichtlinien blockiert.\",\n\t\"BLOCKED_MODERATION\":\"Ihre Nachricht wurde aufgrund der Moderationsrichtlinien blockiert.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Kein Gesprächsverlauf gefunden\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Etwas ist schief gelaufen. Bitte versuchen Sie es erneut\",\n\t\"START_A_CHAT\": \"Beginnen Sie einen Chat, indem Sie auf die Schaltfläche 'Neuer Chat' tippen\",\n\t\"CHAT_HISTORY\": \"Chat-Verlauf\",\n\t\"ASK_ANYTHING\": \"Frag etwas...\",\n\t\"AI_ASSISTANT\": \"AI-Assistent\",\n\t\"MESSAGE_REPORTED\": \"Nachricht gemeldet\",\n\t\"MARK_AS_UNREAD\": \"Ungelesen markieren\",\n\t\"NEW\": \"Neu\",\n\t\"INSERT_LINK\": \"Link einfügen\",\n\t\"EDIT_LINK\": \"Link bearbeiten\",\n\t\"LINK_TEXT\": \"Linktext\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Einfügen\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Sie können keine Nachricht an diese Gruppe senden, da Sie kein Mitglied mehr sind.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/en/translation.json",
    "content": "{\n\t\"INFO\": \"Info\",\n\t\"REACT\": \"React\",\n\t\"EDIT\": \"Edit\",\n\t\"MESSAGE_PRIVATELY\": \"Message Privately\",\n\t\"TRANSLATE\": \"Translate\",\n\t\"USERS\": \"Users\",\n\t\"CHATS\": \"Chats\",\n\t\"SEARCH_EMOJI\": \"Search emoji\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Would you like to delete this conversation? This conversation will be deleted from all of your devices.\",\n\t\"GROUPS\": \"Groups\",\n\t\"SHARED\": \"Shared\",\n\t\"SOUND_MANAGER\": \"Sound Manager\",\n\t\"THEME\": \"Theme\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"all\",\n    \"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notify everyone in this group\",\n\t\"DELETE_MSG_TEXT\": \"This message was deleted\",\n\t\"LOCALIZE\": \"Localize\",\n\t\"CONVERSATION_LIST_ITEM\": \"Conversation List Item\",\n\t\"DATA_ITEM\": \"Data Item\",\n\t\"STATUS_INDICATOR\": \"Status Indicator\",\n\t\"BADGE_COUNT\": \"Badge Count\",\n\t\"MESSAGE_RECEIPT\": \"Message Receipt\",\n\t\"MESSAGE\": \"Message\",\n\t\"RECEIPT_INFORMATION\": \"Receipt Information\",\n\t\"NO_RECIPIENT\": \"No Recipient\",\n\t\"NO_RECIPIENTS\": \"No Recipients\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Conversations With Messages\",\n\t\"CONVERSATIONS\": \"Conversations\",\n\t\"CONVERSATION_LIST\": \"Conversation List\",\n\t\"MESSAGES\": \"Messages\",\n\t\"WRONG_FILE_TYPE\": \"You selected a different type of file. Please choose the appropriate file.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"This file type is not allowed.\",\n\t\"MESSAGE_HEADER\": \"Message Header\",\n\t\"MESSAGE_LIST\": \"Message List\",\n\t\"MESSAGE_COMPOSER\": \"Message Composer\",\n\t\"USERS_WITH_MESSAGES\": \"Users With Messages\",\n\t\"USER_LIST\": \"User List\",\n\t\"GROUP_LIST\": \"Group List\",\n\t\"GROUPS_WITH_MESSAGES\": \"Groups With Messages\",\n\t\"NEW__GROUP\": \"New Group\",\n\t\"PASSWORD\": \"Password\",\n\t\"CONTINUE\": \"Continue\",\n\t\"NO_CHATS_SELECTED\": \"No Chats Selected\",\n\t\"NO_USERS_SELECTED\": \"No Users Selected\",\n\t\"NO_GROUPS_SELECTED\": \"No Groups Selected\",\n\t\"SELECT_DAY\": \"Select a Day\",\n\t\"SELECT_TIME\": \"Select a Time\",\n\t\"NO_CALLS_SELECTED\": \"No Calls Selected\",\n\t\"SELECT__GROUP\": \"Select a group to start messaging\",\n\t\"SELECT__USER\": \"Select a user to start messaging\",\n\t\"GROUP_PASSWORD_BLANK\": \"Group password cannot be blank\",\n\t\"GROUP_NAME_BLANK\": \"Group name cannot be blank\",\n\t\"GROUP_TYPE_BLANK\": \"Group type cannot be blank\",\n\t\"DELETE_CONVERSATION\": \"Delete Conversation?\",\n\t\"ADD_TO_CHAT\": \"Add to Chat\",\n\t\"MORE\": \"More\",\n\t\"COPY\": \"Copy\",\n\t\"VOICE_RECORDING\": \"Voice Recording\",\n\t\"MESSAGE_IMAGE\": \"Image\",\n\t\"MESSAGE_FILE\": \"File\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"CUSTOM_MESSAGE\": \"You have a message\",\n\t\"SELECT_A_DATE\": \"Select a date\",\n\t\"TIME_ZONE\": \"Time Zone\",\n\t\"MEETING_SCHEDULED\": \"Your meeting has been scheduled.\",\n\t\"SOMETHING_WRONG\": \"Something went wrong. Please try again.\",\n\t\"MEETING_SLOT_BOOK\": \"Time slot no longer available. Please choose another.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Book New Slot\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"No time slot available for this date. Please try another date.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Please try a different date.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"No Time slots available\",\n\t\"SCHEDULE\": \"Schedule\",\n\t\"MORE_TIMES\": \"More times\",\n\t\"MISSED_VOICE_CALL\": \"Missed voice call\",\n\t\"MEET_WITH\": \"Meet With\",\n\t\"CONVERSATION_DELETED\": \"Conversation Deleted\",\n\t\"MEETING_SCHEDULER\": \"Meeting Scheduler\",\n\t\"MISSED_VIDEO_CALL\": \"Missed video call\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Poll\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Sticker\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Document\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Whiteboard\",\n\t\"ONLINE\": \"Online\",\n\t\"ADMINISTRATOR\": \"Administrator\",\n\t\"ADMIN\": \"Admin\",\n\t\"MODERATOR\": \"Moderator\",\n\t\"PARTICIPANT\": \"Participant\",\n\t\"PUBLIC\": \"Public\",\n\t\"PRIVATE\": \"Private\",\n\t\"PASSWORD_PROTECTED\": \"Password Protected\",\n\t\"PRIVACY_AND_SECURITY\": \"Privacy and Security\",\n\t\"PREFERENCES\": \"Preferences\",\n\t\"MEMBERS\": \"Members\",\n\t\"MEMBER\": \"Member\",\n\t\"EDITED\": \"Edited\",\n\t\"TODAY\": \"Today\",\n\t\"YESTERDAY\": \"Yesterday\",\n\t\"SUNDAY\": \"Sunday\",\n\t\"MONDAY\": \"Monday\",\n\t\"TUESDAY\": \"Tuesday\",\n\t\"WEDNESDAY\": \"Wednesday\",\n\t\"THURSDAY\": \"Thursday\",\n\t\"FRIDAY\": \"Friday\",\n\t\"SATURDAY\": \"Saturday\",\n\t\"TYPING\": \"Typing...\",\n\t\"IS_TYPING\": \"is typing...\",\n\t\"CLOSE\": \"Close\",\n\t\"ENTER_GROUP_NAME\": \"Enter group name\",\n\t\"ADD_MEMBERS\": \"Add Members\",\n\t\"SEND_MESSAGE\": \"Send Message\",\n\t\"UNBLOCK_USER\": \"Unblock User\",\n\t\"BLOCK_USER\": \"Block User\",\n\t\"DELETE_AND_EXIT\": \"Delete and Exit\",\n\t\"LEAVE_GROUP\": \"Leave Group\",\n\t\"CREATE_GROUP\": \"Create Group\",\n\t\"SHARED_MEDIA\": \"Shared Media\",\n\t\"SHARED_FILE\": \"Shared File\",\n\t\"VIDEO_CALL\": \"Video call\",\n\t\"AUDIO_CALL\": \"Audio call\",\n\t\"LOADING\": \"Loading...\",\n\t\"REPLY\": \"Reply\",\n\t\"REPLIES\": \"Replies\",\n\t\"AI\": \"AI\",\n\t\"SMART_REPLIES\": \"Smart Replies\",\n\t\"CONVERSATION_STARTER\": \"Conversation Starter\",\n\t\"LAUNCH\": \"Launch\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"has shared a collaborative document\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"has shared a collaborative whiteboard\",\n\t\"CREATED_WHITEBOARD\": \"You’ve created a new collaborative whiteboard\",\n\t\"CREATED_DOCUMENT\": \"You’ve created a new collaborative document\",\n\t\"PHOTOS\": \"Photos\",\n\t\"VIDEOS\": \"Videos\",\n\t\"DOCUMENT\": \"Document\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ You deleted this message\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ This message was deleted\",\n\t\"MESSAGE_IS_DELETED\": \"Message is deleted\",\n\t\"MESSAGE_COPIED\": \"Message copied to clipboard.\",\n\t\"MESSAGE_EDITED\": \"Message updated successfully.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Message deleted successfully.\",\n\t\"MESSAGE_TRANSLATED\": \"Message translated successfully.\",\n\t\"VIEW_ON_YOUTUBE\": \"View on Youtube\",\n\t\"SEARCH\": \"Search\",\n\t\"ERROR\": \"Error\",\n\t\"ERROR_TEXT\": \"Looks like something went wrong. Please try again.\",\n\t\"NO_GROUPS_FOUND\": \"No groups found\",\n\t\"NO_CHATS_FOUND\": \"No chats found\",\n\t\"CANT__LOAD__CHATS\": \"Can't load chats\",\n\t\"MEDIA_MESSAGE\": \"Media message\",\n\t\"INCOMING_AUDIO_CALL\": \"Incoming audio call\",\n\t\"INCOMING_VIDEO_CALL\": \"Incoming video call\",\n\t\"DECLINE\": \"Decline\",\n\t\"ACCEPT\": \"Accept\",\n\t\"INCOMING_CALL\": \"Incoming Call\",\n\t\"OUTGOING_CALL\": \"Outgoing Call\",\n\t\"CALL_REJECTED\": \"Call Rejected\",\n\t\"CALL_ANSWERED\": \"Call Answered\",\n\t\"CALL_CANCELLED\": \"Call Cancelled\",\n\t\"MISSED_CALL\": \"Missed Call\",\n\t\"CALL_UNANSWERED\": \"Call Unanswered\",\n\t\"CALL_INITIATED\": \"Call initiated\",\n\t\"OUTGOING_AUDIO_CALL\": \"Outgoing audio call\",\n\t\"OUTGOING_VIDEO_CALL\": \"Outgoing video call\",\n\t\"REJECTED_CALL\": \"rejected call\",\n\t\"CALL_ACCEPTED\": \"Call accepted\",\n\t\"JOINED\": \"joined\",\n\t\"LEFT_THE_CALL\": \"left the call\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Unanswered audio call\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Unanswered video call\",\n\t\"CALL_ENDED\": \"Call Ended\",\n\t\"CALL_BUSY\": \"Call Busy\",\n\t\"CALLING\": \"Calling...\",\n\t\"ADD\": \"Add\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"No banned members found\",\n\t\"BANNED_MEMBERS\": \"Banned Members\",\n\t\"NAME\": \"Name\",\n\t\"SCOPE\": \"Scope\",\n\t\"UNBAN\": \"Unban\",\n\t\"SELECT_GROUP_TYPE\": \"Select group type\",\n\t\"ENTER_GROUP_PASSWORD\": \"Enter group password\",\n\t\"CREATE\": \"Create\",\n\t\"CREATE_POLL\": \"Poll\",\n\t\"QUESTION\": \"Question\",\n\t\"ENTER_YOUR_QUESTION\": \"Enter your question\",\n\t\"OPTIONS\": \"Options\",\n\t\"ENTER_YOUR_OPTION\": \"Enter your option\",\n\t\"ADD_NEW_OPTION\": \"Add new option\",\n\t\"VIEW_MEMBERS\": \"View Members\",\n\t\"DETAILS\": \"Details\",\n\t\"NOTIFICATIONS\": \"Notifications\",\n\t\"OTHER\": \"Other\",\n\t\"HELP\": \"Help\",\n\t\"REPORT_PROBLEM\": \"Report a Problem\",\n\t\"GROUP_MEMBERS\": \"Group Members\",\n\t\"BAN\": \"Ban\",\n\t\"KICK\": \"Kick\",\n\t\"PICK_YOUR_EMOJI\": \"Pick your emoji\",\n\t\"PRIVATE_GROUP\": \"Private Group\",\n\t\"PROTECTED_GROUP\": \"Protected Group\",\n\t\"VISIT\": \"Visit\",\n\t\"ATTACH\": \"Attach\",\n\t\"OPEN_WHITEBOARD\": \"Open Whiteboard\",\n\t\"ATTACH_FILE\": \"Attach file\",\n\t\"GENERATING_REPLIES\": \"Generating replies\",\n\t\"ATTACH_VIDEO\": \"Attach video\",\n\t\"ATTACH_AUDIO\": \"Attach audio\",\n\t\"ATTACH_IMAGE\": \"Attach image\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Collaborate using a document\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Collaborate using a whiteboard\",\n\t\"SUGGEST_A_REPLY\": \"Suggest a reply\",\n\t\"GENERATING_ICEBREAKERS\": \"Generating icebreakers\",\n\t\"EMOJI\": \"Emoji\",\n\t\"NO_REPLIES_FOUND\": \"No Replies Found\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Enter your message here\",\n\t\"NO_MESSAGES_FOUND\": \"No messages found\",\n\t\"THREAD\": \"Thread\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Collaborative Document\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Collaborative Whiteboard\",\n\t\"ADD_REACTION\": \"Add reaction\",\n\t\"NO_STICKERS_FOUND\": \"No stickers found\",\n\t\"REPLY_TO_THREAD\": \"Reply to thread\",\n\t\"REPLY_IN_THREAD\": \"Reply in thread\",\n\t\"VIEW\": \"View\",\n\t\"DELETE_MESSAGE\": \"Delete message\",\n\t\"EDIT_MESSAGE\": \"Edit message\",\n\t\"OWNER\": \"Owner\",\n\t\"CHANGE_SCOPE\": \"Change Scope\",\n\t\"STICKER\": \"Sticker\",\n\t\"LAST_ACTIVE_AT\": \"Last Active At\",\n\t\"LAST_SEEN\": \"Last seen\",\n\t\"AT\": \"at\",\n\t\"VOICE_CALL\": \"Voice call\",\n\t\"VIEW_DETAIL\": \"View Detail\",\n\t\"VOTES\": \"votes\",\n\t\"VOTE\": \"vote\",\n\t\"NO_VOTE\": \"No vote\",\n\t\"REACTED\": \"reacted\",\n\t\"ADDED\": \"added\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Are you sure want to see unsafe content?\",\n\t\"REACT_TO_MESSAGE\": \"React to a message\",\n\t\"UNBANNED\": \"unbanned\",\n\t\"MADE\": \"made\",\n\t\"MISSED_AUDIO_CALL\": \"Missed audio call\",\n\t\"ENTER_YOUR_PASSWORD\": \"Enter your password\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Open document to edit content together\",\n\t\"DOCS\": \"Docs\",\n\t\"NO_RECORDS_FOUND\": \"No records found\",\n\t\"LIVE_REACTION\": \"Live Reaction\",\n\t\"SMILEY_PEOPLE\": \"Smileys & People\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Open whiteboard to draw together\",\n\t\"ANIMALES_NATURE\": \"Animals & Nature\",\n\t\"FOOD_DRINK\": \"Food & Drink\",\n\t\"OPEN_DOCUMENT\": \"Open Document\",\n\t\"ACTIVITY\": \"Activity\",\n\t\"TRAVEL_PLACES\": \"Travel & Places\",\n\t\"OBJECTS\": \"Objects\",\n\t\"SYMBOLS\": \"Symbols\",\n\t\"FLAGS\": \"Flags\",\n\t\"SENT\": \"Sent\",\n\t\"SEEN\": \"Seen\",\n\t\"DELIVERED\": \"Delivered\",\n\t\"READ\": \"Read\",\n\t\"MESSAGE_INFORMATION\": \"Message Info\",\n\t\"TRANSLATE_MESSAGE\": \"Translate message\",\n\t\"TRANSLATED_MESSAGE\": \"Translated message\",\n\t\"LEFT\": \"left\",\n\t\"KICKED\": \"kicked\",\n\t\"BANNED\": \"banned\",\n\t\"NEW_MESSAGES\": \"new messages\",\n\t\"NEW_MESSAGE\": \"new message\",\n\t\"JUMP\": \"Jump\",\n\t\"SELECT_VIDEO_SOURCE\": \"Select video source\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Select input audio source\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Select output audio source\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"has initiated an audio call\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"has initiated a video call\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"You've initiated an audio call\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"You've initiated a video call\",\n\t\"IGNORE\": \"Ignore\",\n\t\"ON_ANOTHER_CALL\": \"is on another call\",\n\t\"CREATING\": \"Creating\",\n\t\"AVATAR\": \"Avatar\",\n\t\"ONGOING_CALL\": \"Ongoing call\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"You are already in an ongoing call\",\n\t\"RESIZE\": \"Resize\",\n\t\"READ_MORE\": \"Read more\",\n\t\"SHOW_LESS\": \"Show less\",\n\t\"SETTINGS\": \"Settings\",\n\t\"ACTIONS\": \"Actions\",\n\t\"VIEW_PROFILE\": \"View Profile\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Send message privately\",\n\t\"DELETE\": \"Delete\",\n\t\"DELETE_CONFIRM\": \"Are you sure you want to delete?\",\n\t\"DELETE_CHAT\": \"Delete this chat?\",\n\t\"SURE_TO_DELETE_CHAT\": \"Are you sure you want to delete this chat? This action cannot be undone.\",\n\t\"CANCEL\": \"Cancel\",\n\t\"LEAVE_CONFIRM\": \"Are you sure you want to leave the group?\",\n\t\"TRANSFER_CONFIRM\": \"You are the group owner, please transfer ownership to a member before leaving the group\",\n\t\"ADDING\": \"Adding...\",\n\t\"TRANSFER\": \"Transfer\",\n\t\"TRANSFER_OWNERSHIP\": \"Transfer Ownership\",\n\t\"TRANSFERRING\": \"Transferring\",\n\t\"YES\": \"Yes\",\n\t\"NO\": \"No\",\n\t\"ANSWER\": \"Answer\",\n\t\"ADD_ANOTHER_ANSWER\": \"Add Another Answer\",\n\t\"SET_THE_ANSWERS\": \"SET THE ANSWERS\",\n\t\"TRY_AGAIN\": \"Try again\",\n\t\"INVALID_GROUP_NAME\": \"Please enter a valid name for the group and try again\",\n\t\"INVALID_PASSWORD\": \"Please enter a valid password for the group and try again\",\n\t\"INVALID_GROUP_TYPE\": \"Please enter a valid type for the group and try again\",\n\t\"WRONG_PASSWORD\": \"Please enter the correct password and try again\",\n\t\"INVALID_POLL_QUESTION\": \"Please enter the required question before creating a poll\",\n\t\"INVALID_POLL_OPTION\": \"Please enter the required answer before creating a poll\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Selected language for translation is similar to the language of original message\",\n\t\"LEAVE\": \"Leave\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Click to start conversation\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍Location\",\n\t\"SHARED_LOCATION\": \"Shared Location\",\n\t\"IN_A_THREAD\": \"In a thread\",\n\t\"CALLS\": \"Calls\",\n\t\"CALL_DETAILS\": \"Call Details\",\n\t\"OFFLINE\": \"Offline\",\n\t\"POLLS\": \"Polls\",\n\t\"YOU\": \"You\",\n\t\"PRIVACY\": \"Privacy\",\n\t\"BLOCKED_USERS\": \"Blocked Users\",\n\t\"YOU'VE_BLOCKED\": \"You've blocked\",\n\t\"NO_PHOTOS\": \"No Photos\",\n\t\"NO_VIDEOS\": \"No Videos\",\n\t\"NO_DOCUMENTS\": \"No Documents\",\n\t\"JOIN\": \"Join\",\n\t\"REMOVE\": \"Remove\",\n\t\"BLOCK\": \"Block\",\n\t\"CHANGE_ROLE\": \"Change Role\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"You can change roles to manage group permissions and responsibilities.\",\n\t\"CHANGE_TO\": \"Change to\",\n\t\"NEW_CHAT\": \"New Chat\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Thank you for filling out the form.\",\n\t\"HISTORY\": \"History\",\n\t\"CANCELLED_AUDIO_CALL\": \"Cancelled audio call\",\n\t\"CANCELLED_VIDEO_CALL\": \"Cancelled video call\",\n\t\"REJECTED_AUDIO_CALL\": \"Rejected audio call\",\n\t\"REJECTED_VIDEO_CALL\": \"Rejected video call\",\n\t\"CANCELLED_CALL\": \"Cancelled call\",\n\t\"UNANSWERED_CALL\": \"Unanswered call\",\n\t\"RECORDING\": \"Recording\",\n\t\"PARTICIPANTS\": \"Participants\",\n\t\"CALL_HISTORY\": \"Call History\",\n\t\"NO_CALLS_FOUND\": \"No Calls Found\",\n\t\"ONGOING_AUDIO_CALL\": \"Ongoing audio call\",\n\t\"ONGOING_VIDEO_CALL\": \"Ongoing video call\",\n\t\"CALL_DETAIL\": \"Call Detail\",\n\t\"FORM\": \"Form\",\n\t\"CARD\": \"Card\",\n\t\"GENERATING_SUMMARY\": \"Generating summary\",\n\t\"CONVERSATION_SUMMARY\": \"Conversation summary\",\n\t\"GENERATE_SUMMARY\": \"Generate a summary\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"How can I help you with this conversation? Please ask me a question and I’ll advice you 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"Ask\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Ask AI Bots\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI Bot\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"You can mention up to 10 users at once.\",\n\t\"ALL\": \"All\",\n\t\"CLICK_TO_REMOVE\": \"Click to remove\",\n\t\"OTHERS\": \"others\",\n\t\"AND\": \"and\",\n\t\"ASK_QUESTION\": \"Ask question\",\n\t\"ADD_OPTION\": \"Add Option\",\n\t\"SEND\": \"Send\",\n\t\"FULL_SCREEN_VIEWER\": \"Full Screen Viewer\",\n\t\"TEXT_TRANSLATE\": \"Text Translate\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Looks like something went wrong\",\n\t\"NO_SUMMARY_AVAILABLE\": \"No Summary Available\",\n\t\"QUESTIONS\": \"Questions\",\n\t\"NO_STICKERS_AVAILABLE\": \"No Stickers Available\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"No Suggestions Available\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Create or join groups to see them listed here and start collaborating\",\n\t\"NO_USERS_AVAILABLE\": \"No Users Available\",\n\t\"NO_GROUPS_AVAILABLE\": \"No Groups Available\",\n\t\"NO_CALL_LOGS\": \"No Call Logs Yet\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Make or receive calls to see your call history listed here\",\n\t\"NO_CONVERSATIONS\": \"No Conversations Yet\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Start a new chat or invite others to join the conversation.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"No Group Member Available\",\n\t\"OOPS!\": \"OOPS!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Waiting for recipients to receive and view the message\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Add contacts to see them listed here.\",\n\t\"REACHED_MAX_LIMIT\": \"You’ve reached the limit. You can add up to 12 options.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Please fill in all required fields before creating a poll.\",\n\t\"NOT_SUPPORTED\": \"This message type is not supported\",\n\t\"NO_USERS_FOUND\": \"No Users Found\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"We couldn’t find any users matching your search. Try adjusting your search.\",\n\t\"SAVE\": \"Save\",\n\t\"RETRY\": \"Retry\",\n\t\"TAP_TO_START_CONVERSATION\": \"Tap to start conversation\",\n\t\"TEXT_TRANSLATED\": \"Text Translated\",\n\t\"CAMERA\": \"Camera\",\n\t\"SHARE\": \"Share\",\n\t\"ATTACH_DOCUMENT\": \"Attach Document\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Are you sure you want to remove \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Type your message...\",\n\t\"TAP_TO_REMOVE\": \"Tap to remove\",\n\t\"ADD_CONTACTS\": \"Add contacts to start conversations and see them listed here.\",\n\t\"SCOPE_CHANGE_INFO\": \"You can change roles to manage group permissions and responsibilities\",\n\t\"SOMETHING_WENT_WRONG\": \"Looks like something went wrong.\",\n\t\"MENTION_UPTO\": \"You can mention up to\",\n\t\"TIME\": \"time\",\n\t\"TIMES\": \"times\",\n\t\"AT_A_TIME\": \"at a time\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"This feature requires specific resource permission(s). Please enable the necessary permissions in your device settings.\",\n\t\"MICROPHONE_PERMISSION\": \"To record a Voice Message, we need access to your microphone. Tap Settings and turn on Microphone.\",\n\t\"CAMERA_PERMISSION\": \"We do not have access to your camera. To enable access, tap Settings and turn on Camera.\",\n\t\"OOPS\": \"Oops!\",\n\t\"ADD_OPTIONS\": \"Add Options\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Open document to draw together\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Open whiteboard to draw together\",\n\t\"TEXT\": \"Text\",\n\t\"BAN_MEMBER_CONFIRM\": \"Are you sure you want to ban \",\n\t\"LOGIN\": \"Log In\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Continue with Google\",\n\t\"JOIN_GROUP\": \"Join Group\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Password for the group is incorrect!\",\n\t\"ENTER_PASSWORD\": \"Enter Password\",\n\t\"TYPE\": \"Type\",\n\t\"USER_INFO\": \"User Info\",\n\t\"DELETE_CHAT_TEXT\": \"Delete Chat\",\n\t\"UNBLOCK\": \"Unblock\",\n\t\"UNBLOCK_CONTACT\": \"Unblock this contact\",\n\t\"BLOCK_CONTACT\": \"Block this contact?\",\n\t\"UNBLOCK_SURE\": \"Are you sure you want to unblock this contact? You will start receiving messages from them again.\",\n\t\"BLOCK_SURE\": \"Are you sure you want to block this contact? You won't receive messages from them anymore.\",\n\t\"VOICE\": \"Voice\",\n\t\"DELETE_AND_EXIT_SURE\": \"Are you sure you want to delete this chat and exit the group? This action cannot be undone.\",\n\t\"TRANSFER_SURE\": \"Are you sure you want to transfer ownership? This can't be undone, and the new owner will take full control.\",\n\t\"GROUP_INFO\": \"Group Info\",\n\t\"LEAVE_GROUP_TEXT\": \"Leave this group\",\n\t\"LEAVE_SURE\": \"Are you sure you want to leave the group? You won't receive any more messages from this chat.\",\n\t\"UNBAN_SURE\": \"Are you sure you want to unban this user?\",\n\t\"DELETE_THIS_CONVERSATION\": \"Delete this conversation?\",\n\t\"LINK\": \"Link\",\n\t\"INSERT_LINK\": \"Insert Link\",\n\t\"EDIT_LINK\": \"Edit Link\",\n\t\"LINK_TEXT\": \"Link text\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Insert\",\n\t\"DELETE_THIS_MESSAGE\": \"Delete this Message?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Are you sure you want to delete this message? This action cannot be undone.\",\n\t\"APP_CREDENTIALS\": \"App Credentials\",\n\t\"REGION\": \"Region\",\n\t\"CHAT_PRIVATELY\": \"Chat Privately\",\n\t\"WRONG_TEXT\":\"Looks like something went wrong.\",\n\t\"WRONG_TEXT_TRY_AGAIN\":\"Please try again.\",\n\t\"BLOCKED_USER_DESC\": \"Can't send a message as the user is blocked\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} added ${onName} to the group\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} joined the group\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} left the group\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} removed ${onName} from the group\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} banned ${onName} from the group\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} unbanned ${onName}\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} made ${onName} a ${role}\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"You don’t have any stickers yet.\",\n\t\"meeting\": \"Meeting\",\n\t\"Flag_Message_Title\": \"Report a Message\",\n\t\"Flag_Message_Subtitle\": \"Report this chat if it goes against our Community Standards. We won't tell the account you reported them.\",\n    \"Flag_Message_Remark_Label\": \"Reason\",\n    \"Flag_Message_Remark_Optional\": \"Optional\",\n    \"Flag_Message_Remark_Placeholder\": \"Provide additional context for your report...\",\n    \"Flag_Message_Confirm_Yes\": \"Report\",\n    \"Flag_Message_Confirm_No\": \"Cancel\",\n    \"Flag_Message_Reason_Id_Spam\": \"Spam / Unwanted Content\",\n    \"Flag_Message_Reason_Id_Sexual\": \"Explicit or inappropriate content\",\n    \"Flag_Message_Reason_Id_Harassment\": \"Insulting or threatening behavior\",\n    \"Message_List_Option_Flag_Message\": \"Report\",\n\t\"Flag_Error_Text\":\"Something went wrong. Please check and try again.\",\n\t\"Flag_Empty_Submit_Text\":\"Unable to submit. Please select a reason before reporting this message\",\n\t\"START_REPORTING\": \"Start Reporting\",\n\t\"LOGOUT\": \"Logout\",\n\t\"AI_ASSISTANTS\": \"AI Assistants\",\n\t\"CREATE_CONVERSATION\": \"Create Conversation\",\n\t\"EDIT_MODERATION\": \"Edit failed. Your message was blocked due to moderation policies.\",\n\t\"BLOCKED_MODERATION\":\"Your message was blocked due to moderation policies.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"No conversation history found\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Something went wrong on our end. Please try again.\",\n\t\"START_A_CHAT\": \"Click the 'New Chat' button to start a chat.\",\n\t\"CHAT_HISTORY\": \"Chat History\",\n\t\"ASK_ANYTHING\": \"Ask anything...\",\n\t\"AI_ASSISTANT\": \"AI Assistant\",\n\t\"SEARCH_PLACEHOLDER\": \"Search...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"No Results\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Start typing to search for messages and conversations\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"No Results Found\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"We couldn't find any matches. Please try a different search keyword.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Error Loading Conversations\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Error Loading Messages\",\n\t\"SEARCH_TRY_AGAIN\": \"Please try again later\",\n\t\"SEARCH_SEE_MORE\": \"See More\",\n\t\"SEARCH_LOADING\": \"Loading...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Unread\",\n\t\"SEARCH_FILTER_GROUPS\": \"Groups\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Photos\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Videos\",\n\t\"SEARCH_FILTER_LINKS\": \"Links\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Documents\",\n\t\"SEARCH_FILTER_AUDIO\": \"Audio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Conversations\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Messages\",\n\t\"SEARCH_NO_MESSAGES\": \"No messages\",\n  \t\"MESSAGE_REPORTED\": \"Message reported\",\n  \t\"MARK_AS_UNREAD\": \"Mark Unread\",\n\t\"NEW\": \"New\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"You can't send message to this group because you're no longer a member.\"\n}"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/es/translation.json",
    "content": "{\n\t\"INCOMING_CALL\": \"Llamada entrante\",\n\t\"OUTGOING_CALL\": \"Llamada saliente\",\n\t\"CALL_REJECTED\": \"Llamada rechazada\",\n\t\"CALL_ANSWERED\": \"Llamada respondida\",\n\t\"CALL_CANCELLED\": \"Llamada cancelada\",\n\t\"MISSED_CALL\": \"Llamada perdida\",\n\t\"CALL_UNANSWERED\": \"Llamada sin respuesta\",\n\t\"INFO\": \"Información\",\n\t\"REACT\": \"Reacciona\",\n\t\"EDIT\": \"Editar\",\n\t\"MESSAGE_PRIVATELY\": \"Mensaje privado\",\n\t\"CONVERSATION_DELETED\": \"Conversación eliminada\",\n\t\"MESSAGE_COPIED\": \"Mensaje copiado al portapapeles.\",\n\t\"MESSAGE_EDITED\": \"El mensaje se actualizó correctamente.\",\n\t\"MESSAGE_DELETED_TEXT\": \"El mensaje se ha eliminado correctamente.\",\n\t\"MESSAGE_TRANSLATED\": \"El mensaje se tradujo correctamente.\",\n\t\"TRANSLATE\": \"Traducir\",\n\t\"LAST_SEEN\": \"Visto por última vez\",\n\t\"AT\": \"a\",\n\t\"USERS\": \"Usuarios\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Todos\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notificar a todos en este grupo\",\n\t\"CHATS\": \"Charlas\",\n\t\"SEARCH_EMOJI\": \"Buscar emojis\",\n\t\"WRONG_FILE_TYPE\": \"Has seleccionado otro tipo de archivo. Seleccione el archivo adecuado.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Este tipo de archivo no está permitido.\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"¿Quieres eliminar esta conversación? Esta conversación se eliminará de todos tus dispositivos.\",\n\t\"GROUPS\": \"Grupos\",\n\t\"SHARED\": \"Compartido\",\n\t\"SOUND_MANAGER\": \"Gestor de sonido\",\n\t\"THEME\": \"Tema\",\n\t\"DELETE_MSG_TEXT\": \"Este mensaje se ha eliminado\",\n\t\"LOCALIZE\": \"Localizar\",\n\t\"CONVERSATION_LIST_ITEM\": \"Elemento de la lista de conversaciones\",\n\t\"DATA_ITEM\": \"Elemento de datos\",\n\t\"STATUS_INDICATOR\": \"Indicador de estado\",\n\t\"BADGE_COUNT\": \"Recuento de distintivos\",\n\t\"MESSAGE_RECEIPT\": \"Recepción del mensaje\",\n\t\"MESSAGE\": \"Mensaje\",\n\t\"RECEIPT_INFORMATION\": \"Información de recibo\",\n\t\"NO_RECIPIENT\": \"Sin destinatario\",\n\t\"NO_RECIPIENTS\": \"Sin destinatarios\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Conversaciones con mensajes\",\n\t\"CONVERSATIONS\": \"Conversaciones\",\n\t\"CONVERSATION_LIST\": \"Lista de conversaciones\",\n\t\"MESSAGES\": \"Mensajes\",\n\t\"MESSAGE_HEADER\": \"Encabezado del mensaje\",\n\t\"MESSAGE_LIST\": \"Lista de mensajes\",\n\t\"MESSAGE_COMPOSER\": \"Compositor de mensajes\",\n\t\"USERS_WITH_MESSAGES\": \"Usuarios con mensajes\",\n\t\"USER_LIST\": \"Lista de usuarios\",\n\t\"GROUP_LIST\": \"Lista de grupos\",\n\t\"GROUPS_WITH_MESSAGES\": \"Grupos con mensajes\",\n\t\"NEW__GROUP\": \"Nuevo grupo\",\n\t\"PASSWORD\": \"Contraseña\",\n\t\"CONTINUE\": \"Continuar\",\n\t\"NO_CHATS_SELECTED\": \"No se ha seleccionado ningún chat\",\n\t\"NO_USERS_SELECTED\": \"No se ha seleccionado ningún usuario\",\n\t\"NO_GROUPS_SELECTED\": \"No se ha seleccionado ningún grupo\",\n\t\"SELECT_DAY\": \"Selecciona un día\",\n\t\"SELECT_TIME\": \"Selecciona una hora\",\n\t\"NO_CALLS_SELECTED\": \"No se ha seleccionado ninguna llamada\",\n\t\"SELECT__GROUP\": \"Selecciona un grupo para empezar a enviar mensajes\",\n\t\"SELECT__USER\": \"Seleccione un usuario para iniciar la mensajería\",\n\t\"GROUP_PASSWORD_BLANK\": \"La contraseña del grupo no puede estar en blanco\",\n\t\"GROUP_NAME_BLANK\": \"El nombre del grupo no puede estar en blanco\",\n\t\"GROUP_TYPE_BLANK\": \"El tipo de grupo no puede estar en blanco\",\n\t\"DELETE_CONVERSATION\": \"¿Eliminar conversación?\",\n\t\"ADD_TO_CHAT\": \"Añadir al chat\",\n\t\"MORE\": \"Más\",\n\t\"COPY\": \"Copiar\",\n\t\"VOICE_RECORDING\": \"Grabación de voz\",\n\t\"MESSAGE_IMAGE\": \"Imagen\",\n\t\"MESSAGE_FILE\": \"Archivo\",\n\t\"MESSAGE_VIDEO\": \"Vídeo\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"CUSTOM_MESSAGE\": \"Tienes un mensaje\",\n\t\"SELECT_A_DATE\": \"Selecciona una fecha\",\n\t\"TIME_ZONE\": \"Zona horaria\",\n\t\"MEETING_SCHEDULED\": \"Se ha programado su reunión.\",\n\t\"SOMETHING_WRONG\": \"Algo salió mal. Por favor, inténtelo de nuevo.\",\n\t\"MEETING_SLOT_BOOK\": \"La franja horaria ya no está disponible. Por favor, elige otro.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Reserva un nuevo espacio\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"No hay franjas horarias disponibles para esta fecha. Por favor, prueba con otra fecha.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Por favor, prueba con una fecha diferente.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"No hay franjas horarias disponibles\",\n\t\"SCHEDULE\": \"Programar\",\n\t\"MORE_TIMES\": \"Más veces\",\n\t\"MISSED_VOICE_CALL\": \"Llamada de voz perdida\",\n\t\"MEET_WITH\": \"Reúnase con\",\n\t\"MEETING_SCHEDULER\": \"Programador de reuniones\",\n\t\"MISSED_VIDEO_CALL\": \"Videollamada perdida\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Encuesta\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Pegatina\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Documento\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Pizarra\",\n\t\"ONLINE\": \"En línea\",\n\t\"ADMINISTRATOR\": \"Administrador\",\n\t\"ADMIN\": \"Administrador\",\n\t\"MODERATOR\": \"presentador\",\n\t\"PARTICIPANT\": \"Participante\",\n\t\"PUBLIC\": \"Público\",\n\t\"PRIVATE\": \"Privada\",\n\t\"PASSWORD_PROTECTED\": \"Protegido con contraseña\",\n\t\"PRIVACY_AND_SECURITY\": \"Privacidad y seguridad\",\n\t\"PREFERENCES\": \"Preferencias\",\n\t\"MEMBERS\": \"Miembros\",\n\t\"MEMBER\": \"Miembro\",\n\t\"EDITED\": \"Editado\",\n\t\"TODAY\": \"Hoy\",\n\t\"YESTERDAY\": \"Ayer\",\n\t\"SUNDAY\": \"domingo\",\n\t\"MONDAY\": \"lunes\",\n\t\"TUESDAY\": \"martes\",\n\t\"WEDNESDAY\": \"miércoles\",\n\t\"THURSDAY\": \"jueves\",\n\t\"FRIDAY\": \"viernes\",\n\t\"SATURDAY\": \"sábado\",\n\t\"TYPING\": \"Escribiendo...\",\n\t\"IS_TYPING\": \"está escribiendo...\",\n\t\"CLOSE\": \"Cerrar\",\n\t\"ENTER_GROUP_NAME\": \"Introduzca el nombre del grupo\",\n\t\"ADD_MEMBERS\": \"Agregar miembros\",\n\t\"SEND_MESSAGE\": \"Enviar mensaje\",\n\t\"UNBLOCK_USER\": \"Desbloquear usuario\",\n\t\"BLOCK_USER\": \"Bloquear usuario\",\n\t\"DELETE_AND_EXIT\": \"Eliminar y salir\",\n\t\"LEAVE_GROUP\": \"Salir del grupo\",\n\t\"CREATE_GROUP\": \"Crear grupo\",\n\t\"SHARED_MEDIA\": \"Medios compartidos\",\n\t\"SHARED_FILE\": \"Archivo compartido\",\n\t\"VIDEO_CALL\": \"Videollamada\",\n\t\"AUDIO_CALL\": \"Llamada de audio\",\n\t\"LOADING\": \"Cargando...\",\n\t\"REPLY\": \"respuesta\",\n\t\"REPLIES\": \"respuestas\",\n\t\"AI\": \"I.A.\",\n\t\"SMART_REPLIES\": \"Respuestas inteligentes\",\n\t\"CONVERSATION_STARTER\": \"Iniciador de conversación\",\n\t\"LAUNCH\": \"Lanzar\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"ha compartido un documento colaborativo\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"ha compartido una pizarra colaborativa\",\n\t\"CREATED_WHITEBOARD\": \"Ha creado una nueva pizarra colaborativa\",\n\t\"CREATED_DOCUMENT\": \"Has creado un nuevo documento colaborativo\",\n\t\"PHOTOS\": \"Fotos\",\n\t\"VIDEOS\": \"Vídeos\",\n\t\"DOCUMENT\": \"Documento\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Has eliminado este mensaje\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Este mensaje se ha eliminado\",\n\t\"MESSAGE_IS_DELETED\": \"El mensaje está eliminado\",\n\t\"VIEW_ON_YOUTUBE\": \"Ver en Youtube\",\n\t\"SEARCH\": \"Búsqueda\",\n\t\"ERROR\": \"Error\",\n\t\"ERROR_TEXT\": \"Parece que algo salió mal. Vuelva a intentarlo.\",\n\t\"NO_GROUPS_FOUND\": \"No se ha encontrado ningún grupo\",\n\t\"NO_CHATS_FOUND\": \"No se han encontrado chats\",\n\t\"CANT__LOAD__CHATS\": \"No se pueden cargar los chats\",\n\t\"MEDIA_MESSAGE\": \"Mensaje para los medios\",\n\t\"INCOMING_AUDIO_CALL\": \"Llamada de audio entrante\",\n\t\"INCOMING_VIDEO_CALL\": \"Videollamada entrante\",\n\t\"DECLINE\": \"Declinación\",\n\t\"ACCEPT\": \"Aceptar\",\n\t\"CALL_INITIATED\": \"Llamada iniciada\",\n\t\"OUTGOING_AUDIO_CALL\": \"Llamada de audio saliente\",\n\t\"OUTGOING_VIDEO_CALL\": \"Videollamada saliente\",\n\t\"REJECTED_CALL\": \"llamada rechazada\",\n\t\"CALL_ACCEPTED\": \"Llamada aceptada\",\n\t\"JOINED\": \"se unió\",\n\t\"LEFT_THE_CALL\": \"dejó la llamada\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Llamada de audio sin respuesta\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Videollamada sin respuesta\",\n\t\"CALL_ENDED\": \"Llamada finalizada\",\n\t\"CALL_BUSY\": \"Llamada ocupada\",\n\t\"CALLING\": \"Llamando...\",\n\t\"ADD\": \"Añadir\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"No se ha encontrado ningún miembro prohibido\",\n\t\"BANNED_MEMBERS\": \"Miembros prohibidos\",\n\t\"NAME\": \"Nombre\",\n\t\"SCOPE\": \"Alcance\",\n\t\"UNBAN\": \"Desbloquear\",\n\t\"SELECT_GROUP_TYPE\": \"Seleccione el tipo de grupo\",\n\t\"ENTER_GROUP_PASSWORD\": \"Ingresa la contraseña del grupo\",\n\t\"CREATE\": \"Crear\",\n\t\"CREATE_POLL\": \"Encuesta\",\n\t\"QUESTION\": \"Pregunta\",\n\t\"ENTER_YOUR_QUESTION\": \"Ingresa tu pregunta\",\n\t\"OPTIONS\": \"Opciones\",\n\t\"ENTER_YOUR_OPTION\": \"Ingresa tu opción\",\n\t\"ADD_NEW_OPTION\": \"Añadir nueva opción\",\n\t\"VIEW_MEMBERS\": \"Ver miembros\",\n\t\"DETAILS\": \"Detalles\",\n\t\"NOTIFICATIONS\": \"Notificaciones\",\n\t\"OTHER\": \"Otros\",\n\t\"HELP\": \"Ayuda\",\n\t\"REPORT_PROBLEM\": \"Reportar un problema\",\n\t\"GROUP_MEMBERS\": \"Miembros del grupo\",\n\t\"BAN\": \"Prohibir\",\n\t\"KICK\": \"Patada\",\n\t\"PICK_YOUR_EMOJI\": \"Elige tu emoji\",\n\t\"PRIVATE_GROUP\": \"Grupo privado\",\n\t\"PROTECTED_GROUP\": \"Grupo protegido\",\n\t\"VISIT\": \"Visita\",\n\t\"ATTACH\": \"Adjuntar\",\n\t\"OPEN_WHITEBOARD\": \"Pizarra abierta\",\n\t\"ATTACH_FILE\": \"Adjuntar archivo\",\n\t\"GENERATING_REPLIES\": \"Generar respuestas\",\n\t\"ATTACH_VIDEO\": \"Adjuntar vídeo\",\n\t\"ATTACH_AUDIO\": \"Adjuntar audio\",\n\t\"ATTACH_IMAGE\": \"Adjuntar imagen\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Colabore mediante un documento\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Colabore mediante una pizarra\",\n\t\"SUGGEST_A_REPLY\": \"Sugerir una respuesta\",\n\t\"GENERATING_ICEBREAKERS\": \"Generando rompehielos\",\n\t\"EMOJI\": \"Emoji\",\n\t\"NO_REPLIES_FOUND\": \"No se han encontrado respuestas\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Introduce tu mensaje aquí\",\n\t\"NO_MESSAGES_FOUND\": \"No se ha encontrado ningún mensaje\",\n\t\"THREAD\": \"Hilo\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Documento colaborativo\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Pizarra colaborativa\",\n\t\"ADD_REACTION\": \"Añadir reacción\",\n\t\"NO_STICKERS_FOUND\": \"No se han encontrado pegatinas\",\n\t\"REPLY_TO_THREAD\": \"Responder al hilo\",\n\t\"REPLY_IN_THREAD\": \"Responder en hilo\",\n\t\"VIEW\": \"Ver\",\n\t\"DELETE_MESSAGE\": \"Eliminar mensaje\",\n\t\"EDIT_MESSAGE\": \"Editar mensaje\",\n\t\"OWNER\": \"Dueño\",\n\t\"CHANGE_SCOPE\": \"Cambiar ámbito\",\n\t\"STICKER\": \"Adhesivo\",\n\t\"LAST_ACTIVE_AT\": \"Último día activo\",\n\t\"VOICE_CALL\": \"Llamada de voz\",\n\t\"VIEW_DETAIL\": \"Ver detalle\",\n\t\"VOTES\": \"vota\",\n\t\"VOTE\": \"votar\",\n\t\"NO_VOTE\": \"Sin voto\",\n\t\"REACTED\": \"reaccionó\",\n\t\"ADDED\": \"adicional\",\n\t\"SHOW_UNSAFE_CONTENT\": \"¿Estás seguro de que quieres ver contenido no seguro?\",\n\t\"REACT_TO_MESSAGE\": \"Reaccionar a un mensaje\",\n\t\"UNBANNED\": \"no prohibido\",\n\t\"MADE\": \"hecho\",\n\t\"MISSED_AUDIO_CALL\": \"Llamada de audio perdida\",\n\t\"ENTER_YOUR_PASSWORD\": \"Introduce tu contraseña\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Abra el documento para editar el contenido de forma conjunta\",\n\t\"DOCS\": \"Documentos\",\n\t\"NO_RECORDS_FOUND\": \"No se ha encontrado ningún registro\",\n\t\"LIVE_REACTION\": \"Reacción en vivo\",\n\t\"SMILEY_PEOPLE\": \"Sonrisas y personas\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Abra la pizarra para dibujar juntos\",\n\t\"ANIMALES_NATURE\": \"Animales y naturaleza\",\n\t\"FOOD_DRINK\": \"Comida y bebida\",\n\t\"OPEN_DOCUMENT\": \"Abrir documento\",\n\t\"ACTIVITY\": \"Actividad\",\n\t\"TRAVEL_PLACES\": \"Viajes y lugares\",\n\t\"OBJECTS\": \"Objetos\",\n\t\"SYMBOLS\": \"Símbolos\",\n\t\"FLAGS\": \"Banderas\",\n\t\"READ_MORE\": \"Leer más\",\n\t\"SHOW_LESS\": \"Mostrar menos\",\n\t\"SENT\": \"Enviado\",\n\t\"SEEN\": \"Visto\",\n\t\"DELIVERED\": \"Entregado\",\n\t\"READ\": \"Leer\",\n\t\"MESSAGE_INFORMATION\": \"Información del mensaje\",\n\t\"TRANSLATE_MESSAGE\": \"Traducir mensaje\",\n\t\"TRANSLATED_MESSAGE\": \"Mensaje traducido\",\n\t\"LEFT\": \"izquierda\",\n\t\"KICKED\": \"pateado\",\n\t\"BANNED\": \"prohibido\",\n\t\"NEW_MESSAGES\": \"mensajes nuevos\",\n\t\"NEW_MESSAGE\": \"mensaje nuevo\",\n\t\"JUMP\": \"Salta\",\n\t\"SELECT_VIDEO_SOURCE\": \"Seleccione la fuente de vídeo\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Seleccione la fuente de audio de entrada\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Seleccione la fuente de audio de salida\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"ha iniciado una llamada de audio\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"ha iniciado una videollamada\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Has iniciado una llamada de audio\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Has iniciado una videollamada\",\n\t\"IGNORE\": \"Ignorar\",\n\t\"ON_ANOTHER_CALL\": \"está en otra llamada\",\n\t\"CREATING\": \"Creando\",\n\t\"AVATAR\": \"Avatar\",\n\t\"ONGOING_CALL\": \"Convocatoria en curso\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Ya estás en una llamada en curso\",\n\t\"RESIZE\": \"Redimensionar\",\n\t\"SETTINGS\": \"Ajustes\",\n\t\"ACTIONS\": \"Acciones\",\n\t\"VIEW_PROFILE\": \"Ver perfil\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Enviar mensaje de forma privada\",\n\t\"DELETE\": \"Borrar\",\n\t\"DELETE_CONFIRM\": \"¿Estás seguro de que quieres eliminarlo?\",\n\t\"DELETE_CHAT\": \"¿Eliminar este chat?\",\n\t\"SURE_TO_DELETE_CHAT\": \"¿Estás seguro de que quieres eliminar este chat? Esta acción no se puede deshacer.\",\n\t\"CANCEL\": \"Cancelar\",\n\t\"LEAVE_CONFIRM\": \"¿Estás seguro de que quieres dejar el grupo?\",\n\t\"TRANSFER_CONFIRM\": \"Eres el propietario del grupo; transfiere la propiedad a un miembro antes de abandonar el grupo\",\n\t\"ADDING\": \"Agregando...\",\n\t\"TRANSFER\": \"Traslado\",\n\t\"TRANSFER_OWNERSHIP\": \"Transferir la propiedad\",\n\t\"TRANSFERRING\": \"Transferir\",\n\t\"YES\": \"Sí\",\n\t\"NO\": \"No\",\n\t\"ANSWER\": \"Responder\",\n\t\"ADD_ANOTHER_ANSWER\": \"Añadir otra respuesta\",\n\t\"SET_THE_ANSWERS\": \"ESTABLECE LAS RESPUESTAS\",\n\t\"TRY_AGAIN\": \"Inténtalo de nuevo\",\n\t\"INVALID_GROUP_NAME\": \"Introduce un nombre válido para el grupo e inténtalo de nuevo\",\n\t\"INVALID_PASSWORD\": \"Introduce una contraseña válida para el grupo e inténtalo de nuevo\",\n\t\"INVALID_GROUP_TYPE\": \"Introduce un tipo válido para el grupo e inténtalo de nuevo\",\n\t\"WRONG_PASSWORD\": \"Introduce la contraseña correcta e inténtalo de nuevo\",\n\t\"INVALID_POLL_QUESTION\": \"Introduce la pregunta requerida antes de crear una encuesta\",\n\t\"INVALID_POLL_OPTION\": \"Ingresa la respuesta requerida antes de crear una encuesta\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"El idioma seleccionado para la traducción es similar al idioma del mensaje original\",\n\t\"LEAVE\": \"Salir\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Haga clic para iniciar la conversación\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Ubicación\",\n\t\"SHARED_LOCATION\": \"Ubicación compartida\",\n\t\"IN_A_THREAD\": \"En un hilo\",\n\t\"CALLS\": \"Convocatorias\",\n\t\"CALL_DETAILS\": \"Detalles de la llamada\",\n\t\"OFFLINE\": \"Fuera de línea\",\n\t\"POLLS\": \"Encuestas\",\n\t\"YOU\": \"Tú\",\n\t\"PRIVACY\": \"Privacidad\",\n\t\"BLOCKED_USERS\": \"Usuarios bloqueados\",\n\t\"YOU'VE_BLOCKED\": \"Has bloqueado\",\n\t\"NO_PHOTOS\": \"No hay fotos\",\n\t\"NO_VIDEOS\": \"No hay vídeos\",\n\t\"NO_DOCUMENTS\": \"Sin documentos\",\n\t\"JOIN\": \"Únete\",\n\t\"REMOVE\": \"Eliminar\",\n\t\"BLOCK\": \"Bloquear\",\n\t\"CHANGE_ROLE\": \"Cambiar rol\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Puede cambiar los roles para administrar los permisos y las responsabilidades del grupo.\",\n\t\"CHANGE_TO\": \"Cambiar a\",\n\t\"NEW_CHAT\": \"Nuevo chat\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Gracias por rellenar el formulario.\",\n\t\"HISTORY\": \"Historia\",\n\t\"CANCELLED_AUDIO_CALL\": \"Llamada de audio cancelada\",\n\t\"CANCELLED_VIDEO_CALL\": \"Videollamada cancelada\",\n\t\"REJECTED_AUDIO_CALL\": \"Llamada de audio rechazada\",\n\t\"REJECTED_VIDEO_CALL\": \"Videollamada rechazada\",\n\t\"CANCELLED_CALL\": \"Llamada cancelada\",\n\t\"UNANSWERED_CALL\": \"Llamada sin respuesta\",\n\t\"RECORDING\": \"Grabación\",\n\t\"PARTICIPANTS\": \"Participantes\",\n\t\"CALL_HISTORY\": \"Historial de llamadas\",\n\t\"NO_CALLS_FOUND\": \"No se han encontrado llamadas\",\n\t\"ONGOING_AUDIO_CALL\": \"Llamada de audio en curso\",\n\t\"ONGOING_VIDEO_CALL\": \"Videollamada en curso\",\n\t\"CALL_DETAIL\": \"Detalle de la llamada\",\n\t\"FORM\": \"Formulario\",\n\t\"CARD\": \"Tarjeta\",\n\t\"GENERATING_SUMMARY\": \"Generando un resumen\",\n\t\"CONVERSATION_SUMMARY\": \"Resumen de la conversación\",\n\t\"GENERATE_SUMMARY\": \"Generar un resumen\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"¿Cómo puedo ayudarlo con esta conversación? Por favor, hazme una pregunta y te asesoraré 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"Preguntar\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Pregúntale a los bots de IA\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"Bot de IA\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Puedes mencionar hasta 10 usuarios a la vez.\",\n\t\"ALL\": \"Todas\",\n\t\"CLICK_TO_REMOVE\": \"Haga clic para eliminar\",\n\t\"OTHERS\": \"otros\",\n\t\"AND\": \"un\",\n\t\"ASK_QUESTION\": \"Haga una pregunta\",\n\t\"ADD_OPTION\": \"Añadir opción\",\n\t\"SEND\": \"Enviar\",\n\t\"FULL_SCREEN_VIEWER\": \"Visor de pantalla completa\",\n\t\"TEXT_TRANSLATE\": \"Traductor de texto\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Parece que algo salió mal\",\n\t\"NO_SUMMARY_AVAILABLE\": \"No hay ningún resumen disponible\",\n\t\"QUESTIONS\": \"Preguntas\",\n\t\"NO_STICKERS_AVAILABLE\": \"No hay pegatinas disponibles\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"No hay sugerencias disponibles\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Crea o únete a grupos para verlos listados aquí y empezar a colaborar\",\n\t\"NO_USERS_AVAILABLE\": \"No hay usuarios disponibles\",\n\t\"NO_GROUPS_AVAILABLE\": \"No hay grupos disponibles\",\n\t\"NO_CALL_LOGS\": \"Aún no hay registros de llamadas\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Haz o recibe llamadas para ver tu historial de llamadas aquí\",\n\t\"NO_CONVERSATIONS\": \"Aún no hay conversaciones»;  \",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Inicia un nuevo chat o invita a otras personas a unirse a la conversación.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"No hay ningún miembro del grupo disponible\",\n\t\"OOPS\": \"¡UY!\",\n\t\"OOPS!\": \"¡UY!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Esperando a que los destinatarios reciban y vean el mensaje\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Añade contactos para verlos listados aquí.\",\n\t\"REACHED_MAX_LIMIT\": \"Has llegado al límite. Puedes añadir hasta 12 opciones.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Rellene todos los campos obligatorios antes de crear una encuesta\",\n\t\"NOT_SUPPORTED\": \"No se admite este tipo de mensaje\",\n\t\"NO_USERS_FOUND\": \"No se ha encontrado ningún usuario\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"No hemos podido encontrar ningún usuario que coincida con tu búsqueda. Intenta ajustar la búsqueda.\",\n\t\"SAVE\": \"Guardar\",\n\t\"RETRY\": \"Reintentar\",\n\t\"TAP_TO_START_CONVERSATION\": \"Toca para iniciar la conversación\",\n\t\"ATTACH_DOCUMENT\": \"Adjuntar documento\",\n\t\"CAMERA\": \"cámara\",\n\t\"SHARE\": \"Compartir\",\n\t\"TEXT_TRANSLATED\": \"Texto traducido\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"¿Estás seguro de que deseas eliminar\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Introduce tu mensaje aquí\",\n\t\"TAP_TO_REMOVE\": \"Pulsa para eliminar\",\n\t\"ADD_CONTACTS\": \"Agrega contactos para iniciar conversaciones y verlos listados aquí.\",\n\t\"SCOPE_CHANGE_INFO\": \"Puedes cambiar roles para gestionar los permisos y responsabilidades del grupo\",\n\t\"SOMETHING_WENT_WRONG\": \"Parece que algo salió mal.\",\n\t\"MENTION_UPTO\": \"Puedes mencionar hasta\",\n\t\"TIME\": \"tiempo\",\n\t\"TIMES\": \"veces\",\n\t\"AT_A_TIME\": \"a la vez\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Esta función requiere permisos de recursos específicos. Habilite los permisos necesarios en la configuración de su dispositivo\",\n\t\"CHAT_PRIVATELY\": \"Chatea en privado\",\n\t\"MICROPHONE_PERMISSION\": \"Para grabar un mensaje de voz, necesitamos acceder a tu micrófono. Pulsa Ajustes y activa el micrófono.\",\n\t\"CAMERA_PERMISSION\": \"No tenemos acceso a su cámara. Para habilitar el acceso, toca Configuración y activa la Cámara.\",\n\t\"ADD_OPTIONS\": \"Agregar opciones\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Documento abierto para dibujar juntos\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Abra la pizarra para dibujar juntos\",\n\t\"TEXT\": \"Texto\",\n\t\"BAN_MEMBER_CONFIRM\": \"¿Estás seguro de que deseas prohibir \",\n\t\"LOGIN\": \"Iniciar sesión\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Continuar con Google\",\n\t\"JOIN_GROUP\": \"Unirse al grupo\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"¡La contraseña del grupo es incorrecta!\",\n\t\"ENTER_PASSWORD\": \"Introducir contraseña\",\n\t\"TYPE\": \"Tipo\",\n\t\"USER_INFO\": \"Información del usuario\",\n\t\"DELETE_CHAT_TEXT\": \"Eliminar chat\",\n\t\"UNBLOCK\": \"Desbloquear\",\n\t\"UNBLOCK_CONTACT\": \"Desbloquear este contacto\",\n\t\"BLOCK_CONTACT\": \"¿Bloquear este contacto?\",\n\t\"UNBLOCK_SURE\": \"¿Estás seguro de que deseas desbloquear este contacto? Volverás a recibir mensajes de ellos.\",\n\t\"BLOCK_SURE\": \"¿Estás seguro de que deseas bloquear este contacto? No recibirás más mensajes de ellos.\",\n\t\"VOICE\": \"Voz\",\n\t\"DELETE_AND_EXIT_SURE\": \"¿Estás seguro de que deseas eliminar este chat y salir del grupo? Esta acción no se puede deshacer.\",\n\t\"TRANSFER_SURE\": \"¿Estás seguro de que deseas transferir la propiedad? Esto no se puede deshacer, y el nuevo propietario tomará el control total.\",\n\t\"GROUP_INFO\": \"Información del grupo\",\n\t\"LEAVE_GROUP_TEXT\": \"Salir de este grupo\",\n\t\"LEAVE_SURE\": \"¿Estás seguro de que deseas salir del grupo? No recibirás más mensajes de este chat.\",\n\t\"UNBAN_SURE\": \"¿Estás seguro de que deseas desbanear a este usuario?\",\n\t\"DELETE_THIS_CONVERSATION\": \"¿Eliminar esta conversación?\",\n\t\"LINK\": \"Enlace\",\n\t\"DELETE_THIS_MESSAGE\": \"¿Eliminar este mensaje?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"¿Estás seguro de que deseas eliminar este mensaje? Esta acción no se puede deshacer.\",\n\t\"APP_CREDENTIALS\": \"Credenciales de la aplicación\",\n\t\"REGION\": \"Región\",\n\t\"WRONG_TEXT\": \"Parece que algo salió mal.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Por favor, inténtalo de nuevo.\",\n\t\"BLOCKED_USER_DESC\": \"No se puede enviar un mensaje porque el usuario está bloqueado\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} añadió a ${onName} al grupo\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} se unió al grupo\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} salió del grupo\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} expulsó a ${onName} del grupo\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} prohibió a ${onName} del grupo\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} permitió a ${onName} volver al grupo\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} hizo a ${onName} ${role}\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Aún no tienes ningún sticker.\",\n\t\"meeting\": \"Reunión\",\n\t\"Flag_Message_Title\": \"Reportar un mensaje\",\n\t\"Flag_Message_Subtitle\": \"Reporta este chat si va en contra de nuestros estándares comunitarios. No le diremos a la cuenta que la denunciaste.\",\n\t\"Flag_Message_Remark_Label\": \"Motivo\",\n\t\"Flag_Message_Remark_Optional\": \"Opcional\",\n\t\"Flag_Message_Remark_Placeholder\": \"Proporcione contexto adicional para su reporte…\",\n\t\"Flag_Message_Confirm_Yes\": \"Reportar\",\n\t\"Flag_Message_Confirm_No\": \"Cancelar\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / Contenido no deseado\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Contenido explícito o inapropiado\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Comportamiento insultante o amenazante\",\n\t\"Message_List_Option_Flag_Message\": \"Reportar\",\n\t\"Flag_Error_Text\":\"Algo salió mal. Por favor, verifica e inténtalo de nuevo.\",\n\t\"Flag_Empty_Submit_Text\":\"No se puede enviar. Por favor, selecciona un motivo antes de reportar este mensaje\",\n\t\"START_REPORTING\": \"Iniciar informe\",\n\t\"LOGOUT\": \"Cerrar sesión\",\n\t\"AI_ASSISTANTS\": \"Asistentes de IA\",\n\t\"CREATE_CONVERSATION\": \"Crear conversación\",\n\t\"EDIT_MODERATION\": \"Edición fallida. Tu mensaje fue bloqueado debido a las políticas de moderación.\",\n\t\"BLOCKED_MODERATION\":\"Tu mensaje fue bloqueado debido a las políticas de moderación.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"No se encontró el historial de conversaciones\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Algo salió mal de nuestro lado. Por favor, intenta de nuevo.\",\n\t\"START_A_CHAT\": \"Comienza un chat tocando el botón 'Nueva conversación'\",\n\t\"CHAT_HISTORY\": \"Historial de chat\",\n\t\"ASK_ANYTHING\": \"Pregunta lo que sea...\",\n\t\"AI_ASSISTANT\": \"Asistente AI\",\n\t\"SEARCH_PLACEHOLDER\": \"Buscar...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Sin Resultados\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Comienza a escribir para buscar mensajes y conversaciones\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"No Se Encontraron Resultados\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"No encontramos coincidencias. Intenta con otra palabra clave.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Error al Cargar Conversaciones\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Error al Cargar Mensajes\",\n\t\"SEARCH_TRY_AGAIN\": \"Por favor, inténtalo más tarde\",\n\t\"SEARCH_SEE_MORE\": \"Ver Más\",\n\t\"SEARCH_LOADING\": \"Cargando...\",\n\t\"SEARCH_FILTER_UNREAD\": \"No leídos\",\n\t\"SEARCH_FILTER_GROUPS\": \"Grupos\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Fotos\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Videos\",\n\t\"SEARCH_FILTER_LINKS\": \"Enlaces\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Documentos\",\n\t\"SEARCH_FILTER_AUDIO\": \"Audio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Conversaciones\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Mensajes\",\n\t\"SEARCH_NO_MESSAGES\": \"No hay mensajes\",\n\t\"MESSAGE_REPORTED\": \"Mensaje reportado\",\n\t\"MARK_AS_UNREAD\": \"Marcar no leído\",\n\t\"NEW\": \"Nuevo\",\n\t\"INSERT_LINK\": \"Insertar enlace\",\n\t\"EDIT_LINK\": \"Editar enlace\",\n\t\"LINK_TEXT\": \"Texto del enlace\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Insertar\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"No puedes enviar mensajes a este grupo porque ya no eres miembro.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/fr/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Accepter\",\n\t\"ACTIONS\": \"Actions\",\n\t\"ACTIVITY\": \"Activité\",\n\t\"ADD\": \"Ajouter\",\n\t\"ADDED\": \"ajoutée\",\n\t\"ADDING\": \"Ajouter...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Ajouter une autre réponse\",\n\t\"ADD_CONTACTS\": \"Ajoutez des contacts pour commencer des conversations et les voir listés ici.\",\n\t\"ADD_MEMBERS\": \"Ajouter des membres\",\n\t\"ADD_NEW_OPTION\": \"Ajouter une nouvelle option\",\n\t\"ADD_OPTION\": \"Ajouter une option\",\n\t\"ADD_OPTIONS\": \"Ajouter des options\",\n\t\"ADD_REACTION\": \"Ajouter une réaction\",\n\t\"ADD_TO_CHAT\": \"Ajouter au chat\",\n\t\"ADMIN\": \"Administrateur\",\n\t\"ADMINISTRATOR\": \"Administrateur\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Tous\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notifier tout le monde dans ce groupe\",\n\t\"AI\": \"IA\",\n\t\"ALL\": \"Tous\",\n\t\"AND\": \"un\",\n\t\"ANIMALES_NATURE\": \"Animaux et nature\",\n\t\"ANSWER\": \"Réponse\",\n\t\"APP_CREDENTIALS\": \"Identifiants de l'application\",\n\t\"ASK_QUESTION\": \"Poser une question\",\n\t\"AT\": \"à\",\n\t\"ATTACH\": \"Joindre\",\n\t\"ATTACH_AUDIO\": \"Joindre un fichier audio\",\n\t\"ATTACH_DOCUMENT\": \"Joindre un document\",\n\t\"ATTACH_FILE\": \"Joindre un fichier\",\n\t\"ATTACH_IMAGE\": \"Joindre une image\",\n\t\"ATTACH_VIDEO\": \"Joindre une vidéo\",\n\t\"AT_A_TIME\": \"à la fois\",\n\t\"AUDIO_CALL\": \"Appel audio\",\n\t\"AVATAR\": \"Avatar\",\n\t\"BADGE_COUNT\": \"Nombre de badges\",\n\t\"BAN\": \"Interdiction\",\n\t\"BANNED\": \"interdit\",\n\t\"BANNED_MEMBERS\": \"Membres bannis\",\n\t\"BAN_MEMBER_CONFIRM\": \"Êtes-vous sûr de vouloir bannir \",\n\t\"BLOCK\": \"Bloquer\",\n\t\"BLOCKED_USERS\": \"Utilisateurs bloqués\",\n\t\"BLOCKED_USER_DESC\": \"Impossible d'envoyer un message car l'utilisateur est bloqué\",\n\t\"BLOCK_CONTACT\": \"Bloquer ce contact ?\",\n\t\"BLOCK_SURE\": \"Biztosan blokkolni szeretné ezt a kapcsolatot? Többé nem fog üzeneteket kapni tőlük.\",\n\t\"BLOCK_USER\": \"Bloquer l'utilisateur\",\n\t\"CALLING\": \"J'appelle...\",\n\t\"CALLS\": \"Appels\",\n\t\"CALL_ACCEPTED\": \"Appel accepté\",\n\t\"CALL_ANSWERED\": \"Réponse à l'appel\",\n\t\"CALL_BUSY\": \"Appelez Occupé\",\n\t\"CALL_CANCELLED\": \"Appel annulé\",\n\t\"CALL_DETAIL\": \"Détail de l'appel\",\n\t\"CALL_DETAILS\": \"Détails de l'appel\",\n\t\"CALL_ENDED\": \"Appel terminé\",\n\t\"CALL_HISTORY\": \"Historique des appels\",\n\t\"CALL_INITIATED\": \"Appel lancé\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Passez ou recevez des appels pour voir l'historique de vos appels répertorié ici\",\n\t\"CALL_REJECTED\": \"Appel rejeté\",\n\t\"CALL_UNANSWERED\": \"Appel sans réponse\",\n\t\"CAMERA\": \"caméra\",\n\t\"CAMERA_PERMISSION\": \"Nous n'avons pas accès à votre caméra. Pour activer l'accès, touchez Réglages et activez l'appareil photo.\",\n\t\"CANCEL\": \"Annuler\",\n\t\"CANCELLED_AUDIO_CALL\": \"Appel audio annulé\",\n\t\"CANCELLED_CALL\": \"Appel annulé\",\n\t\"CANCELLED_VIDEO_CALL\": \"Appel vidéo annulé\",\n\t\"CANT__LOAD__CHATS\": \"Impossible de charger les chats\",\n\t\"CARD\": \"Carte\",\n\t\"CHANGE_ROLE\": \"Changer de rôle\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Vous pouvez modifier les rôles pour gérer les autorisations et les responsabilités des groupes.\",\n\t\"CHANGE_SCOPE\": \"Modifier la portée\",\n\t\"CHANGE_TO\": \"Passez à\",\n\t\"CHATS\": \"Discussions\",\n\t\"CHAT_PRIVATELY\": \"Discutez en privé\",\n\t\"CLICK_TO_REMOVE\": \"Cliquez pour supprimer\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Cliquez pour démarrer la conversation\",\n\t\"CLOSE\": \"Fermer\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Collaborez à l'aide d'un document\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Collaborez à l'aide d'un tableau blanc\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Document collaboratif\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Tableau blanc collaboratif\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Demandez à AI Bots\",\n\t\"COMETCHAT_ASK_BOT\": \"Demandez\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"Robot IA\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Comment puis-je vous aider dans cette conversation ? Posez-moi une question et je vous conseillerai 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Entrez votre message ici\",\n\t\"CONTINUE\": \"Continuer\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Continuer avec Google\",\n\t\"CONVERSATIONS\": \"Conversations\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Démarrez une nouvelle discussion ou invitez d'autres personnes à se joindre à la conversation.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Conversations avec messages\",\n\t\"CONVERSATION_DELETED\": \"Conversation supprimée\",\n\t\"CONVERSATION_LIST\": \"Liste des conversations\",\n\t\"CONVERSATION_LIST_ITEM\": \"Élément de la liste des conversations\",\n\t\"CONVERSATION_STARTER\": \"Démarreur de conversation\",\n\t\"CONVERSATION_SUMMARY\": \"Résumé de la conversation\",\n\t\"COPY\": \"Copier\",\n\t\"CREATE\": \"Créez\",\n\t\"CREATED_DOCUMENT\": \"Vous avez créé un nouveau document collaboratif\",\n\t\"CREATED_WHITEBOARD\": \"Vous avez créé un nouveau tableau blanc collaboratif\",\n\t\"CREATE_GROUP\": \"Créer un groupe\",\n\t\"CREATE_POLL\": \"Sondage\",\n\t\"CREATING\": \"Création\",\n\t\"CUSTOM_MESSAGE\": \"Vous avez un message\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Dossier\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Lieu\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Sondage\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Autocollant\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Tableau blanc\",\n\t\"DATA_ITEM\": \"Élément de données\",\n\t\"DECLINE\": \"Déclin\",\n\t\"DELETE\": \"Supprimer\",\n\t\"DELETE_AND_EXIT\": \"Supprimer et quitter\",\n\t\"DELETE_AND_EXIT_SURE\": \"Are you sure you want to delete this chat and exit the group? This action cannot be undone.\",\n\t\"DELETE_CHAT\": \"Supprimer ce chat ?\",\n\t\"DELETE_CHAT_TEXT\": \"Supprimer le chat\",\n\t\"DELETE_CONFIRM\": \"Êtes-vous sûr de vouloir supprimer ?\",\n\t\"DELETE_CONVERSATION\": \"Supprimer la conversation ?\",\n\t\"DELETE_MESSAGE\": \"Supprimer le message\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Êtes-vous sûr de vouloir supprimer ce message ? Cette action est irréversible.\",\n\t\"DELETE_MSG_TEXT\": \"Ce message a été supprimé\",\n\t\"DELETE_THIS_CONVERSATION\": \"Supprimer cette conversation ?\",\n\t\"DELETE_THIS_MESSAGE\": \"Supprimer ce message ?\",\n\t\"DELIVERED\": \"Livré\",\n\t\"DETAILS\": \"Détails\",\n\t\"DOCS\": \"Docs\",\n\t\"DOCUMENT\": \"Document\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Ouvrez le document pour modifier le contenu ensemble\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Ouvrez le tableau blanc pour dessiner ensemble\",\n\t\"EDIT\": \"Modifier\",\n\t\"EDITED\": \"Edité\",\n\t\"EDIT_MESSAGE\": \"Modifier le message\",\n\t\"EMOJI\": \"Emoji\",\n\t\"ENTER_GROUP_NAME\": \"Entrez le nom du groupe\",\n\t\"ENTER_GROUP_PASSWORD\": \"Entrez le mot de passe du groupe\",\n\t\"ENTER_PASSWORD\": \"Entrer le mot de passe\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Entrez votre message ici\",\n\t\"ENTER_YOUR_OPTION\": \"Entrez votre option\",\n\t\"ENTER_YOUR_PASSWORD\": \"Entrez votre mot de passe\",\n\t\"ENTER_YOUR_QUESTION\": \"Entrez votre question\",\n\t\"ERROR\": \"Erreur\",\n\t\"ERROR_TEXT\": \"Il semblerait que quelque chose s'est mal passé. Veuillez réessayer.\",\n\t\"FLAGS\": \"Drapeaux\",\n\t\"FOOD_DRINK\": \"Nourriture et boisson\",\n\t\"FORM\": \"Formulaire\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Merci d'avoir rempli le formulaire.\",\n\t\"FRIDAY\": \"Vendredi\",\n\t\"FULL_SCREEN_VIEWER\": \"Visionneuse plein écran\",\n\t\"GENERATE_SUMMARY\": \"Générer un résumé\",\n\t\"GENERATING_ICEBREAKERS\": \"Génération de brise-glaces\",\n\t\"GENERATING_REPLIES\": \"Génération de réponses\",\n\t\"GENERATING_SUMMARY\": \"Génération d'un résumé\",\n\t\"GROUPS\": \"Groupes\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Créez ou rejoignez des groupes pour les voir répertoriés ici et commencez à collaborer\",\n\t\"GROUPS_WITH_MESSAGES\": \"Groupes contenant des messages\",\n\t\"GROUP_INFO\": \"Csoport információ\",\n\t\"GROUP_LIST\": \"Liste des groupes\",\n\t\"GROUP_MEMBERS\": \"Membres du groupe\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Ajoutez des contacts pour les voir répertoriés ici.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"En attente de réception et de consultation du message par les destinataires\",\n\t\"GROUP_NAME_BLANK\": \"Le nom du groupe ne peut pas être vide\",\n\t\"GROUP_PASSWORD_BLANK\": \"Le mot de passe du groupe ne peut pas être vide\",\n\t\"GROUP_TYPE_BLANK\": \"Le type de groupe ne peut pas être vide\",\n\t\"HELP\": \"Aide\",\n\t\"HISTORY\": \"L'histoire\",\n\t\"IGNORE\": \"Ignorer\",\n\t\"INCOMING_AUDIO_CALL\": \"Appel audio entrant\",\n\t\"INCOMING_CALL\": \"Appel entrant\",\n\t\"INCOMING_VIDEO_CALL\": \"Appel vidéo entrant\",\n\t\"INFO\": \"Infos\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"a lancé un appel audio\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"a lancé un appel vidéo\",\n\t\"INVALID_GROUP_NAME\": \"Entrez un nom valide pour le groupe et réessayez\",\n\t\"INVALID_GROUP_TYPE\": \"Entrez un type valide pour le groupe et réessayez\",\n\t\"INVALID_PASSWORD\": \"Entrez un mot de passe valide pour le groupe et réessayez\",\n\t\"INVALID_POLL_OPTION\": \"Veuillez saisir la réponse requise avant de créer un sondage\",\n\t\"INVALID_POLL_QUESTION\": \"Veuillez saisir la question requise avant de créer un sondage\",\n\t\"IN_A_THREAD\": \"Dans un fil\",\n\t\"IS_TYPING\": \"est en train de taper...\",\n\t\"JOIN\": \"Joignez-vous\",\n\t\"JOINED\": \"joint\",\n\t\"JOIN_GROUP\": \"Rejoindre le groupe\",\n\t\"JUMP\": \"Sauter\",\n\t\"KICK\": \"Kick\",\n\t\"KICKED\": \"coup de pied\",\n\t\"LAST_ACTIVE_AT\": \"Dernière activité\",\n\t\"LAST_SEEN\": \"Vu pour la dernière fois\",\n\t\"LAUNCH\": \"Lancement\",\n\t\"LEAVE\": \"Partir\",\n\t\"LEAVE_CONFIRM\": \"Es-tu sûr de vouloir quitter le groupe ?\",\n\t\"LEAVE_GROUP\": \"Quitter le groupe\",\n\t\"LEAVE_GROUP_TEXT\": \"Kilép ebből a csoportból\",\n\t\"LEAVE_SURE\": \"Biztosan ki akar lépni a csoportból? Többé nem fog üzeneteket kapni ebből a csevegésből.\",\n\t\"LEFT\": \"parti\",\n\t\"LEFT_THE_CALL\": \"a quitté l'appel\",\n\t\"LINK\": \"Lien\",\n\t\"LIVE_REACTION\": \"Réaction en direct\",\n\t\"LOADING\": \"Chargement en cours...\",\n\t\"LOCALIZE\": \"Localiser\",\n\t\"LOGIN\": \"Se connecter\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Il semblerait que quelque chose s'est mal passé\",\n\t\"MADE\": \"confectionné\",\n\t\"MEDIA_MESSAGE\": \"Message aux médias\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Réservez une nouvelle machine à sous\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Aucun créneau horaire n'est disponible pour cette date. Veuillez essayer une autre date.\",\n\t\"MEETING_SCHEDULED\": \"Votre réunion a été programmée.\",\n\t\"MEETING_SCHEDULER\": \"Planificateur de réunions\",\n\t\"MEETING_SLOT_BOOK\": \"Le créneau horaire n'est plus disponible. Veuillez en choisir un autre.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Veuillez essayer une autre date.\",\n\t\"MEET_WITH\": \"Rencontrez\",\n\t\"MEMBER\": \"Membre\",\n\t\"MEMBERS\": \"Membres\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Vous pouvez mentionner jusqu'à 10 utilisateurs à la fois.\",\n\t\"MENTION_UPTO\": \"Vous pouvez citer jusqu'à\",\n\t\"MESSAGE\": \"Message\",\n\t\"MESSAGES\": \"Messages\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"MESSAGE_COMPOSER\": \"Compositeur de messages\",\n\t\"MESSAGE_COPIED\": \"Message copié dans le presse-papiers.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Le message a été supprimé avec succès.\",\n\t\"MESSAGE_EDITED\": \"Le message a été correctement mis à jour.\",\n\t\"MESSAGE_FILE\": \"Dossier\",\n\t\"MESSAGE_HEADER\": \"En-tête du message\",\n\t\"MESSAGE_IMAGE\": \"Photo\",\n\t\"MESSAGE_INFORMATION\": \"Informations sur le message\",\n\t\"MESSAGE_IS_DELETED\": \"Le message est supprimé\",\n\t\"MESSAGE_LIST\": \"Liste des messages\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} a ajouté ${onName} au groupe\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} a banni ${onName} du groupe\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} a rejoint le groupe\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} a expulsé ${onName} du groupe\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} a quitté le groupe\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} a promu ${onName} au rang de ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} a levé le bannissement de ${onName}\",\n\t\"MESSAGE_PRIVATELY\": \"Message privé\",\n\t\"MESSAGE_RECEIPT\": \"Réception du message\",\n\t\"MESSAGE_TRANSLATED\": \"Le message a été traduit avec succès.\",\n\t\"MESSAGE_VIDEO\": \"Vidéo\",\n\t\"MICROPHONE_PERMISSION\": \"Pour enregistrer un message vocal, nous avons besoin d'accéder à votre microphone. Appuyez sur Réglages et activez le microphone.\",\n\t\"MISSED_AUDIO_CALL\": \"Appel audio manqué\",\n\t\"MISSED_CALL\": \"Appel manqué\",\n\t\"MISSED_VIDEO_CALL\": \"Appel vidéo manqué\",\n\t\"MISSED_VOICE_CALL\": \"Appel vocal manqué\",\n\t\"MODERATOR\": \"présentateur\",\n\t\"MONDAY\": \"Lundi\",\n\t\"MORE\": \"Plus\",\n\t\"MORE_TIMES\": \"Plus de fois\",\n\t\"NAME\": \"Nom\",\n\t\"NEW_CHAT\": \"Nouveau chat\",\n\t\"NEW_MESSAGE\": \"nouveau message\",\n\t\"NEW_MESSAGES\": \"nouveaux messages\",\n\t\"NEW__GROUP\": \"Nouveau groupe\",\n\t\"NO\": \"Non\",\n\t\"NOTIFICATIONS\": \"Notifications\",\n\t\"NOT_SUPPORTED\": \"Ce type de message n'est pas pris en charge\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Aucun membre banni n'a été trouvé\",\n\t\"NO_CALLS_FOUND\": \"Aucun appel trouvé\",\n\t\"NO_CALLS_SELECTED\": \"Aucun appel sélectionné\",\n\t\"NO_CALL_LOGS\": \"Aucun journal d'appels pour le moment\",\n\t\"NO_CHATS_FOUND\": \"Aucune discussion trouvée\",\n\t\"NO_CHATS_SELECTED\": \"Aucune discussion sélectionnée\",\n\t\"NO_CONVERSATIONS\": \"Aucune conversation pour le moment » ;  \",\n\t\"NO_DOCUMENTS\": \"Aucun document\",\n\t\"NO_GROUPS_AVAILABLE\": \"Aucun groupe disponible\",\n\t\"NO_GROUPS_FOUND\": \"Aucun groupe n'a été trouvé\",\n\t\"NO_GROUPS_SELECTED\": \"Aucun groupe sélectionné\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Aucun membre du groupe disponible\",\n\t\"NO_MESSAGES_FOUND\": \"Aucun message n'a été trouvé\",\n\t\"NO_PHOTOS\": \"Pas de photos\",\n\t\"NO_RECIPIENT\": \"Aucun destinataire\",\n\t\"NO_RECIPIENTS\": \"Aucun bénéficiaire\",\n\t\"NO_RECORDS_FOUND\": \"Aucun enregistrement n'a été trouvé\",\n\t\"NO_REPLIES_FOUND\": \"Aucune réponse trouvée\",\n\t\"NO_STICKERS_AVAILABLE\": \"Aucun autocollant disponible\",\n\t\"NO_STICKERS_FOUND\": \"Aucun autocollant n'a été trouvé\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Aucune suggestion disponible\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Aucun résumé disponible\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Aucun créneau horaire disponible\",\n\t\"NO_USERS_AVAILABLE\": \"Aucun utilisateur disponible\",\n\t\"NO_USERS_FOUND\": \"Aucun utilisateur n'a été trouvé\",\n\t\"NO_USERS_SELECTED\": \"Aucun utilisateur sélectionné\",\n\t\"NO_VIDEOS\": \"Pas de vidéos\",\n\t\"NO_VOTE\": \"Pas de vote\",\n\t\"OBJECTS\": \"Objets\",\n\t\"OFFLINE\": \"Hors ligne\",\n\t\"ONGOING_AUDIO_CALL\": \"Appel audio en cours\",\n\t\"ONGOING_CALL\": \"Appel en cours\",\n\t\"ONGOING_VIDEO_CALL\": \"Appel vidéo en cours\",\n\t\"ONLINE\": \"En ligne\",\n\t\"ON_ANOTHER_CALL\": \"est sur un autre appel\",\n\t\"OOPS\": \"OUPS !\",\n\t\"OOPS!\": \"OUPS !\",\n\t\"OPEN_DOCUMENT\": \"Ouvrir le document\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Ouvrir un document pour dessiner ensemble\",\n\t\"OPEN_WHITEBOARD\": \"Tableau blanc ouvert\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Ouvrez le tableau blanc pour dessiner ensemble\",\n\t\"OPTIONS\": \"Options\",\n\t\"OTHER\": \"Autres\",\n\t\"OTHERS\": \"autres\",\n\t\"OUTGOING_AUDIO_CALL\": \"Appel audio sortant\",\n\t\"OUTGOING_CALL\": \"Appel sortant\",\n\t\"OUTGOING_VIDEO_CALL\": \"Appel vidéo sortant\",\n\t\"OWNER\": \"Propriétaire\",\n\t\"PARTICIPANT\": \"Participant\",\n\t\"PARTICIPANTS\": \"Les participants\",\n\t\"PASSWORD\": \"Mot de passe\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Le mot de passe pour le groupe est incorrect !\",\n\t\"PASSWORD_PROTECTED\": \"Protection par mot de passe\",\n\t\"PHOTOS\": \"Des photos\",\n\t\"PICK_YOUR_EMOJI\": \"Choisissez votre emoji\",\n\t\"POLLS\": \"Sondages\",\n\t\"PREFERENCES\": \"Préférences\",\n\t\"PRIVACY\": \"Confidentialité\",\n\t\"PRIVACY_AND_SECURITY\": \"Confidentialité et sécurité\",\n\t\"PRIVATE\": \"Privé\",\n\t\"PRIVATE_GROUP\": \"Groupe privé\",\n\t\"PROTECTED_GROUP\": \"Groupe protégé\",\n\t\"PUBLIC\": \"Publique\",\n\t\"QUESTION\": \"Question\",\n\t\"QUESTIONS\": \"Des questions\",\n\t\"REACHED_MAX_LIMIT\": \"Tu as atteint la limite. Vous pouvez ajouter jusqu'à 12 options.\",\n\t\"REACT\": \"Réagir\",\n\t\"REACTED\": \"réagissait\",\n\t\"REACT_TO_MESSAGE\": \"Réagir à un message\",\n\t\"READ\": \"Lisez\",\n\t\"READ_MORE\": \"En savoir plus\",\n\t\"RECEIPT_INFORMATION\": \"Informations sur le reçu\",\n\t\"RECORDING\": \"Enregistrement\",\n\t\"REGION\": \"Région\",\n\t\"REJECTED_AUDIO_CALL\": \"Appel audio rejeté\",\n\t\"REJECTED_CALL\": \"appel rejeté\",\n\t\"REJECTED_VIDEO_CALL\": \"Appel vidéo rejeté\",\n\t\"REMOVE\": \"Supprimer\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Êtes-vous sûr de vouloir supprimer\",\n\t\"REPLIES\": \"réponses\",\n\t\"REPLY\": \"répondre\",\n\t\"REPLY_IN_THREAD\": \"Répondre dans le fil\",\n\t\"REPLY_TO_THREAD\": \"Répondre au fil\",\n\t\"REPORT_PROBLEM\": \"Signaler un problème\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Veuillez remplir tous les champs obligatoires avant de créer un sondage\",\n\t\"RESIZE\": \"Redimensionner\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Cette fonctionnalité nécessite des autorisations de ressources spécifiques. Veuillez activer les autorisations nécessaires dans les paramètres de votre appareil\",\n\t\"RETRY\": \"Réessayer\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"La langue sélectionnée pour la traduction est similaire à la langue du message d'origine\",\n\t\"SATURDAY\": \"samedi\",\n\t\"SAVE\": \"Enregistrer\",\n\t\"SCHEDULE\": \"Calendrier\",\n\t\"SCOPE\": \"Portée\",\n\t\"SCOPE_CHANGE_INFO\": \"Vous pouvez modifier les rôles pour gérer les autorisations et les responsabilités du groupe\",\n\t\"SEARCH\": \"Rechercher\",\n\t\"SEARCH_EMOJI\": \"Rechercher des emoji\",\n\t\"SEEN\": \"Vu\",\n\t\"SELECT_A_DATE\": \"Sélectionnez une date\",\n\t\"SELECT_DAY\": \"Sélectionnez un jour\",\n\t\"SELECT_GROUP_TYPE\": \"Sélectionnez le type de groupe\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Sélectionnez la source audio d'entrée\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Sélectionnez la source audio de sortie\",\n\t\"SELECT_TIME\": \"Sélectionnez une heure\",\n\t\"SELECT_VIDEO_SOURCE\": \"Sélectionnez la source vidéo\",\n\t\"SELECT__GROUP\": \"Sélectionnez un groupe pour commencer à envoyer des messages\",\n\t\"SELECT__USER\": \"Sélectionnez un utilisateur pour commencer à envoyer des messages\",\n\t\"SEND\": \"Envoyer\",\n\t\"SEND_MESSAGE\": \"Envoyer un message\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Envoyer un message en privé\",\n\t\"SENT\": \"Envoyé\",\n\t\"SETTINGS\": \"Réglages\",\n\t\"SET_THE_ANSWERS\": \"DÉFINISSEZ LES RÉPONSES\",\n\t\"SHARE\": \"PARTAGEZ\",\n\t\"SHARED\": \"Partagé\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"a partagé un document collaboratif\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"a partagé un tableau blanc collaboratif\",\n\t\"SHARED_FILE\": \"Fichier partagé\",\n\t\"SHARED_LOCATION\": \"Emplacement partagé\",\n\t\"SHARED_MEDIA\": \"Médias partagés\",\n\t\"SHOW_LESS\": \"Afficher moins\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Êtes-vous sûr de vouloir voir du contenu non sécurisé ?\",\n\t\"SMART_REPLIES\": \"Réponses intelligentes\",\n\t\"SMILEY_PEOPLE\": \"Smileys et personnages\",\n\t\"SOMETHING_WENT_WRONG\": \"Il semble que quelque chose se soit mal passé.\",\n\t\"SOMETHING_WRONG\": \"Quelque chose s'est mal passé. Veuillez réessayer.\",\n\t\"SOUND_MANAGER\": \"Gestionnaire de sons\",\n\t\"STATUS_INDICATOR\": \"Indicateur d'état\",\n\t\"STICKER\": \"Autocollant\",\n\t\"SUGGEST_A_REPLY\": \"Suggérer une réponse\",\n\t\"SUNDAY\": \"dimanche\",\n\t\"SURE_TO_DELETE_CHAT\": \"Êtes-vous sûr de vouloir supprimer cette discussion ? Cette action ne peut pas être annulée.\",\n\t\"SYMBOLS\": \"Symboles\",\n\t\"TAP_TO_REMOVE\": \"Touchez pour supprimer\",\n\t\"TAP_TO_START_CONVERSATION\": \"Appuyez pour démarrer la conversation\",\n\t\"TEXT\": \"Texte\",\n\t\"TEXT_TRANSLATE\": \"Traduire du texte\",\n\t\"TEXT_TRANSLATED\": \"Texte traduit\",\n\t\"THEME\": \"Thème\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Ce message a été supprimé\",\n\t\"THREAD\": \"fil\",\n\t\"THURSDAY\": \"Jeudi\",\n\t\"TIME\": \"temps\",\n\t\"TIMES\": \"temps\",\n\t\"TIME_ZONE\": \"Fuseau horaire\",\n\t\"TODAY\": \"Aujourd'hui\",\n\t\"TRANSFER\": \"Transfert\",\n\t\"TRANSFERRING\": \"Transférer\",\n\t\"TRANSFER_CONFIRM\": \"Vous êtes le propriétaire du groupe ; veuillez transférer la propriété à un membre avant de quitter le groupe\",\n\t\"TRANSFER_OWNERSHIP\": \"Transférer la propriété\",\n\t\"TRANSFER_SURE\": \"Biztosan át akarja adni a tulajdonjogot? Ezt nem lehet visszavonni, és az új tulajdonos teljes ellenőrzést fog kapni.\",\n\t\"TRANSLATE\": \"Traduire\",\n\t\"TRANSLATED_MESSAGE\": \"Message traduit\",\n\t\"TRANSLATE_MESSAGE\": \"Traduire le message\",\n\t\"TRAVEL_PLACES\": \"Voyages et lieux\",\n\t\"TRY_AGAIN\": \"Essayez à nouveau\",\n\t\"TUESDAY\": \"Mardi\",\n\t\"TYPE\": \"Type\",\n\t\"TYPING\": \"Dactylographie...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Appel audio sans réponse\",\n\t\"UNANSWERED_CALL\": \"Appel sans réponse\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Appel vidéo sans réponse\",\n\t\"UNBAN\": \"Unban\",\n\t\"UNBANNED\": \"non banni\",\n\t\"UNBAN_SURE\": \"Biztosan fel akarja oldani a tiltást erről a felhasználóról?\",\n\t\"UNBLOCK\": \"Débloquer\",\n\t\"UNBLOCK_CONTACT\": \"Débloquer ce contact\",\n\t\"UNBLOCK_SURE\": \"Êtes-vous sûr de vouloir débloquer ce contact ? Vous recommencerez à recevoir ses messages.\",\n\t\"UNBLOCK_USER\": \"Débloquer un utilisateur\",\n\t\"USERS\": \"Les utilisateurs\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Nous n'avons trouvé aucun utilisateur correspondant à votre recherche. Essayez d'ajuster votre recherche.\",\n\t\"USERS_WITH_MESSAGES\": \"Utilisateurs avec messages\",\n\t\"USER_INFO\": \"Infos utilisateur\",\n\t\"USER_LIST\": \"Liste des utilisateurs\",\n\t\"VIDEOS\": \"Vidéos\",\n\t\"VIDEO_CALL\": \"Appel vidéo\",\n\t\"VIEW\": \"Afficher\",\n\t\"VIEW_DETAIL\": \"Afficher les détails\",\n\t\"VIEW_MEMBERS\": \"Afficher les membres\",\n\t\"VIEW_ON_YOUTUBE\": \"Voir sur Youtube\",\n\t\"VIEW_PROFILE\": \"Afficher le profil\",\n\t\"VISIT\": \"Visitez\",\n\t\"VOICE\": \"Hang\",\n\t\"VOICE_CALL\": \"Appel vocal\",\n\t\"VOICE_RECORDING\": \"Enregistrement vocal\",\n\t\"VOTE\": \"vote\",\n\t\"VOTES\": \"votes\",\n\t\"WEDNESDAY\": \"Mercredi\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Désirez-vous supprimer cette conversation ? Cette conversation sera supprimée de tous vos appareils.\",\n\t\"WRONG_FILE_TYPE\": \"Vous avez sélectionné un autre type de fichier. Choisissez le fichier approprié.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Ce type de fichier n'est pas autorisé.\",\n\t\"WRONG_PASSWORD\": \"Entrez le mot de passe correct et réessayez\",\n\t\"WRONG_TEXT\": \"On dirait que quelque chose a mal tourné.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Veuillez réessayer.\",\n\t\"YES\": \"Oui\",\n\t\"YESTERDAY\": \"Hier\",\n\t\"YOU\": \"Vous\",\n\t\"YOU'VE_BLOCKED\": \"Vous avez bloqué\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Vous êtes déjà inscrit à un appel en cours\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Vous avez supprimé ce message\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Vous n’avez pas encore d’autocollants.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Vous avez lancé un appel audio\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Vous avez lancé un appel vidéo\",\n\t\"meeting\": \"Réunion\",\n\t\"Flag_Message_Title\": \"Signaler un message\",\n\t\"Flag_Message_Subtitle\": \"Signalez cette conversation si elle va à l’encontre de nos normes communautaires. Nous n’informerons pas le compte que vous l’avez signalé.\",\n\t\"Flag_Message_Remark_Label\": \"Raison\",\n\t\"Flag_Message_Remark_Optional\": \"Optionnel\",\n\t\"Flag_Message_Remark_Placeholder\": \"Fournissez un contexte supplémentaire pour votre signalement…\",\n\t\"Flag_Message_Confirm_Yes\": \"Signaler\",\n\t\"Flag_Message_Confirm_No\": \"Annuler\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / contenu indésirable\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Contenu explicite ou inapproprié\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Comportement insultant ou menaçant\",\n\t\"Message_List_Option_Flag_Message\": \"Signaler\",\n  \"Flag_Error_Text\":\"Une erreur s’est produite. Veuillez vérifier et réessayer.\",\n\t\"Flag_Empty_Submit_Text\":\"Impossible d’envoyer. Veuillez sélectionner une raison avant de signaler ce message.\",\n\t\"START_REPORTING\": \"Commencer le rapport\",\n\t\"LOGOUT\": \"Se déconnecter\",\n\t\"AI_ASSISTANTS\": \"Assistants IA\",\n\t\"CREATE_CONVERSATION\": \"Créer une conversation\",\n\t\"EDIT_MODERATION\": \"Édition échouée. Votre message a été bloqué en raison des politiques de modération\",\n  \"BLOCKED_MODERATION\":\"Votre message a été bloqué en raison des politiques de modération.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Aucun historique de conversation trouvé\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Quelque chose a mal tourné de notre côté. Veuillez réessayer\",\n\t\"START_A_CHAT\": \"Commencez un chat en appuyant sur le bouton 'Nouvelle conversation'\",\n\t\"CHAT_HISTORY\": \"Historique des chats\",\n\t\"ASK_ANYTHING\": \"Demandez n'importe quoi...\",\n\t\"AI_ASSISTANT\": \"Assistant IA\",\n\t\"SEARCH_PLACEHOLDER\": \"Rechercher...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Aucun Résultat\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Commencez à taper pour rechercher des messages et des conversations\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Aucun Résultat Trouvé\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Aucune correspondance trouvée. Essayez un autre mot-clé.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Erreur lors du Chargement des Conversations\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Erreur lors du Chargement des Messages\",\n\t\"SEARCH_TRY_AGAIN\": \"Veuillez réessayer plus tard\",\n\t\"SEARCH_SEE_MORE\": \"Voir Plus\",\n\t\"SEARCH_LOADING\": \"Chargement...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Non lus\",\n\t\"SEARCH_FILTER_GROUPS\": \"Groupes\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Photos\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Vidéos\",\n\t\"SEARCH_FILTER_LINKS\": \"Liens\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Documents\",\n\t\"SEARCH_FILTER_AUDIO\": \"Audio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Conversations\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Messages\",\n\t\"SEARCH_NO_MESSAGES\": \"Aucun message\",\n\t\"MESSAGE_REPORTED\": \"Message signalé\",\n\t\"MARK_AS_UNREAD\": \"Marquer non lu\",\n\t\"NEW\": \"Nouveau\",\n\t\"INSERT_LINK\": \"Insérer un lien\",\n\t\"EDIT_LINK\": \"Modifier le lien\",\n\t\"LINK_TEXT\": \"Texte du lien\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Insérer\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Vous ne pouvez pas envoyer de message à ce groupe car vous n'en êtes plus membre.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/hi/translation.json",
    "content": "{\n\t\"INCOMING_CALL\": \"इनकमिंग कॉल\",\n\t\"OUTGOING_CALL\": \"आउटगोइंग कॉल\",\n\t\"CALL_REJECTED\": \"कॉल अस्वीकृत\",\n\t\"CALL_ANSWERED\": \"कॉल का जवाब दिया गया\",\n\t\"CALL_CANCELLED\": \"कॉल कैंसिल किया गया\",\n\t\"MISSED_CALL\": \"मिस्ड कॉल\",\n\t\"CALL_UNANSWERED\": \"कॉल अनुत्तरित\",\n\t\"INFO\": \"जानकारी\",\n\t\"REACT\": \"रिएक्ट\",\n\t\"EDIT\": \"संपादित करें\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"सभी\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"इस समूह में सभी को सूचित करें\",\n\t\"CONVERSATION_DELETED\":\"बातचीत मिटाई गई\",\n\t\"MESSAGE_COPIED\": \"संदेश क्लिपबोर्ड पर कॉपी किया गया।\",\n\t\"MESSAGE_EDITED\": \"संदेश सफलतापूर्वक अपडेट किया गया।\",\n\t\"MESSAGE_DELETED_TEXT\": \"संदेश सफलतापूर्वक मिटाया गया।\",\n\t\"MESSAGE_TRANSLATED\": \"संदेश का सफलतापूर्वक अनुवाद किया गया।\",\n\t\"MESSAGE_PRIVATELY\": \"निजी तौर पर संदेश\",\n\t\"TRANSLATE\": \"अनुवाद करें\",\n\t\"USERS\": \"यूज़र\",\n\t\"CHATS\": \"चैट्स\",\n\t\"LAST_SEEN\": \"पिछली बार देखा गया\",\n\t\"AT\": \"पर\",\n\t\"SEARCH_EMOJI\": \"सर्च इमोजी\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"क्या आप इस वार्तालाप को मिटाना चाहेंगे? यह वार्तालाप आपके सभी डिवाइस से हटा दिया जाएगा।\",\n\t\"GROUPS\": \"ग्रुप्स\",\n\t\"WRONG_FILE_TYPE\": \"आपने एक अलग प्रकार की फ़ाइल चुनी है। कृपया उपयुक्त फ़ाइल चुनें।\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"इस फ़ाइल प्रकार की अनुमति नहीं है।\",\n\t\"SHARED\": \"शेयर्ड\",\n\t\"SOUND_MANAGER\": \"साउंड मैनेजर\",\n\t\"THEME\": \"थीम\",\n\t\"DELETE_MSG_TEXT\": \"यह संदेश हटा दिया गया था\",\n\t\"LOCALIZE\": \"लोकलाइज़ करें\",\n\t\"CONVERSATION_LIST_ITEM\": \"वार्तालाप सूची आइटम\",\n\t\"DATA_ITEM\": \"डेटा आइटम\",\n\t\"STATUS_INDICATOR\": \"स्टेटस इंडिकेटर\",\n\t\"BADGE_COUNT\": \"बैज काउंट\",\n\t\"MESSAGE_RECEIPT\": \"संदेश रसीद\",\n\t\"MESSAGE\": \"सन्देश\",\n\t\"RECEIPT_INFORMATION\": \"रसीद की जानकारी\",\n\t\"NO_RECIPIENT\": \"कोई प्राप्तकर्ता नहीं\",\n\t\"NO_RECIPIENTS\": \"कोई प्राप्तकर्ता नहीं\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"संदेशों के साथ वार्तालाप\",\n\t\"CONVERSATIONS\": \"वार्तालाप\",\n\t\"CONVERSATION_LIST\": \"वार्तालाप सूची\",\n\t\"MESSAGES\": \"सन्देश\",\n\t\"MESSAGE_HEADER\": \"संदेश शीर्षलेख\",\n\t\"MESSAGE_LIST\": \"संदेश सूची\",\n\t\"MESSAGE_COMPOSER\": \"संदेश कंपोज़र\",\n\t\"USERS_WITH_MESSAGES\": \"संदेश वाले उपयोगकर्ता\",\n\t\"USER_LIST\": \"यूज़र लिस्ट\",\n\t\"GROUP_LIST\": \"समूह सूची\",\n\t\"GROUPS_WITH_MESSAGES\": \"संदेशों के साथ समूह\",\n\t\"NEW__GROUP\": \"नया समूह\",\n\t\"PASSWORD\": \"पासवर्ड\",\n\t\"CONTINUE\": \"जारी रखें\",\n\t\"NO_CHATS_SELECTED\": \"कोई चैट नहीं चुनी गई\",\n\t\"NO_USERS_SELECTED\": \"कोई उपयोगकर्ता नहीं चुना गया\",\n\t\"NO_GROUPS_SELECTED\": \"कोई समूह नहीं चुना गया\",\n\t\"SELECT_DAY\": \"एक दिन चुनें\",\n\t\"SELECT_TIME\": \"एक समय चुनें\",\n\t\"NO_CALLS_SELECTED\": \"कोई कॉल नहीं चुनी गई\",\n\t\"SELECT__GROUP\": \"मैसेजिंग शुरू करने के लिए ग्रुप चुनें\",\n\t\"SELECT__USER\": \"मैसेजिंग शुरू करने के लिए एक उपयोगकर्ता का चयन करें\",\n\t\"GROUP_PASSWORD_BLANK\": \"ग्रुप पासवर्ड खाली नहीं हो सकता\",\n\t\"GROUP_NAME_BLANK\": \"समूह का नाम रिक्त नहीं हो सकता\",\n\t\"GROUP_TYPE_BLANK\": \"समूह का प्रकार रिक्त नहीं हो सकता\",\n\t\"DELETE_CONVERSATION\": \"बातचीत मिटाएँ?\",\n\t\"ADD_TO_CHAT\": \"चैट में जोड़ें\",\n\t\"MORE\": \"ज़्यादा\",\n\t\"COPY\": \"कॉपी करें\",\n\t\"VOICE_RECORDING\": \"वॉइस रिकॉर्डिंग\",\n\t\"MESSAGE_IMAGE\": \"छवि\",\n\t\"MESSAGE_FILE\": \"फ़ाइल\",\n\t\"MESSAGE_VIDEO\": \"वीडियो\",\n\t\"MESSAGE_AUDIO\": \"ऑडियो\",\n\t\"CUSTOM_MESSAGE\": \"आपके पास एक संदेश है\",\n\t\"SELECT_A_DATE\": \"एक तारीख चुनें\",\n\t\"TIME_ZONE\": \"टाइम ज़ोन\",\n\t\"MEETING_SCHEDULED\": \"आपकी मीटिंग शेड्यूल कर दी गई है।\",\n\t\"SOMETHING_WRONG\": \"कुछ गड़बड़ हो गई। कृपया फिर से कोशिश करें।\",\n\t\"MEETING_SLOT_BOOK\": \"टाइम स्लॉट अब उपलब्ध नहीं है। कृपया कोई दूसरा चुनें।\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"नया स्लॉट बुक करें\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"इस तिथि के लिए कोई समय स्लॉट उपलब्ध नहीं है। कृपया कोई और तारीख आज़माएँ।\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"कृपया कोई दूसरी तारीख आज़माएँ।\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"कोई टाइम स्लॉट उपलब्ध नहीं\",\n\t\"SCHEDULE\": \"शेड्यूल\",\n\t\"MORE_TIMES\": \"ज़्यादा बार\",\n\t\"MISSED_VOICE_CALL\": \"मिस्ड वॉइस कॉल\",\n\t\"MEET_WITH\": \"के साथ मिलें\",\n\t\"MEETING_SCHEDULER\": \"मीटिंग शेड्यूलर\",\n\t\"MISSED_VIDEO_CALL\": \"मिस्ड वीडियो कॉल\",\n\t\"CUSTOM_MESSAGE_POLL\": \"पोल\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"स्टिकर\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"दस्तावेज़\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"व्हाइटबोर्ड\",\n\t\"ONLINE\": \"ऑनलाइन\",\n\t\"ADMINISTRATOR\": \"प्रशासक\",\n\t\"ADMIN\": \"एडमिन\",\n\t\"MODERATOR\": \"प्रस्तुतकर्ता\",\n\t\"PARTICIPANT\": \"प्रतिभागी\",\n\t\"PUBLIC\": \"पब्लिक\",\n\t\"PRIVATE\": \"निजी\",\n\t\"PASSWORD_PROTECTED\": \"पासवर्ड प्रोटेक्टेड\",\n\t\"PRIVACY_AND_SECURITY\": \"गोपनीयता और सुरक्षा\",\n\t\"PREFERENCES\": \"प्राथमिकताएं\",\n\t\"MEMBERS\": \"सदस्य\",\n\t\"MEMBER\": \"सदस्य\",\n\t\"EDITED\": \"संपादित किया गया\",\n\t\"TODAY\": \"आज\",\n\t\"YESTERDAY\": \"कल\",\n\t\"SUNDAY\": \"रविवार\",\n\t\"MONDAY\": \"सोमवार\",\n\t\"TUESDAY\": \"मंगलवार\",\n\t\"WEDNESDAY\": \"बुधवार\",\n\t\"THURSDAY\": \"गुरुवार\",\n\t\"FRIDAY\": \"शुक्रवार\",\n\t\"SATURDAY\": \"शनिवार\",\n\t\"TYPING\": \"टाइप कर रहा है...\",\n\t\"IS_TYPING\": \"टाइप कर रहा है...\",\n\t\"CLOSE\": \"बंद करें\",\n\t\"ENTER_GROUP_NAME\": \"ग्रुप का नाम दर्ज करें\",\n\t\"ADD_MEMBERS\": \"सदस्य जोड़ें\",\n\t\"SEND_MESSAGE\": \"सन्देश भेजो\",\n\t\"UNBLOCK_USER\": \"यूज़र को अनब्लॉक करें\",\n\t\"BLOCK_USER\": \"उपयोगकर्ता को ब्लॉक करें\",\n\t\"DELETE_AND_EXIT\": \"हटाएँ और बाहर निकलें\",\n\t\"LEAVE_GROUP\": \"लीव ग्रुप\",\n\t\"CREATE_GROUP\": \"ग्रुप बनाएं\",\n\t\"SHARED_MEDIA\": \"शेयर्ड मीडिया\",\n\t\"SHARED_FILE\": \"शेयर की गई फ़ाइल\",\n\t\"VIDEO_CALL\": \"वीडियो कॉल\",\n\t\"AUDIO_CALL\": \"ऑडियो कॉल\",\n\t\"LOADING\": \"लोड हो रहा है...\",\n\t\"REPLY\": \"उत्तर दें\",\n\t\"REPLIES\": \"उत्तर\",\n\t\"AI\": \"एआई\",\n\t\"SMART_REPLIES\": \"स्मार्ट जवाब\",\n\t\"CONVERSATION_STARTER\": \"वार्तालाप स्टार्टर\",\n\t\"LAUNCH\": \"लांच\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"ने एक सहयोगी दस्तावेज़ साझा किया है\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"ने एक सहयोगी व्हाइटबोर्ड साझा किया है\",\n\t\"CREATED_WHITEBOARD\": \"आपने एक नया सहयोगी व्हाइटबोर्ड बनाया है\",\n\t\"CREATED_DOCUMENT\": \"आपने एक नया सहयोगी दस्तावेज़ बनाया है\",\n\t\"PHOTOS\": \"फ़ोटोज़\",\n\t\"VIDEOS\": \"वीडियो\",\n\t\"DOCUMENT\": \"दस्तावेज़\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ आपने यह संदेश डिलीट कर दिया\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ यह संदेश हटा दिया गया था\",\n\t\"MESSAGE_IS_DELETED\": \"संदेश हटाया गया है\",\n\t\"VIEW_ON_YOUTUBE\": \"यूट्यूब पर देखें\",\n\t\"SEARCH\": \"सर्च करें\",\n\t\"ERROR\": \"एरर\",\n\t\"ERROR_TEXT\": \"ऐसा लगता है कि कुछ गड़बड़ हो गई। कृपया फिर से कोशिश करें।\",\n\t\"NO_GROUPS_FOUND\": \"कोई समूह नहीं मिला\",\n\t\"NO_CHATS_FOUND\": \"कोई चैट नहीं मिली\",\n\t\"CANT__LOAD__CHATS\": \"चैट लोड नहीं कर पा रहे\",\n\t\"MEDIA_MESSAGE\": \"मीडिया संदेश\",\n\t\"INCOMING_AUDIO_CALL\": \"इनकमिंग ऑडियो कॉल\",\n\t\"INCOMING_VIDEO_CALL\": \"इनकमिंग वीडियो कॉल\",\n\t\"DECLINE\": \"डिक्लाइन\",\n\t\"ACCEPT\": \"स्वीकारें\",\n\t\"CALL_INITIATED\": \"कॉल शुरू किया गया\",\n\t\"OUTGOING_AUDIO_CALL\": \"आउटगोइंग ऑडियो कॉल\",\n\t\"OUTGOING_VIDEO_CALL\": \"आउटगोइंग वीडियो कॉल\",\n\t\"REJECTED_CALL\": \"अस्वीकृत कॉल\",\n\t\"CALL_ACCEPTED\": \"कॉल स्वीकार किया गया\",\n\t\"JOINED\": \"शामिल हो गए\",\n\t\"LEFT_THE_CALL\": \"कॉल छोड़ दिया\",\n\t\"UNANSWERED_AUDIO_CALL\": \"अनुत्तरित ऑडियो कॉल\",\n\t\"UNANSWERED_VIDEO_CALL\": \"अनुत्तरित वीडियो कॉल\",\n\t\"CALL_ENDED\": \"कॉल एंडेड\",\n\t\"CALL_BUSY\": \"कॉल व्यस्त है\",\n\t\"CALLING\": \"कॉलिंग...।\",\n\t\"ADD\": \"जोड़ें\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"कोई प्रतिबंधित सदस्य नहीं मिला\",\n\t\"BANNED_MEMBERS\": \"प्रतिबंधित सदस्य\",\n\t\"NAME\": \"नाम\",\n\t\"SCOPE\": \"स्कोप\",\n\t\"UNBAN\": \"प्रतिबंध हटाएँ\",\n\t\"SELECT_GROUP_TYPE\": \"ग्रुप क़िस्म चुनें\",\n\t\"ENTER_GROUP_PASSWORD\": \"ग्रुप पासवर्ड डालें\",\n\t\"CREATE\": \"बनाएँ\",\n\t\"CREATE_POLL\": \"मतदान\",\n\t\"QUESTION\": \"सवाल\",\n\t\"ENTER_YOUR_QUESTION\": \"अपना प्रश्न दर्ज करें\",\n\t\"OPTIONS\": \"ऑप्शन्स\",\n\t\"ENTER_YOUR_OPTION\": \"अपना विकल्प दर्ज करें\",\n\t\"ADD_NEW_OPTION\": \"नया विकल्प जोड़ें\",\n\t\"VIEW_MEMBERS\": \"सदस्यों को देखें\",\n\t\"DETAILS\": \"विवरण\",\n\t\"NOTIFICATIONS\": \"सूचनाएं\",\n\t\"OTHER\": \"अन्य\",\n\t\"HELP\": \"मदद\",\n\t\"REPORT_PROBLEM\": \"समस्या की रिपोर्ट करें\",\n\t\"GROUP_MEMBERS\": \"ग्रुप के सदस्य\",\n\t\"BAN\": \"बैन\",\n\t\"KICK\": \"किक\",\n\t\"PICK_YOUR_EMOJI\": \"अपना इमोजी चुनें\",\n\t\"PRIVATE_GROUP\": \"प्राइवेट ग्रुप\",\n\t\"PROTECTED_GROUP\": \"संरक्षित समूह\",\n\t\"VISIT\": \"विजिट करें\",\n\t\"ATTACH\": \"अटैच करें\",\n\t\"OPEN_WHITEBOARD\": \"व्हाइटबोर्ड खोलें\",\n\t\"ATTACH_FILE\": \"फ़ाइल संलग्न करें\",\n\t\"GENERATING_REPLIES\": \"जवाब जनरेट कर रहा\",\n\t\"ATTACH_VIDEO\": \"वीडियो संलग्न करें\",\n\t\"ATTACH_AUDIO\": \"ऑडियो अटैच करें\",\n\t\"ATTACH_IMAGE\": \"छवि संलग्न करें\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"दस्तावेज़ का उपयोग करके सहयोग करें\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"व्हाइटबोर्ड का उपयोग करके सहयोग करें\",\n\t\"SUGGEST_A_REPLY\": \"जवाब सुझाएं\",\n\t\"GENERATING_ICEBREAKERS\": \"आइसब्रेकर बनाना\",\n\t\"EMOJI\": \"इमोजी\",\n\t\"NO_REPLIES_FOUND\": \"कोई जवाब नहीं मिला\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"अपना संदेश यहां दर्ज करें\",\n\t\"NO_MESSAGES_FOUND\": \"कोई संदेश नहीं मिला\",\n\t\"THREAD\": \"थ्रेड\",\n\t\"COLLABORATIVE_DOCUMENT\": \"सहयोगात्मक दस्तावेज़\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"सहयोगात्मक व्हाइटबोर्ड\",\n\t\"ADD_REACTION\": \"प्रतिक्रिया जोड़ें\",\n\t\"NO_STICKERS_FOUND\": \"कोई स्टिकर नहीं मिला\",\n\t\"REPLY_TO_THREAD\": \"थ्रेड का जवाब दें\",\n\t\"REPLY_IN_THREAD\": \"थ्रेड में जवाब दें\",\n\t\"VIEW\": \"देखें\",\n\t\"DELETE_MESSAGE\": \"संदेश मिटाएँ\",\n\t\"EDIT_MESSAGE\": \"संदेश संपादित करें\",\n\t\"OWNER\": \"मालिक\",\n\t\"CHANGE_SCOPE\": \"स्कोप बदलें\",\n\t\"STICKER\": \"स्टिकर\",\n\t\"LAST_ACTIVE_AT\": \"पिछली बार सक्रिय\",\n\t\"VOICE_CALL\": \"वॉइस कॉल\",\n\t\"VIEW_DETAIL\": \"विस्तार से देखें\",\n\t\"VOTES\": \"वोट\",\n\t\"VOTE\": \"वोट\",\n\t\"NO_VOTE\": \"कोई वोट नहीं\",\n\t\"REACTED\": \"प्रतिक्रिया व्यक्त की\",\n\t\"ADDED\": \"जोड़ा गया\",\n\t\"SHOW_UNSAFE_CONTENT\": \"क्या आप वाकई असुरक्षित सामग्री देखना चाहते हैं?\",\n\t\"REACT_TO_MESSAGE\": \"किसी संदेश पर प्रतिक्रिया दें\",\n\t\"UNBANNED\": \"अप्रतिबंधित\",\n\t\"MADE\": \"बनाया\",\n\t\"MISSED_AUDIO_CALL\": \"मिस्ड ऑडियो कॉल\",\n\t\"ENTER_YOUR_PASSWORD\": \"अपना पासवर्ड डालें\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"सामग्री को एक साथ संपादित करने के लिए दस्तावेज़ खोलें\",\n\t\"DOCS\": \"डॉक्स\",\n\t\"NO_RECORDS_FOUND\": \"कोई रिकॉर्ड नहीं मिला\",\n\t\"LIVE_REACTION\": \"लाइव रिएक्शन\",\n\t\"SMILEY_PEOPLE\": \"स्माइलीज एंड पीपल\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"एक साथ खींचने के लिए व्हाइटबोर्ड खोलें\",\n\t\"ANIMALES_NATURE\": \"पशु और प्रकृति\",\n\t\"FOOD_DRINK\": \"खाना और पीना\",\n\t\"OPEN_DOCUMENT\": \"दस्तावेज़ खोलें\",\n\t\"ACTIVITY\": \"गतिविधि\",\n\t\"TRAVEL_PLACES\": \"यात्रा और स्थान\",\n\t\"OBJECTS\": \"ऑब्जेक्ट्स\",\n\t\"SYMBOLS\": \"सिंबल\",\n\t\"FLAGS\": \"फ्लैग्स\",\n\t\"SENT\": \"भेजा गया\",\n\t\"SEEN\": \"देखा गया\",\n\t\"DELIVERED\": \"डिलीवर किया\",\n\t\"READ\": \"पढ़ें\",\n\t\"MESSAGE_INFORMATION\": \"संदेश की जानकारी\",\n\t\"TRANSLATE_MESSAGE\": \"संदेश का अनुवाद करें\",\n\t\"TRANSLATED_MESSAGE\": \"अनुवादित संदेश\",\n\t\"LEFT\": \"छोड़ दिया है\",\n\t\"KICKED\": \"लात मारी\",\n\t\"BANNED\": \"बारित\",\n\t\"NEW_MESSAGES\": \"नए संदेश\",\n\t\"NEW_MESSAGE\": \"नया संदेश\",\n\t\"JUMP\": \"जंप\",\n\t\"SELECT_VIDEO_SOURCE\": \"वीडियो स्रोत चुनें\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"इनपुट ऑडियो स्रोत का चयन करें\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"आउटपुट ऑडियो स्रोत चुनें\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"ने एक ऑडियो कॉल शुरू किया है\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"एक वीडियो कॉल शुरू किया है\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"आपने एक ऑडियो कॉल शुरू किया है\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"आपने वीडियो कॉल शुरू किया है\",\n\t\"IGNORE\": \"ध्यान न दें\",\n\t\"ON_ANOTHER_CALL\": \"एक और कॉल पर है\",\n\t\"CREATING\": \"बनाना\",\n\t\"AVATAR\": \"पुनर्जन्म\",\n\t\"ONGOING_CALL\": \"चल रही कॉल\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"आप पहले से चल रही कॉल में हैं\",\n\t\"RESIZE\": \"आकार बदलें\",\n\t\"SETTINGS\": \"सेटिंग्स\",\n\t\"ACTIONS\": \"क्रियाएँ\",\n\t\"VIEW_PROFILE\": \"प्रोफ़ाइल देखें\",\n\t\"READ_MORE\": \"और पढ़ें\",\n\t\"SHOW_LESS\": \"कम दिखाएं\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"संदेश निजी तौर पर भेजें\",\n\t\"DELETE\": \"मिटाएँ\",\n\t\"DELETE_CONFIRM\": \"क्या आप वाकई डिलीट करना चाहते हैं?\",\n\t\"DELETE_CHAT\": \"इस चैट को डिलीट करें?\",\n\t\"SURE_TO_DELETE_CHAT\": \"क्या आप वाकई इस चैट को डिलीट करना चाहते हैं? इस क्रिया को पूर्ववत नहीं किया जा सकता।\",\n\t\"CANCEL\": \"रद्द करें\",\n\t\"LEAVE_CONFIRM\": \"क्या आप वाकई समूह छोड़ना चाहते हैं?\",\n\t\"TRANSFER_CONFIRM\": \"आप समूह के स्वामी हैं; कृपया समूह छोड़ने से पहले किसी सदस्य को स्वामित्व हस्तांतरित करें\",\n\t\"ADDING\": \"जोड़ रहा है...\",\n\t\"TRANSFER\": \"ट्रांसफ़र\",\n\t\"TRANSFER_OWNERSHIP\": \"ट्रांसफर ओनरशिप\",\n\t\"TRANSFERRING\": \"ट्रांसफर किया जा रहा है\",\n\t\"YES\": \"हाँ\",\n\t\"NO\": \"नहीं\",\n\t\"ANSWER\": \"उत्तर\",\n\t\"ADD_ANOTHER_ANSWER\": \"एक और जवाब जोड़ें\",\n\t\"SET_THE_ANSWERS\": \"उत्तर सेट करें\",\n\t\"TRY_AGAIN\": \"फिर से कोशिश करें\",\n\t\"INVALID_GROUP_NAME\": \"कृपया ग्रुप के लिए मान्य नाम दर्ज़ करें और फिर से कोशिश करें\",\n\t\"INVALID_PASSWORD\": \"कृपया ग्रुप के लिए एक मान्य पासवर्ड डालें और फिर से कोशिश करें\",\n\t\"INVALID_GROUP_TYPE\": \"कृपया समूह के लिए एक मान्य प्रकार दर्ज करें और फिर से कोशिश करें\",\n\t\"WRONG_PASSWORD\": \"कृपया सही पासवर्ड डालें और फिर से कोशिश करें\",\n\t\"INVALID_POLL_QUESTION\": \"पोल बनाने से पहले कृपया आवश्यक प्रश्न दर्ज करें\",\n\t\"INVALID_POLL_OPTION\": \"पोल बनाने से पहले कृपया आवश्यक उत्तर दर्ज करें\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"अनुवाद के लिए चुनी गई भाषा मूल संदेश की भाषा के समान है\",\n\t\"LEAVE\": \"लीव\",\n\t\"CLICK_TO_START_CONVERSATION\": \"बातचीत शुरू करने के लिए क्लिक करें\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 स्थान\",\n\t\"SHARED_LOCATION\": \"साझा किया गया स्थान\",\n\t\"IN_A_THREAD\": \"एक सूत्र में\",\n\t\"CALLS\": \"कॉल्स\",\n\t\"CALL_DETAILS\": \"कॉल की जानकारी\",\n\t\"OFFLINE\": \"ऑफ़लाइन\",\n\t\"POLLS\": \"पोल\",\n\t\"YOU\": \"आप\",\n\t\"PRIVACY\": \"प्राइवेसी\",\n\t\"BLOCKED_USERS\": \"ब्लॉक किए गए यूज़र\",\n\t\"YOU'VE_BLOCKED\": \"आपने ब्लॉक कर दिया है\",\n\t\"NO_PHOTOS\": \"कोई फ़ोटो नहीं\",\n\t\"NO_VIDEOS\": \"कोई वीडियो नहीं\",\n\t\"NO_DOCUMENTS\": \"कोई दस्तावेज़ नहीं\",\n\t\"JOIN\": \"जुड़ें\",\n\t\"REMOVE\": \"हटाएँ\",\n\t\"BLOCK\": \"ब्लॉक\",\n\t\"CHANGE_ROLE\": \"भूमिका बदलें\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"आप समूह की अनुमतियों और जिम्मेदारियों को प्रबंधित करने के लिए भूमिकाएँ बदल सकते हैं।\",\n\t\"CHANGE_TO\": \"में बदलें\",\n\t\"NEW_CHAT\": \"नई चैट\",\n\t\"FORM_COMPLETION_MESSAGE\": \"फ़ॉर्म भरने के लिए धन्यवाद।\",\n\t\"HISTORY\": \"इतिहास\",\n\t\"CANCELLED_AUDIO_CALL\": \"रद्द किया गया ऑडियो कॉल\",\n\t\"CANCELLED_VIDEO_CALL\": \"रद्द किया गया वीडियो कॉल\",\n\t\"REJECTED_AUDIO_CALL\": \"अस्वीकृत ऑडियो कॉल\",\n\t\"REJECTED_VIDEO_CALL\": \"अस्वीकृत वीडियो कॉल\",\n\t\"CANCELLED_CALL\": \"रद्द की गई कॉल\",\n\t\"UNANSWERED_CALL\": \"अनुत्तरित कॉल\",\n\t\"RECORDING\": \"रिकॉर्डिंग\",\n\t\"PARTICIPANTS\": \"प्रतिभागी\",\n\t\"CALL_HISTORY\": \"कॉल हिस्ट्री\",\n\t\"NO_CALLS_FOUND\": \"कोई कॉल नहीं मिली\",\n\t\"ONGOING_AUDIO_CALL\": \"ऑडियो कॉल चल रही है\",\n\t\"ONGOING_VIDEO_CALL\": \"वीडियो कॉल जारी है\",\n\t\"CALL_DETAIL\": \"कॉल विवरण\",\n\t\"FORM\": \"प्रपत्र\",\n\t\"CARD\": \"कार्ड\",\n\t\"GENERATING_SUMMARY\": \"सारांश तैयार कर रहा है\",\n\t\"CONVERSATION_SUMMARY\": \"बातचीत का सारांश\",\n\t\"GENERATE_SUMMARY\": \"सारांश जेनरेट करें\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"मैं इस बातचीत में आपकी मदद कैसे कर सकता हूं? कृपया मुझसे एक प्रश्न पूछें और मैं आपको सलाह दूंगा 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"पूछो\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"AI बॉट्स से पूछें\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"एआई बॉट\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"आप एक बार में अधिकतम 10 यूज़र का उल्लेख कर सकते हैं।\",\n\t\"ALL\": \"सभी\",\n\t\"CLICK_TO_REMOVE\": \"हटाने के लिए क्लिक करें\",\n\t\"OTHERS\": \"अन्य लोगों\",\n\t\"AND\": \"एक\",\n\t\"ASK_QUESTION\": \"सवाल पूछें\",\n\t\"ADD_OPTION\": \"विकल्प जोड़ें\",\n\t\"SEND\": \"भेजें\",\n\t\"FULL_SCREEN_VIEWER\": \"फ़ुल स्क्रीन व्यूअर\",\n\t\"TEXT_TRANSLATE\": \"टेक्स्ट ट्रांसलेट\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"ऐसा लगता है कि कुछ गड़बड़ हो गई\",\n\t\"NO_SUMMARY_AVAILABLE\": \"कोई सारांश उपलब्ध नहीं\",\n\t\"QUESTIONS\": \"प्रशन\",\n\t\"NO_STICKERS_AVAILABLE\": \"कोई स्टिकर उपलब्ध नहीं\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"कोई सुझाव उपलब्ध नहीं\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"उन्हें यहां सूचीबद्ध देखने के लिए समूह बनाएं या उनसे जुड़ें और सहयोग करना शुरू करें\",\n\t\"NO_USERS_AVAILABLE\": \"कोई यूज़र उपलब्ध नहीं\",\n\t\"NO_GROUPS_AVAILABLE\": \"कोई समूह उपलब्ध नहीं है\",\n\t\"NO_CALL_LOGS\": \"अभी तक कोई कॉल लॉग नहीं\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"यहां सूचीबद्ध अपना कॉल इतिहास देखने के लिए कॉल करें या कॉल प्राप्त करें\",\n\t\"NO_CONVERSATIONS\": \"अभी तक कोई बातचीत नहीं हुई है”;  \",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"नई चैट शुरू करें या बातचीत में शामिल होने के लिए अन्य लोगों को आमंत्रित करें।\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"समूह का कोई सदस्य उपलब्ध नहीं\",\n\t\"OOPS\": \"उफ़\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"संदेश प्राप्त करने और देखने के लिए प्राप्तकर्ताओं की प्रतीक्षा की जा रही है\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"उन्हें यहां सूचीबद्ध देखने के लिए संपर्क जोड़ें।\",\n\t\"REACHED_MAX_LIMIT\": \"आप सीमा तक पहुँच गए हैं। आप अधिकतम 12 विकल्प जोड़ सकते हैं।\",\n\t\"REQUIRED_FIELDS_WARNING\": \"पोल बनाने से पहले कृपया सभी आवश्यक फ़ील्ड भरें\",\n\t\"NOT_SUPPORTED\": \"यह संदेश क़िस्म समर्थित नहीं है\",\n\t\"NO_USERS_FOUND\": \"कोई उपयोगकर्ता नहीं मिला\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"हमें आपकी खोज से मेल खाने वाला कोई भी यूज़र नहीं मिला। अपनी खोज में बदलाव करने की कोशिश करें।\",\n\t\"SAVE\": \"सेव करें\",\n\t\"RETRY\": \"पुनः प्रयास करें\",\n\t\"TAP_TO_START_CONVERSATION\": \"बातचीत शुरू करने के लिए टैप करें\",\n\t\"ATTACH_DOCUMENT\": \"दस्तावेज़ संलग्न करें\",\n\t\"CAMERA\": \"कैमरा\",\n\t\"SHARE\": \"शेयर करें\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"क्या आप वाकई हटाना चाहते हैं\",\n\t\"BAN_MEMBER_CONFIRM\": \"क्या आप वाकई प्रतिबंधित करना चाहते हैं \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"अपना संदेश यहां दर्ज करें\",\n\t\"TAP_TO_REMOVE\": \"हटाने के लिए टैप करें\",\n\t\"OOPS!\": \"उफ़!\",\n\t\"ADD_CONTACTS\": \"संपर्क जोड़ें ताकि बातचीत शुरू हो और उन्हें यहां सूचीबद्ध देखें।\",\n\t\"SCOPE_CHANGE_INFO\": \"आप समूह अनुमतियों और जिम्मेदारियों को प्रबंधित करने के लिए भूमिकाएँ बदल सकते हैं\",\n\t\"SOMETHING_WENT_WRONG\": \"लगता है कुछ गलत हो गया है।\",\n\t\"MENTION_UPTO\": \"आप निम्नलिखित का उल्लेख कर सकते हैं\",\n\t\"TIME\": \"पहर\",\n\t\"TIMES\": \"समय\",\n\t\"AT_A_TIME\": \"एक समय में\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"इस सुविधा के लिए विशिष्ट संसाधन अनुमति (ओं) की आवश्यकता होती है। कृपया अपनी डिवाइस सेटिंग में आवश्यक अनुमतियां सक्षम करें।\",\n\t\"MICROPHONE_PERMISSION\": \"ध्वनि संदेश रिकॉर्ड करने के लिए, हमें आपके माइक्रोफ़ोन तक पहुँच की आवश्यकता है। सेटिंग पर टैप करें और माइक्रोफ़ोन चालू करें।\",\n\t\"CAMERA_PERMISSION\": \"हमारे पास आपके कैमरे तक पहुंच नहीं है। ऐक्सेस चालू करने के लिए, सेटिंग पर टैप करें और कैमरा चालू करें।\",\n\t\"ADD_OPTIONS\": \"विकल्प जोड़ें\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"एक साथ आरेखित करने के लिए दस्तावेज़ खोलें\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"एक साथ खींचने के लिए व्हाइटबोर्ड खोलें\",\n\t\"TEXT\": \"टेक्स्ट\",\n\t\"TEXT_TRANSLATED\": \"अनुवादित पाठ\",\n\t\"LOGIN\": \"लॉग इन करें\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Google के साथ जारी रखें\",\n\t\"JOIN_GROUP\": \"ग्रुप में शामिल हों\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"ग्रुप का पासवर्ड गलत है!\",\n\t\"ENTER_PASSWORD\": \"पासवर्ड दर्ज करें\",\n\t\"TYPE\": \"टाइप\",\n\t\"USER_INFO\": \"उपयोगकर्ता जानकारी\",\n\t\"DELETE_CHAT_TEXT\": \"चैट हटाएं\",\n\t\"UNBLOCK\": \"अनब्लॉक करें\",\n\t\"UNBLOCK_CONTACT\": \"इस संपर्क को अनब्लॉक करें\",\n\t\"BLOCK_CONTACT\": \"क्या इस संपर्क को ब्लॉक करें?\",\n\t\"UNBLOCK_SURE\": \"क्या आप वाकई इस संपर्क को अनब्लॉक करना चाहते हैं? आप फिर से उनके संदेश प्राप्त करना शुरू कर देंगे।\",\n\t\"BLOCK_SURE\": \"क्या आप वाकई इस संपर्क को ब्लॉक करना चाहते हैं? अब आप उनसे संदेश प्राप्त नहीं करेंगे।\",\n\t\"VOICE\": \"आवाज़\",\n\t\"DELETE_AND_EXIT_SURE\": \"क्या आप वाकई इस चैट को हटाना और ग्रुप छोड़ना चाहते हैं? यह कार्रवाई अपरिवर्तनीय है।\",\n\t\"TRANSFER_SURE\": \"क्या आप वाकई स्वामित्व हस्तांतरण करना चाहते हैं? इसे पूर्ववत नहीं किया जा सकता, और नया मालिक पूरा नियंत्रण लेगा।\",\n\t\"GROUP_INFO\": \"ग्रुप जानकारी\",\n\t\"LEAVE_GROUP_TEXT\": \"इस ग्रुप को छोड़ें\",\n\t\"LEAVE_SURE\": \"क्या आप वाकई ग्रुप छोड़ना चाहते हैं? अब आप इस चैट से संदेश प्राप्त नहीं करेंगे।\",\n\t\"UNBAN_SURE\": \"क्या आप वाकई इस उपयोगकर्ता से प्रतिबंध हटाना चाहते हैं?\",\n\t\"DELETE_THIS_CONVERSATION\": \"क्या इस बातचीत को हटाना है?\",\n\t\"LINK\": \"लिंक\",\n\t\"DELETE_THIS_MESSAGE\": \"इस संदेश को हटाएं?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"क्या आप वाकई इस संदेश को हटाना चाहते हैं? इस क्रिया को पूर्ववत नहीं किया जा सकता।\",\n\t\"APP_CREDENTIALS\": \"ऐप क्रेडेंशियल्स\",\n\t\"REGION\": \"क्षेत्र\",\n\t\"CHAT_PRIVATELY\": \"निजी रूप से चैट करें\",\n\t\"WRONG_TEXT\":\"लगता है कुछ गलत हो गया।\",\n\t\"WRONG_TEXT_TRY_AGAIN\":\"कृपया पुनः प्रयास करें।\",\n\t\"BLOCKED_USER_DESC\": \"संदेश नहीं भेज सकते क्योंकि उपयोगकर्ता अवरुद्ध है\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} ने ${onName} को समूह में जोड़ा\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} समूह में शामिल हुए\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} ने समूह छोड़ दिया\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} ने ${onName} को समूह से निकाला\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} ने ${onName} को समूह से प्रतिबंधित किया\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} ने ${onName} का प्रतिबंध हटाया\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} ने ${onName} को ${role} बनाया\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"आपके पास अभी तक कोई स्टिकर नहीं है।\",\n\t\"meeting\": \"मीटिंग\",\n\t\"Flag_Message_Title\": \"संदेश की रिपोर्ट करें\",\n\t\"Flag_Message_Subtitle\": \"यदि यह चैट हमारे समुदाय मानकों के खिलाफ है तो रिपोर्ट करें। हम उस खाते को नहीं बताएँगे जिसे आपने रिपोर्ट किया है।\",\n\t\"Flag_Message_Remark_Label\": \"कारण\",\n\t\"Flag_Message_Remark_Optional\": \"वैकल्पिक\",\n\t\"Flag_Message_Remark_Placeholder\": \"अपनी रिपोर्ट के लिए अतिरिक्त संदर्भ दें…\",\n\t\"Flag_Message_Confirm_Yes\": \"रिपोर्ट करें\",\n\t\"Flag_Message_Confirm_No\": \"रद्द करें\",\n\t\"Flag_Message_Reason_Id_Spam\": \"स्पैम / अवांछित सामग्री\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"स्पष्ट या अनुपयुक्त सामग्री\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"अपमानजनक या धमकी भरा व्यवहार\",\n\t\"Message_List_Option_Flag_Message\": \"रिपोर्ट करें\",\n\t\"Flag_Error_Text\":\"कुछ गलत हो गया। कृपया जाँच करें और फिर से प्रयास करें।\",\n\t\"Flag_Empty_Submit_Text\":\"सबमिट करने में असमर्थ। कृपया इस संदेश की रिपोर्ट करने से पहले एक कारण चुनें।\",\n\t\"START_REPORTING\": \"रिपोर्ट शुरू करें\",\n\t\"LOGOUT\": \"लॉगआउट करें\",\n\t\"AI_ASSISTANTS\": \"एआई सहायक\",\n\t\"CREATE_CONVERSATION\": \"बातचीत बनाएँ\",\n\t\"EDIT_MODERATION\": \"संपादन विफल हो गया। आपका संदेश मोडरेशन नीतियों के कारण ब्लॉक कर दिया गया।\",\n\t\"BLOCKED_MODERATION\":\"आपका संदेश मोडरेशन नीतियों के कारण ब्लॉक कर दिया गया।\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"कोई बातचीत का इतिहास नहीं मिला\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"हमारी ओर से कुछ गलत हो गया। कृपया पुनः प्रयास करें।\",\n\t\"START_A_CHAT\": \"'नई चैट' बटन पर टैप करके चैट शुरू करें।\",\n\t\"CHAT_HISTORY\": \"चैट इतिहास\",\n\t\"ASK_ANYTHING\": \"कुछ भी पूछें...\",\n\t\"AI_ASSISTANT\": \"एआई सहायक\",\n\t\"SEARCH_PLACEHOLDER\": \"खोजें...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"कोई परिणाम नहीं\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"संदेशों और बातचीत को खोजने के लिए टाइप करना शुरू करें\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"कोई परिणाम नहीं मिला\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"हम कुछ नहीं ढूँढ पाए। कृपया कोई दूसरा कीवर्ड आज़माएँ।\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"बातचीत लोड करने में त्रुटि\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"संदेश लोड करने में त्रुटि\",\n\t\"SEARCH_TRY_AGAIN\": \"कृपया बाद में पुनः प्रयास करें\",\n\t\"SEARCH_SEE_MORE\": \"और देखें\",\n\t\"SEARCH_LOADING\": \"लोड हो रहा है...\",\n\t\"SEARCH_FILTER_UNREAD\": \"अपठित\",\n\t\"SEARCH_FILTER_GROUPS\": \"समूह\",\n\t\"SEARCH_FILTER_PHOTOS\": \"तस्वीरें\",\n\t\"SEARCH_FILTER_VIDEOS\": \"वीडियो\",\n\t\"SEARCH_FILTER_LINKS\": \"लिंक\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"दस्तावेज़\",\n\t\"SEARCH_FILTER_AUDIO\": \"ऑडियो\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"बातचीत\",\n\t\"SEARCH_FILTER_MESSAGES\": \"संदेश\",\n\t\"SEARCH_NO_MESSAGES\": \"कोई संदेश नहीं\",\n\t\"MESSAGE_REPORTED\": \"संदेश रिपोर्ट किया गया\",\n\t\"MARK_AS_UNREAD\": \"अपठित करें\",\n\t\"NEW\": \"नया\",\n\t\"INSERT_LINK\": \"लिंक डालें\",\n\t\"EDIT_LINK\": \"लिंक संपादित करें\",\n\t\"LINK_TEXT\": \"लिंक टेक्स्ट\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"डालें\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"आप इस समूह में संदेश नहीं भेज सकते क्योंकि अब आप सदस्य नहीं हैं।\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/hu/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Elfogadom\",\n\t\"ACTIONS\": \"Tevékenységek\",\n\t\"ACTIVITY\": \"Tevékenység\",\n\t\"ADD\": \"Hozzáadás\",\n\t\"ADDED\": \"Hozzátette\",\n\t\"ADDING\": \"Hozzáadva...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Adjon hozzá egy másik választ\",\n\t\"ADD_CONTACTS\": \"Adjon hozzá kapcsolatokat, hogy beszélgetéseket kezdjen és itt láthassa őket felsorolva.\",\n\t\"ADD_MEMBERS\": \"Tagok hozzáadása\",\n\t\"ADD_NEW_OPTION\": \"Új opció hozzáadása\",\n\t\"ADD_OPTION\": \"Opció hozzáadása\",\n\t\"ADD_OPTIONS\": \"Opciók hozzáadása\",\n\t\"ADD_REACTION\": \"Adjon hozzá reakciót\",\n\t\"ADD_TO_CHAT\": \"Hozzáadás a csevegéshez\",\n\t\"ADMIN\": \"Rendszergazda\",\n\t\"ADMINISTRATOR\": \"Rendszergazda\",\n\t\"AI\": \"Ó,\",\n\t\"ALL\": \"Minden\",\n\t\"AND\": \"egy\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Mind\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Értesítsen mindenkit ebben a csoportban\",\n\t\"ANIMALES_NATURE\": \"Állatok és természet\",\n\t\"ANSWER\": \"Válasz\",\n\t\"APP_CREDENTIALS\": \"Alkalmazás hitelesítő adatai\",\n\t\"ASK_QUESTION\": \"Tegyen fel kérdést\",\n\t\"AT\": \"itt:\",\n\t\"ATTACH\": \"Csatlakoztassa\",\n\t\"ATTACH_AUDIO\": \"Hang csatolása\",\n\t\"ATTACH_DOCUMENT\": \"Dokumentum csatolása\",\n\t\"ATTACH_FILE\": \"Fájl csatolása\",\n\t\"ATTACH_IMAGE\": \"Kép csatolása\",\n\t\"ATTACH_VIDEO\": \"Videó csatolása\",\n\t\"AT_A_TIME\": \"egyszerre\",\n\t\"AUDIO_CALL\": \"Hanghívás\",\n\t\"AVATAR\": \"Avatár\",\n\t\"BADGE_COUNT\": \"Jelvény száma\",\n\t\"BAN\": \"Tiltás\",\n\t\"BANNED\": \"betiltott\",\n\t\"BANNED_MEMBERS\": \"Tiltott tagok\",\n\t\"BAN_MEMBER_CONFIRM\": \"Biztosan tiltani szeretné \",\n\t\"BLOCK\": \"Blokkolás\",\n\t\"BLOCKED_USERS\": \"Blokkolt felhasználók\",\n\t\"BLOCKED_USER_DESC\": \"Nem lehet üzenetet küldeni, mert a felhasználó le van tiltva\",\n\t\"BLOCK_CONTACT\": \"Blokkolja ezt a kapcsolatot?\",\n\t\"BLOCK_SURE\": \"Biztosan blokkolni szeretné ezt a kapcsolatot? Többé nem fog üzeneteket kapni tőlük.\",\n\t\"BLOCK_USER\": \"Felhasználó blokkolása\",\n\t\"CALLING\": \"Hívom...\",\n\t\"CALLS\": \"Hívások\",\n\t\"CALL_ACCEPTED\": \"Hívás elfogadva\",\n\t\"CALL_ANSWERED\": \"Válaszolt hívás\",\n\t\"CALL_BUSY\": \"Hívjon elfoglaltnak\",\n\t\"CALL_CANCELLED\": \"Hívás törölve\",\n\t\"CALL_DETAIL\": \"Hívás részletei\",\n\t\"CALL_DETAILS\": \"Hívás részletei\",\n\t\"CALL_ENDED\": \"Hívás befejeződött\",\n\t\"CALL_HISTORY\": \"Hívási előzmények\",\n\t\"CALL_INITIATED\": \"Hívás kezdeményezett\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Hívások kezdeményezése vagy fogadása az itt felsorolt híváselőzmények megtekintéséhez\",\n\t\"CALL_REJECTED\": \"Hívás elutasítása\",\n\t\"CALL_UNANSWERED\": \"Hívás megválaszolatlan\",\n\t\"CAMERA\": \"kamera\",\n\t\"CAMERA_PERMISSION\": \"Nem férünk hozzá a kamerájához. A hozzáférés engedélyezéséhez érintse meg a Beállítások elemet, és kapcsolja be a Kamera lehetőséget.\",\n\t\"CANCEL\": \"Törölje\",\n\t\"CANCELLED_AUDIO_CALL\": \"Hanghívás törlése\",\n\t\"CANCELLED_CALL\": \"Törölött hívás\",\n\t\"CANCELLED_VIDEO_CALL\": \"Törölték a videohívást\",\n\t\"CANT__LOAD__CHATS\": \"Nem lehet betölteni a csevegéseket\",\n\t\"CARD\": \"Kártya\",\n\t\"CHANGE_ROLE\": \"Szerepváltás\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"A szerepköröket módosíthatja a csoportjogosultságok és felelősségek kezeléséhez.\",\n\t\"CHANGE_SCOPE\": \"Hatókör módosítása\",\n\t\"CHANGE_TO\": \"Változtassa meg\",\n\t\"CHATS\": \"Beszélgetések\",\n\t\"CHAT_PRIVATELY\": \"Privát csevegés\",\n\t\"CLICK_TO_REMOVE\": \"Kattintson az eltávolításhoz\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Kattintson a beszélgetés megkezdéséhez\",\n\t\"CLOSE\": \"Bezárás\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Együttműködés dokumentum használatával\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Együttműködés tábla segítségével\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Együttműködési dokumentum\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Együttműködési tábla\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Kérdezze meg AI botokat\",\n\t\"COMETCHAT_ASK_BOT\": \"Kérdezd\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI Bot\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Hogyan segíthetek ebben a beszélgetésben? Kérlek, tegyen fel egy kérdést, és tanácsot adok neked 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Írja be ide az üzenetét\",\n\t\"CONTINUE\": \"Folytatás\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Folytatás Google-lal\",\n\t\"CONVERSATIONS\": \"Beszélgetések\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Indítson el egy új csevegést, vagy hívjon meg másokat, hogy csatlakozzanak a beszélgetéshez.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Beszélgetések üzenetekkel\",\n\t\"CONVERSATION_DELETED\": \"A beszélgetés törölve\",\n\t\"CONVERSATION_LIST\": \"Beszélgetési lista\",\n\t\"CONVERSATION_LIST_ITEM\": \"Beszélgetési lista elem\",\n\t\"CONVERSATION_STARTER\": \"Beszélgetés indítója\",\n\t\"CONVERSATION_SUMMARY\": \"A beszélgetés összefoglalása\",\n\t\"COPY\": \"Másolás\",\n\t\"CREATE\": \"Létrehozni\",\n\t\"CREATED_DOCUMENT\": \"Új együttműködési dokumentumot hoztál létre\",\n\t\"CREATED_WHITEBOARD\": \"Új együttműködési táblát hoztál létre\",\n\t\"CREATE_GROUP\": \"Csoport létrehozása\",\n\t\"CREATE_POLL\": \"Szavazás\",\n\t\"CREATING\": \"Létrehozás\",\n\t\"CUSTOM_MESSAGE\": \"Van egy üzeneted\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Dokumentum\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Elhelyezkedés\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Szavazás\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Matrica\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Tábla\",\n\t\"DATA_ITEM\": \"Adatelem\",\n\t\"DECLINE\": \"Visszaesés\",\n\t\"DELETE\": \"Törlés\",\n\t\"DELETE_AND_EXIT\": \"Törlés és kilépés\",\n\t\"DELETE_AND_EXIT_SURE\": \"Biztosan törli ezt a csevegést és kilép a csoportból? Ezt a műveletet nem lehet visszavonni.\",\n\t\"DELETE_CHAT\": \"Törölje ezt a csevegést?\",\n\t\"DELETE_CHAT_TEXT\": \"Csevegés törlése\",\n\t\"DELETE_CONFIRM\": \"Biztos vagy benne, hogy törölni akarod?\",\n\t\"DELETE_CONVERSATION\": \"Beszélgetés törlése?\",\n\t\"DELETE_MESSAGE\": \"Üzenet törlése\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Biztos vagy benne, hogy törölni szeretnéd ezt az üzenetet? Ez a művelet nem vonható vissza.\",\n\t\"DELETE_MSG_TEXT\": \"Ezt az üzenetet törölték\",\n\t\"DELETE_THIS_CONVERSATION\": \" Törli ezt a beszélgetést?\",\n\t\"DELETE_THIS_MESSAGE\": \"Üzenet törlése?\",\n\t\"DELIVERED\": \"Szállított\",\n\t\"DETAILS\": \"Részletek\",\n\t\"DOCS\": \"Dokumentumok\",\n\t\"DOCUMENT\": \"Dokumentum\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Nyissa meg a dokumentumot a tartalom közös szerkesztéséhez\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Nyissa meg a táblát a rajzoláshoz\",\n\t\"EDIT\": \"Szerkesztés\",\n\t\"EDITED\": \"Szerkesztett\",\n\t\"EDIT_MESSAGE\": \"Üzenet szerkesztése\",\n\t\"EMOJI\": \"Emoji\",\n\t\"ENTER_GROUP_NAME\": \"Adja meg a csoport nevét\",\n\t\"ENTER_GROUP_PASSWORD\": \"Adja meg a csoport jelszavát\",\n\t\"ENTER_PASSWORD\": \"Írd be a jelszót\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Írja be ide az üzenetét\",\n\t\"ENTER_YOUR_OPTION\": \"Adja meg az opciót\",\n\t\"ENTER_YOUR_PASSWORD\": \"Adja meg a jelszavát\",\n\t\"ENTER_YOUR_QUESTION\": \"Írja be a kérdését\",\n\t\"ERROR\": \"Hiba\",\n\t\"ERROR_TEXT\": \"Úgy tűnik, valami rosszul ment. Kérjük, próbálkozzon újra.\",\n\t\"FLAGS\": \"Zászlók\",\n\t\"FOOD_DRINK\": \"Étel és ital\",\n\t\"FORM\": \"Űrlap\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Köszönjük, hogy kitöltötte az űrlapot.\",\n\t\"FRIDAY\": \"péntek\",\n\t\"FULL_SCREEN_VIEWER\": \"Teljes képernyős megjelenítő\",\n\t\"GENERATE_SUMMARY\": \"Összefoglaló létrehozása\",\n\t\"GENERATING_ICEBREAKERS\": \"Jégtörők generálása\",\n\t\"GENERATING_REPLIES\": \"Válaszok generálása\",\n\t\"GENERATING_SUMMARY\": \"Összefoglaló generálása\",\n\t\"GROUPS\": \"Csoportok\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Hozzon létre csoportokat vagy csatlakozzon, hogy megtekinthesse őket itt felsorolva, és kezdjen el együttműkö\",\n\t\"GROUPS_WITH_MESSAGES\": \"Csoportok üzenetekkel\",\n\t\"GROUP_INFO\": \"Csoport információ\",\n\t\"GROUP_LIST\": \"Csoportlista\",\n\t\"GROUP_MEMBERS\": \"Csoporttagok\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Adjon hozzá névjegyeket, hogy megtekinthesse őket itt.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Várakozás arra, hogy a címzettek fogadják és megtekinthetik az üzenetet\",\n\t\"GROUP_NAME_BLANK\": \"A csoport neve nem lehet üres\",\n\t\"GROUP_PASSWORD_BLANK\": \"A csoport jelszava nem lehet üres\",\n\t\"GROUP_TYPE_BLANK\": \"A csoport típusa nem lehet üres\",\n\t\"HELP\": \"Segítség\",\n\t\"HISTORY\": \"Történelem\",\n\t\"IGNORE\": \"Figyelmen kívül\",\n\t\"INCOMING_AUDIO_CALL\": \"Bejövő hanghívás\",\n\t\"INCOMING_CALL\": \"Bejövő hívás\",\n\t\"INCOMING_VIDEO_CALL\": \"Bejövő videohívás\",\n\t\"INFO\": \"Információ\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"hanghívást kezdeményezett\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"videohívást kezdeményezett\",\n\t\"INVALID_GROUP_NAME\": \"Kérjük, adja meg a csoport érvényes nevét, és próbálkozzon újra\",\n\t\"INVALID_GROUP_TYPE\": \"Adjon meg egy érvényes típust a csoporthoz, és próbálkozzon újra\",\n\t\"INVALID_PASSWORD\": \"Kérjük, írjon be egy érvényes jelszót a csoport számára, és próbálkozzon újra\",\n\t\"INVALID_POLL_OPTION\": \"Kérjük, a szavazás létrehozása előtt adja meg a szükséges választ\",\n\t\"INVALID_POLL_QUESTION\": \"Kérjük, írja be a szükséges kérdést a szavazás létrehozása előtt\",\n\t\"IN_A_THREAD\": \"Egy szálban\",\n\t\"IS_TYPING\": \"gépelne...\",\n\t\"JOIN\": \"Csatlakozz\",\n\t\"JOINED\": \"csatlakozott\",\n\t\"JOIN_GROUP\": \"Csoporthoz csatlakozás\",\n\t\"JUMP\": \"Ugrás\",\n\t\"KICK\": \"Rúgás\",\n\t\"KICKED\": \"rúgott\",\n\t\"LAST_ACTIVE_AT\": \"Utoljára aktív itt:\",\n\t\"LAST_SEEN\": \"Utoljára látott\",\n\t\"LAUNCH\": \"Indítás\",\n\t\"LEAVE\": \"Menj el\",\n\t\"LEAVE_CONFIRM\": \"Biztos vagy benne, hogy el akarod hagyni a csoportot?\",\n\t\"LEAVE_GROUP\": \"Elhagyja a csoportot\",\n\t\"LEAVE_GROUP_TEXT\": \"Biztosan ki akar lépni a csoportból? Többé nem fog üzeneteket kapni ebből a csevegésből.\",\n\t\"LEAVE_SURE\": \"Biztosan ki akar lépni a csoportból? Többé nem fog üzeneteket kapni ebből a csevegésből.\",\n\t\"LEFT\": \"balra\",\n\t\"LEFT_THE_CALL\": \"elhagyta a hívást\",\n\t\"LINK\": \"Hivatkozás\",\n\t\"LIVE_REACTION\": \"Élő reakció\",\n\t\"LOADING\": \"Betöltés...\",\n\t\"LOCALIZE\": \"Lokalizálás\",\n\t\"LOGIN\": \"Bejelentkezés\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Úgy tűnik, valami rosszul ment\",\n\t\"MADE\": \"készített\",\n\t\"MEDIA_MESSAGE\": \"Médiaüzenet\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Foglaljon új nyerőgépet\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Erre a dátumra nincs időszint. Kérjük, próbálkozzon egy másik dátumot.\",\n\t\"MEETING_SCHEDULED\": \"A találkozót már ütemeztük.\",\n\t\"MEETING_SCHEDULER\": \"Találkozó ütemező\",\n\t\"MEETING_SLOT_BOOK\": \"Az időhely már nem érhető el. Kérjük, válasszon másikat.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Próbáljon meg egy másik dátumot.\",\n\t\"MEET_WITH\": \"Találkozz\",\n\t\"MEMBER\": \"Tagja\",\n\t\"MEMBERS\": \"Tagok\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Egyszerre akár 10 felhasználót is megemlíthet.\",\n\t\"MENTION_UPTO\": \"Megemlítheti akár\",\n\t\"MESSAGE\": \"Üzenet\",\n\t\"MESSAGES\": \"Üzenetek\",\n\t\"MESSAGE_AUDIO\": \"Hang\",\n\t\"MESSAGE_COMPOSER\": \"Üzenet zeneszerző\",\n\t\"MESSAGE_COPIED\": \"Az üzenet vágólapra másolva.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Az üzenet sikeresen törölve.\",\n\t\"MESSAGE_EDITED\": \"Az üzenet sikeresen frissült.\",\n\t\"MESSAGE_FILE\": \"Fájl\",\n\t\"MESSAGE_HEADER\": \"Üzenet fejléc\",\n\t\"MESSAGE_IMAGE\": \"Kép\",\n\t\"MESSAGE_INFORMATION\": \"Üzeneti információk\",\n\t\"MESSAGE_IS_DELETED\": \"Az üzenet törölve lett\",\n\t\"MESSAGE_LIST\": \"Üzenetek listája\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} hozzáadta ${onName} felhasználót a csoporthoz\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} kitiltotta ${onName} felhasználót a csoportból\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} csatlakozott a csoporthoz\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} kirúgta ${onName} felhasználót a csoportból\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} kilépett a csoportból\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} kinevezte ${onName} felhasználót ${role} szerepkörbe\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} feloldotta ${onName} kitiltását\",\n\t\"MESSAGE_PRIVATELY\": \"Privát üzenet\",\n\t\"MESSAGE_RECEIPT\": \"Üzenet átvétele\",\n\t\"MESSAGE_TRANSLATED\": \"Az üzenet sikeresen lefordítva.\",\n\t\"MESSAGE_VIDEO\": \"Videó\",\n\t\"MICROPHONE_PERMISSION\": \"Hangüzenet rögzítéséhez hozzáférésre van szükségünk a mikrofonjához. Koppintson a Beállítások elemre, és kapcsolja be a Mikrofont.\",\n\t\"MISSED_AUDIO_CALL\": \"Hiányzott hanghívás\",\n\t\"MISSED_CALL\": \"Elmulasztott hívás\",\n\t\"MISSED_VIDEO_CALL\": \"Elmulasztott videohívás\",\n\t\"MISSED_VOICE_CALL\": \"Hiányzott hanghívás\",\n\t\"MODERATOR\": \"Előadó\",\n\t\"MONDAY\": \"Hétfő\",\n\t\"MORE\": \"Több\",\n\t\"MORE_TIMES\": \"Több alkalommal\",\n\t\"NAME\": \"Név\",\n\t\"NEW_CHAT\": \"Új csevegés\",\n\t\"NEW_MESSAGE\": \"új üzenet\",\n\t\"NEW_MESSAGES\": \"új üzenetek\",\n\t\"NEW__GROUP\": \"Új csoport\",\n\t\"NO\": \"Nem\",\n\t\"NOTIFICATIONS\": \"Értesítések\",\n\t\"NOT_SUPPORTED\": \"Ez az üzenettípus nem támogatott\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Nem találtak tiltott tagokat\",\n\t\"NO_CALLS_FOUND\": \"Nem találtak hívásokat\",\n\t\"NO_CALLS_SELECTED\": \"Nincs kiválasztott hívás\",\n\t\"NO_CALL_LOGS\": \"Még nincsenek hívásnaplók\",\n\t\"NO_CHATS_FOUND\": \"Nem találtak csevegést\",\n\t\"NO_CHATS_SELECTED\": \"Nincs kiválasztott csevegés\",\n\t\"NO_CONVERSATIONS\": \"Még nincs beszélgetés”;  \",\n\t\"NO_DOCUMENTS\": \"Nincsenek dokumentumok\",\n\t\"NO_GROUPS_AVAILABLE\": \"Nincsenek rendelkezésre álló csoportok\",\n\t\"NO_GROUPS_FOUND\": \"Nem találtak csoportokat\",\n\t\"NO_GROUPS_SELECTED\": \"Nincs kiválasztott csoport\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Nincs elérhető csoporttag\",\n\t\"NO_MESSAGES_FOUND\": \"Nem található üzenet\",\n\t\"NO_PHOTOS\": \"Nincsenek fotók\",\n\t\"NO_RECIPIENT\": \"Nincs címzett\",\n\t\"NO_RECIPIENTS\": \"Nincsenek címzettek\",\n\t\"NO_RECORDS_FOUND\": \"Nem találtak nyilvántartást\",\n\t\"NO_REPLIES_FOUND\": \"Nem található válasz\",\n\t\"NO_STICKERS_AVAILABLE\": \"Nincsenek matricák\",\n\t\"NO_STICKERS_FOUND\": \"Nem találtak matricát\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Nincs elérhető javaslat\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Nincs elérhető összefoglaló\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Nincsenek rendelkezésre álló időpontok\",\n\t\"NO_USERS_AVAILABLE\": \"Nincs elérhető felhasználó\",\n\t\"NO_USERS_FOUND\": \"Nincs felhasználó található\",\n\t\"NO_USERS_SELECTED\": \"Nincs kiválasztott felhasználó\",\n\t\"NO_VIDEOS\": \"Nincsenek videók\",\n\t\"NO_VOTE\": \"Nincs szavazás\",\n\t\"OBJECTS\": \"Tárgyak\",\n\t\"OFFLINE\": \"Offline\",\n\t\"ONGOING_AUDIO_CALL\": \"Folyamatos hanghívás\",\n\t\"ONGOING_CALL\": \"Folyamatos hívás\",\n\t\"ONGOING_VIDEO_CALL\": \"Folyamatos videohívás\",\n\t\"ONLINE\": \"Online\",\n\t\"ON_ANOTHER_CALL\": \"Újabb hívásban van\",\n\t\"OOPS\": \"HOPPÁ\",\n\t\"OOPS!\": \"HOPPÁ!\",\n\t\"OPEN_DOCUMENT\": \"Nyissa meg a dokumentumot\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Nyissa meg a dokumentumot az összeállításhoz\",\n\t\"OPEN_WHITEBOARD\": \"Nyissa meg a táblát\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Nyissa meg a táblát a rajzoláshoz\",\n\t\"OPTIONS\": \"Opciók\",\n\t\"OTHER\": \"Egyéb\",\n\t\"OTHERS\": \"mások\",\n\t\"OUTGOING_AUDIO_CALL\": \"Kimenő hanghívás\",\n\t\"OUTGOING_CALL\": \"Kimenő hívás\",\n\t\"OUTGOING_VIDEO_CALL\": \"Kimenő videohívás\",\n\t\"OWNER\": \"Tulajdonos\",\n\t\"PARTICIPANT\": \"Résztvevő\",\n\t\"PARTICIPANTS\": \"Résztvevők\",\n\t\"PASSWORD\": \"Jelszó\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"A csoport jelszava hibás!\",\n\t\"PASSWORD_PROTECTED\": \"Jelszóval védett\",\n\t\"PHOTOS\": \"Képek\",\n\t\"PICK_YOUR_EMOJI\": \"Válassza ki a hangulatjeleket\",\n\t\"POLLS\": \"Közvélemény-kutatások\",\n\t\"PREFERENCES\": \"Beállítások\",\n\t\"PRIVACY\": \"Adatvédelem\",\n\t\"PRIVACY_AND_SECURITY\": \"Adatvédelem és biztonság\",\n\t\"PRIVATE\": \"Privát\",\n\t\"PRIVATE_GROUP\": \"Magáncsoport\",\n\t\"PROTECTED_GROUP\": \"Védett csoport\",\n\t\"PUBLIC\": \"Nyilvános\",\n\t\"QUESTION\": \"Kérdés\",\n\t\"QUESTIONS\": \"Kérdések\",\n\t\"REACHED_MAX_LIMIT\": \"Elérted a határt. Legfeljebb 12 opciót adhat hozzá.\",\n\t\"REACT\": \"Reagáljon\",\n\t\"REACTED\": \"reagált\",\n\t\"REACT_TO_MESSAGE\": \"Reagáljon egy üzenetre\",\n\t\"READ\": \"Olvassa el\",\n\t\"READ_MORE\": \"Olvass tovább\",\n\t\"RECEIPT_INFORMATION\": \"Átvételi információk\",\n\t\"RECORDING\": \"Felvétel\",\n\t\"REGION\": \"Régió\",\n\t\"REJECTED_AUDIO_CALL\": \"Elutasított hanghívás\",\n\t\"REJECTED_CALL\": \"elutasított hívás\",\n\t\"REJECTED_VIDEO_CALL\": \"Elutasított videohívás\",\n\t\"REMOVE\": \"Távolítsa el\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Biztosan el akarja távolítani\",\n\t\"REPLIES\": \"Válaszok\",\n\t\"REPLY\": \"Válasz\",\n\t\"REPLY_IN_THREAD\": \"Válasz szálban\",\n\t\"REPLY_TO_THREAD\": \"Válasz a szálra\",\n\t\"REPORT_PROBLEM\": \"Hiba jelentése\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Kérjük, töltse ki az összes kötelező mezőt a szavazás létrehozása előtt\",\n\t\"RESIZE\": \"Átméretezés\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Ehhez a funkcióhoz speciális erőforrás-engedély (ek) re van szükség. Kérjük, engedélyezze a szükséges engedélyeket a készülék beállításaiban.\",\n\t\"RETRY\": \"Újrapróbál\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"A fordításhoz kiválasztott nyelv hasonló az eredeti üzenet nyelvéhez\",\n\t\"SATURDAY\": \"Szombat\",\n\t\"SAVE\": \"Mentés\",\n\t\"SCHEDULE\": \"Menetrend\",\n\t\"SCOPE\": \"Hatály\",\n\t\"SCOPE_CHANGE_INFO\": \"Megváltoztathatja a szerepeket a csoport jogosultságainak és felelősségeinek kezeléséhez\",\n\t\"SEARCH\": \"Keresés\",\n\t\"SEARCH_EMOJI\": \"Emoji keresése\",\n\t\"SEEN\": \"Látott\",\n\t\"SELECT_A_DATE\": \"Válasszon egy dátumot\",\n\t\"SELECT_DAY\": \"Válasszon egy napot\",\n\t\"SELECT_GROUP_TYPE\": \"Válassza ki a csoporttípust\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Válassza ki a bemeneti hangforrást\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Válassza ki a kimeneti hangforrást\",\n\t\"SELECT_TIME\": \"Válasszon egy időt\",\n\t\"SELECT_VIDEO_SOURCE\": \"Válassza ki a videó forrását\",\n\t\"SELECT__GROUP\": \"Válasszon ki egy csoportot az üzenetküldés elindításához\",\n\t\"SELECT__USER\": \"Válasszon ki egy felhasználót az üzenetküldés elindításához\",\n\t\"SEND\": \"Küldés\",\n\t\"SEND_MESSAGE\": \"Üzenet küldése\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Üzenet küldése privát módon\",\n\t\"SENT\": \"Elküldött\",\n\t\"SETTINGS\": \"Beállítások\",\n\t\"SET_THE_ANSWERS\": \"ÁLLÍTSA BE A VÁLASZOKAT\",\n\t\"SHARE\": \"Megosztás\",\n\t\"SHARED\": \"Megosztott\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"megosztott egy együttműködési dokumentumot\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"megosztott egy együttműködési táblát\",\n\t\"SHARED_FILE\": \"Megosztott fájl\",\n\t\"SHARED_LOCATION\": \"Megosztott hely\",\n\t\"SHARED_MEDIA\": \"Megosztott média\",\n\t\"SHOW_LESS\": \"Mutasson kevesebbet\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Biztosan nem biztonságos tartalmat szeretne látni?\",\n\t\"SMART_REPLIES\": \"Intelligens válaszok\",\n\t\"SMILEY_PEOPLE\": \"Mosolyok és emberek\",\n\t\"SOMETHING_WENT_WRONG\": \"Úgy tűnik, valami hiba történt.\",\n\t\"SOMETHING_WRONG\": \"Valami rosszul ment. Kérjük, próbálkozzon újra.\",\n\t\"SOUND_MANAGER\": \"Hangkezelő\",\n\t\"STATUS_INDICATOR\": \"Állapotjelző\",\n\t\"STICKER\": \"Matrica\",\n\t\"SUGGEST_A_REPLY\": \"Javasolj egy választ\",\n\t\"SUNDAY\": \"Vasárnap\",\n\t\"SURE_TO_DELETE_CHAT\": \"Biztos, hogy törölni szeretné ezt a csevegést? Ezt a műveletet nem lehet visszavonni.\",\n\t\"SYMBOLS\": \"Szimbólumok\",\n\t\"TAP_TO_REMOVE\": \"Koppintson az eltávolításhoz\",\n\t\"TAP_TO_START_CONVERSATION\": \"Koppints a beszélgetés indításához\",\n\t\"TEXT\": \"Szöveg\",\n\t\"TEXT_TRANSLATE\": \"Szöveg fordítása\",\n\t\"TEXT_TRANSLATED\": \"Fordított szöveg\",\n\t\"THEME\": \"Téma\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Ezt az üzenetet törölték\",\n\t\"THREAD\": \"Szál\",\n\t\"THURSDAY\": \"csütörtök\",\n\t\"TIME\": \"idő\",\n\t\"TIMES\": \"alkalmak\",\n\t\"TIME_ZONE\": \"Időzóna\",\n\t\"TODAY\": \"Ma\",\n\t\"TRANSFER\": \"Átutalás\",\n\t\"TRANSFERRING\": \"Átvitel\",\n\t\"TRANSFER_CONFIRM\": \"Ön a csoport tulajdonosa, kérjük, adja át a tulajdonjogot egy tagra, mielőtt elhagyja a csoportot\",\n\t\"TRANSFER_OWNERSHIP\": \"Tulajdonjog átruházása\",\n\t\"TRANSFER_SURE\": \"Biztosan át akarja adni a tulajdonjogot? Ezt nem lehet visszavonni, és az új tulajdonos teljes ellenőrzést fog kapni.\",\n\t\"TRANSLATE\": \"Fordítás\",\n\t\"TRANSLATED_MESSAGE\": \"Fordított üzenet\",\n\t\"TRANSLATE_MESSAGE\": \"Üzenet fordítása\",\n\t\"TRAVEL_PLACES\": \"Utazás és helyek\",\n\t\"TRY_AGAIN\": \"Próbáld újra\",\n\t\"TUESDAY\": \"Kedd\",\n\t\"TYPE\": \"Típus\",\n\t\"TYPING\": \"Gépelés...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Megválaszolatlan hanghívás\",\n\t\"UNANSWERED_CALL\": \"Megválaszolatlan hívás\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Megválaszolatlan videohívás\",\n\t\"UNBAN\": \"Unban\",\n\t\"UNBANNED\": \"nem tiltott\",\n\t\"UNBAN_SURE\": \"Biztosan fel akarja oldani a tiltást erről a felhasználóról?\",\n\t\"UNBLOCK\": \"Blokkolás feloldása\",\n\t\"UNBLOCK_CONTACT\": \"Blokkolás feloldása erről a kapcsolatról\",\n\t\"UNBLOCK_SURE\": \"Biztosan feloldja a blokkolást ennél a kapcsolattól? Újra fog üzeneteket kapni tőlük.\",\n\t\"UNBLOCK_USER\": \"Felhasználó letiltása\",\n\t\"USERS\": \"Felhasználók\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"A keresésnek megfelelő felhasználót nem találtunk. Próbálja meg módosítani a keresést.\",\n\t\"USERS_WITH_MESSAGES\": \"Üzenetekkel rendelkező felhasználók\",\n\t\"USER_INFO\": \"Felhasználói információ\",\n\t\"USER_LIST\": \"Felhasználói lista\",\n\t\"VIDEOS\": \"Videók\",\n\t\"VIDEO_CALL\": \"Videohívás\",\n\t\"VIEW\": \"Nézet\",\n\t\"VIEW_DETAIL\": \"Részletek megtekintése\",\n\t\"VIEW_MEMBERS\": \"Tagok megtekintése\",\n\t\"VIEW_ON_YOUTUBE\": \"Megtekintés a Youtube-on\",\n\t\"VIEW_PROFILE\": \"Profil megtekintése\",\n\t\"VISIT\": \"Látogatás\",\n\t\"VOICE\": \"Hang\",\n\t\"VOICE_CALL\": \"Hanghívás\",\n\t\"VOICE_RECORDING\": \"Hangfelvétel\",\n\t\"VOTE\": \"szavazás\",\n\t\"VOTES\": \"szavazatok\",\n\t\"WEDNESDAY\": \"Szerda\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Szeretné törölni ezt a beszélgetést? Ez a beszélgetés törlődik az összes eszközéről.\",\n\t\"WRONG_FILE_TYPE\": \"Másik típusú fájlt választott ki. Kérjük, válassza ki a megfelelő fájlt.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Ez a fájltípus nem engedélyezett.\",\n\t\"WRONG_PASSWORD\": \"Kérjük, adja meg a helyes jelszót, és próbálja újra\",\n\t\"WRONG_TEXT\": \"Úgy tűnik, valami félrement.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Kérjük, próbáld meg újra.\",\n\t\"YES\": \"Igen\",\n\t\"YESTERDAY\": \"Tegnap\",\n\t\"YOU\": \"Te\",\n\t\"YOU'VE_BLOCKED\": \"Letiltottad\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Már folyamatos hívásban van\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Törölted ezt az üzenetet\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Még nincsenek matricáid.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Hanghívást kezdeményezett\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Videohívást kezdeményezett\",\n\t\"meeting\": \"Találkozó\",\n\t\"Flag_Message_Title\": \"Üzenet jelentése\",\n\t\"Flag_Message_Subtitle\": \"Jelentsd ezt a beszélgetést, ha az megsérti a Közösségi Szabályzatunkat. Nem közöljük azzal a fiókkal, hogy te jelentetted őket.\",\n\t\"Flag_Message_Remark_Label\": \"Ok\",\n\t\"Flag_Message_Remark_Optional\": \"Opcionális\",\n\t\"Flag_Message_Remark_Placeholder\": \"Adjon meg további kontextust a jelentéséhez…\",\n\t\"Flag_Message_Confirm_Yes\": \"Jelentés\",\n\t\"Flag_Message_Confirm_No\": \"Mégsem\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / nem kívánt tartalom\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Kifejezett vagy nem megfelelő tartalom\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Sértő vagy fenyegető viselkedés\",\n\t\"Message_List_Option_Flag_Message\": \"Jelentés\",\n\t\"Flag_Error_Text\":\"Hiba történt. Kérjük, ellenőrizze és próbálja meg újra.\",\n\t\"Flag_Empty_Submit_Text\":\"Nem sikerült elküldeni. Kérjük, válasszon egy indokot, mielőtt bejelentené ezt az üzenetet.\",\n\t\"START_REPORTING\": \"Jelentés indítása\",\n\t\"LOGOUT\": \"Kijelentkezés\",\n\t\"AI_ASSISTANTS\": \"AI asszisztensek\",\n\t\"CREATE_CONVERSATION\": \"Beszélgetés létrehozása\",\n\t\"EDIT_MODERATION\": \"Szerkesztés sikertelen. Üzenetét moderálási irányelvek miatt blokkolták.\",\n\t\"BLOCKED_MODERATION\":\"Üzenetét moderálási\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Nincs beszélgetési előzmény\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Valami hiba történt nálunk. Kérjük, próbálja újra\",\n\t\"START_A_CHAT\": \"Kezdje el a csevegést a 'Új csevegés' gombra kattintva\",\n\t\"CHAT_HISTORY\": \"Chat előzményei\",\n\t\"ASK_ANYTHING\": \"Kérdezz bármit...\",\n\t\"AI_ASSISTANT\": \"AI asszisztens\",\n\t\"SEARCH_PLACEHOLDER\": \"Keresés...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Nincs találat\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Kezdje el a beírást az üzenetek és beszélgetések kereséséhez\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Nincs találat\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Nem találtunk egyezést. Próbáljon meg más kulcsszót.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Hiba a beszélgetések betöltésekor\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Hiba az üzenetek betöltésekor\",\n\t\"SEARCH_TRY_AGAIN\": \"Kérjük, próbálja meg később\",\n\t\"SEARCH_SEE_MORE\": \"Tovább\",\n\t\"SEARCH_LOADING\": \"Betöltés...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Olvasatlan\",\n\t\"SEARCH_FILTER_GROUPS\": \"Csoportok\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Fotók\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Videók\",\n\t\"SEARCH_FILTER_LINKS\": \"Hivatkozások\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Dokumentumok\",\n\t\"SEARCH_FILTER_AUDIO\": \"Hang\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Beszélgetések\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Üzenetek\",\n\t\"SEARCH_NO_MESSAGES\": \"Nincsenek üzenetek\",\n\t\"MESSAGE_REPORTED\": \"Üzenet jelentve\",\n\t\"MARK_AS_UNREAD\": \"Olvasatlanként jelöl\",\n\t\"NEW\": \"Új\",\n\t\"INSERT_LINK\": \"Link beszúrása\",\n\t\"EDIT_LINK\": \"Link szerkesztése\",\n\t\"LINK_TEXT\": \"Link szövege\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Beszúrás\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Nem küldhet üzenetet ebbe a csoportba, mert már nem tag.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/it/translation.json",
    "content": "{\n\t\"INFO\": \"Info\",\n\t\"REACT\": \"Reagisci\",\n\t\"EDIT\": \"Modifica\",\n\t\"MESSAGE_PRIVATELY\": \"Messaggio privato\",\n\t\"TRANSLATE\": \"Traduci\",\n\t\"USERS\": \"Utenti\",\n\t\"CHATS\": \"Chat\",\n\t\"SEARCH_EMOJI\": \"Cerca emoji\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"tutti\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notifica a tutti in questo gruppo\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Vuoi eliminare questa conversazione? Questa conversazione verrà eliminata da tutti i tuoi dispositivi.\",\n\t\"GROUPS\": \"Gruppi\",\n\t\"SHARED\": \"Condivisi\",\n\t\"SOUND_MANAGER\": \"Gestore audio\",\n\t\"THEME\": \"Tema\",\n\t\"DELETE_MSG_TEXT\": \"Questo messaggio è stato eliminato\",\n\t\"LOCALIZE\": \"Localizza\",\n\t\"CONVERSATION_LIST_ITEM\": \"Elemento lista conversazioni\",\n\t\"DATA_ITEM\": \"Elemento dati\",\n\t\"STATUS_INDICATOR\": \"Indicatore di stato\",\n\t\"BADGE_COUNT\": \"Conteggio badge\",\n\t\"MESSAGE_RECEIPT\": \"Ricevuta messaggio\",\n\t\"MESSAGE\": \"Messaggio\",\n\t\"RECEIPT_INFORMATION\": \"Informazioni ricevuta\",\n\t\"NO_RECIPIENT\": \"Nessun destinatario\",\n\t\"NO_RECIPIENTS\": \"Nessun destinatario\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Conversazioni con messaggi\",\n\t\"CONVERSATIONS\": \"Conversazioni\",\n\t\"CONVERSATION_LIST\": \"Lista conversazioni\",\n\t\"MESSAGES\": \"Messaggi\",\n\t\"WRONG_FILE_TYPE\": \"Hai selezionato un tipo di file diverso. Scegli il file appropriato.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Questo tipo di file non è consentito.\",\n\t\"MESSAGE_HEADER\": \"Intestazione messaggio\",\n\t\"MESSAGE_LIST\": \"Lista messaggi\",\n\t\"MESSAGE_COMPOSER\": \"Compositore messaggi\",\n\t\"USERS_WITH_MESSAGES\": \"Utenti con messaggi\",\n\t\"USER_LIST\": \"Lista utenti\",\n\t\"GROUP_LIST\": \"Lista gruppi\",\n\t\"GROUPS_WITH_MESSAGES\": \"Gruppi con messaggi\",\n\t\"NEW__GROUP\": \"Nuovo gruppo\",\n\t\"PASSWORD\": \"Password\",\n\t\"CONTINUE\": \"Continua\",\n\t\"NO_CHATS_SELECTED\": \"Nessuna chat selezionata\",\n\t\"NO_USERS_SELECTED\": \"Nessun utente selezionato\",\n\t\"NO_GROUPS_SELECTED\": \"Nessun gruppo selezionato\",\n\t\"SELECT_DAY\": \"Seleziona un giorno\",\n\t\"SELECT_TIME\": \"Seleziona un orario\",\n\t\"NO_CALLS_SELECTED\": \"Nessuna chiamata selezionata\",\n\t\"SELECT__GROUP\": \"Seleziona un gruppo per iniziare a inviare messaggi\",\n\t\"SELECT__USER\": \"Seleziona un utente per iniziare a inviare messaggi\",\n\t\"GROUP_PASSWORD_BLANK\": \"La password del gruppo non può essere vuota\",\n\t\"GROUP_NAME_BLANK\": \"Il nome del gruppo non può essere vuoto\",\n\t\"GROUP_TYPE_BLANK\": \"Il tipo di gruppo non può essere vuoto\",\n\t\"DELETE_CONVERSATION\": \"Eliminare conversazione?\",\n\t\"ADD_TO_CHAT\": \"Aggiungi alla chat\",\n\t\"MORE\": \"Altro\",\n\t\"COPY\": \"Copia\",\n\t\"VOICE_RECORDING\": \"Registrazione vocale\",\n\t\"MESSAGE_IMAGE\": \"Immagine\",\n\t\"MESSAGE_FILE\": \"File\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"CUSTOM_MESSAGE\": \"Hai un messaggio\",\n\t\"SELECT_A_DATE\": \"Seleziona una data\",\n\t\"TIME_ZONE\": \"Fuso orario\",\n\t\"MEETING_SCHEDULED\": \"Il tuo meeting è stato programmato.\",\n\t\"SOMETHING_WRONG\": \"Qualcosa è andato storto. Riprova.\",\n\t\"MEETING_SLOT_BOOK\": \"Slot orario non più disponibile. Scegli un altro.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Prenota nuovo slot\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Nessuno slot orario disponibile per questa data. Prova un'altra data.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Prova una data diversa.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Nessuno slot orario disponibile\",\n\t\"SCHEDULE\": \"Programma\",\n\t\"MORE_TIMES\": \"Altri orari\",\n\t\"MISSED_VOICE_CALL\": \"Chiamata vocale persa\",\n\t\"MEET_WITH\": \"Incontra con\",\n\t\"CONVERSATION_DELETED\": \"Conversazione eliminata\",\n\t\"MEETING_SCHEDULER\": \"Programmatore incontri\",\n\t\"MISSED_VIDEO_CALL\": \"Videochiamata persa\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Sondaggio\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Adesivo\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Documento\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Lavagna\",\n\t\"ONLINE\": \"Online\",\n\t\"ADMINISTRATOR\": \"Amministratore\",\n\t\"ADMIN\": \"Admin\",\n\t\"MODERATOR\": \"Moderatore\",\n\t\"PARTICIPANT\": \"Partecipante\",\n\t\"PUBLIC\": \"Pubblico\",\n\t\"PRIVATE\": \"Privato\",\n\t\"PASSWORD_PROTECTED\": \"Protetto da password\",\n\t\"PRIVACY_AND_SECURITY\": \"Privacy e sicurezza\",\n\t\"PREFERENCES\": \"Preferenze\",\n\t\"MEMBERS\": \"Membri\",\n\t\"MEMBER\": \"Membro\",\n\t\"EDITED\": \"Modificato\",\n\t\"TODAY\": \"Oggi\",\n\t\"YESTERDAY\": \"Ieri\",\n\t\"SUNDAY\": \"Domenica\",\n\t\"MONDAY\": \"Lunedì\",\n\t\"TUESDAY\": \"Martedì\",\n\t\"WEDNESDAY\": \"Mercoledì\",\n\t\"THURSDAY\": \"Giovedì\",\n\t\"FRIDAY\": \"Venerdì\",\n\t\"SATURDAY\": \"Sabato\",\n\t\"TYPING\": \"Digitando...\",\n\t\"IS_TYPING\": \"sta scrivendo...\",\n\t\"CLOSE\": \"Chiudi\",\n\t\"ENTER_GROUP_NAME\": \"Inserisci nome gruppo\",\n\t\"ADD_MEMBERS\": \"Aggiungi membri\",\n\t\"SEND_MESSAGE\": \"Invia messaggio\",\n\t\"UNBLOCK_USER\": \"Sblocca utente\",\n\t\"BLOCK_USER\": \"Blocca utente\",\n\t\"DELETE_AND_EXIT\": \"Elimina ed esci\",\n\t\"LEAVE_GROUP\": \"Abbandona gruppo\",\n\t\"CREATE_GROUP\": \"Crea gruppo\",\n\t\"SHARED_MEDIA\": \"Media condivisi\",\n\t\"SHARED_FILE\": \"File condiviso\",\n\t\"VIDEO_CALL\": \"Videochiamata\",\n\t\"AUDIO_CALL\": \"Chiamata audio\",\n\t\"LOADING\": \"Caricamento...\",\n\t\"REPLY\": \"Rispondi\",\n\t\"REPLIES\": \"Risposte\",\n\t\"AI\": \"IA\",\n\t\"SMART_REPLIES\": \"Risposte intelligenti\",\n\t\"CONVERSATION_STARTER\": \"Iniziatore conversazione\",\n\t\"LAUNCH\": \"Avvia\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"ha condiviso un documento collaborativo\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"ha condiviso una lavagna collaborativa\",\n\t\"CREATED_WHITEBOARD\": \"Hai creato una nuova lavagna collaborativa\",\n\t\"CREATED_DOCUMENT\": \"Hai creato un nuovo documento collaborativo\",\n\t\"PHOTOS\": \"Foto\",\n\t\"VIDEOS\": \"Video\",\n\t\"DOCUMENT\": \"Documento\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Hai eliminato questo messaggio\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Questo messaggio è stato eliminato\",\n\t\"MESSAGE_IS_DELETED\": \"Il messaggio è stato eliminato\",\n\t\"MESSAGE_COPIED\": \"Messaggio copiato negli appunti.\",\n\t\"MESSAGE_EDITED\": \"Messaggio aggiornato con successo.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Messaggio eliminato con successo.\",\n\t\"MESSAGE_TRANSLATED\": \"Messaggio tradotto con successo.\",\n\t\"VIEW_ON_YOUTUBE\": \"Visualizza su Youtube\",\n\t\"SEARCH\": \"Cerca\",\n\t\"ERROR\": \"Errore\",\n\t\"ERROR_TEXT\": \"Sembra che qualcosa sia andato storto. Riprova.\",\n\t\"NO_GROUPS_FOUND\": \"Nessun gruppo trovato\",\n\t\"NO_CHATS_FOUND\": \"Nessuna chat trovata\",\n\t\"CANT__LOAD__CHATS\": \"Impossibile caricare le chat\",\n\t\"MEDIA_MESSAGE\": \"Messaggio multimediale\",\n\t\"INCOMING_AUDIO_CALL\": \"Chiamata audio in arrivo\",\n\t\"INCOMING_VIDEO_CALL\": \"Videochiamata in arrivo\",\n\t\"DECLINE\": \"Rifiuta\",\n\t\"ACCEPT\": \"Accetta\",\n\t\"INCOMING_CALL\": \"Chiamata in arrivo\",\n\t\"OUTGOING_CALL\": \"Chiamata in uscita\",\n\t\"CALL_REJECTED\": \"Chiamata rifiutata\",\n\t\"CALL_ANSWERED\": \"Chiamata risposta\",\n\t\"CALL_CANCELLED\": \"Chiamata annullata\",\n\t\"MISSED_CALL\": \"Chiamata persa\",\n\t\"CALL_UNANSWERED\": \"Chiamata senza risposta\",\n\t\"CALL_INITIATED\": \"Chiamata avviata\",\n\t\"OUTGOING_AUDIO_CALL\": \"Chiamata audio in uscita\",\n\t\"OUTGOING_VIDEO_CALL\": \"Videochiamata in uscita\",\n\t\"REJECTED_CALL\": \"chiamata rifiutata\",\n\t\"CALL_ACCEPTED\": \"Chiamata accettata\",\n\t\"JOINED\": \"si è unito\",\n\t\"LEFT_THE_CALL\": \"ha abbandonato la chiamata\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Chiamata audio senza risposta\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Videochiamata senza risposta\",\n\t\"CALL_ENDED\": \"Chiamata terminata\",\n\t\"CALL_BUSY\": \"Chiamata occupata\",\n\t\"CALLING\": \"Chiamando...\",\n\t\"ADD\": \"Aggiungi\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Nessun membro bannato trovato\",\n\t\"BANNED_MEMBERS\": \"Membri bannati\",\n\t\"NAME\": \"Nome\",\n\t\"SCOPE\": \"Ambito\",\n\t\"UNBAN\": \"Rimuovi ban\",\n\t\"SELECT_GROUP_TYPE\": \"Seleziona tipo di gruppo\",\n\t\"ENTER_GROUP_PASSWORD\": \"Inserisci password gruppo\",\n\t\"CREATE\": \"Crea\",\n\t\"CREATE_POLL\": \"Sondaggio\",\n\t\"QUESTION\": \"Domanda\",\n\t\"ENTER_YOUR_QUESTION\": \"Inserisci la tua domanda\",\n\t\"OPTIONS\": \"Opzioni\",\n\t\"ENTER_YOUR_OPTION\": \"Inserisci la tua opzione\",\n\t\"ADD_NEW_OPTION\": \"Aggiungi nuova opzione\",\n\t\"VIEW_MEMBERS\": \"Visualizza membri\",\n\t\"DETAILS\": \"Dettagli\",\n\t\"NOTIFICATIONS\": \"Notifiche\",\n\t\"OTHER\": \"Altro\",\n\t\"HELP\": \"Aiuto\",\n\t\"REPORT_PROBLEM\": \"Segnala un problema\",\n\t\"GROUP_MEMBERS\": \"Membri del gruppo\",\n\t\"BAN\": \"Banna\",\n\t\"KICK\": \"Espelli\",\n\t\"PICK_YOUR_EMOJI\": \"Scegli la tua emoji\",\n\t\"PRIVATE_GROUP\": \"Gruppo privato\",\n\t\"PROTECTED_GROUP\": \"Gruppo protetto\",\n\t\"VISIT\": \"Visita\",\n\t\"ATTACH\": \"Allega\",\n\t\"OPEN_WHITEBOARD\": \"Apri lavagna\",\n\t\"ATTACH_FILE\": \"Allega file\",\n\t\"GENERATING_REPLIES\": \"Generando risposte\",\n\t\"ATTACH_VIDEO\": \"Allega video\",\n\t\"ATTACH_AUDIO\": \"Allega audio\",\n\t\"ATTACH_IMAGE\": \"Allega immagine\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Collabora usando un documento\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Collabora usando una lavagna\",\n\t\"SUGGEST_A_REPLY\": \"Suggerisci una risposta\",\n\t\"GENERATING_ICEBREAKERS\": \"Generando rompighiaccio\",\n\t\"EMOJI\": \"Emoji\",\n\t\"NO_REPLIES_FOUND\": \"Nessuna risposta trovata\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Inserisci qui il tuo messaggio\",\n\t\"NO_MESSAGES_FOUND\": \"Nessun messaggio trovato\",\n\t\"THREAD\": \"Thread\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Documento collaborativo\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Lavagna collaborativa\",\n\t\"ADD_REACTION\": \"Aggiungi reazione\",\n\t\"NO_STICKERS_FOUND\": \"Nessun adesivo trovato\",\n\t\"REPLY_TO_THREAD\": \"Rispondi al thread\",\n\t\"REPLY_IN_THREAD\": \"Rispondi nel thread\",\n\t\"VIEW\": \"Visualizza\",\n\t\"DELETE_MESSAGE\": \"Elimina messaggio\",\n\t\"EDIT_MESSAGE\": \"Modifica messaggio\",\n\t\"OWNER\": \"Proprietario\",\n\t\"CHANGE_SCOPE\": \"Cambia ambito\",\n\t\"STICKER\": \"Adesivo\",\n\t\"LAST_ACTIVE_AT\": \"Ultima attività\",\n\t\"LAST_SEEN\": \"Ultima volta\",\n\t\"AT\": \"alle\",\n\t\"VOICE_CALL\": \"Chiamata vocale\",\n\t\"VIEW_DETAIL\": \"Visualizza dettaglio\",\n\t\"VOTES\": \"voti\",\n\t\"VOTE\": \"voto\",\n\t\"NO_VOTE\": \"Nessun voto\",\n\t\"REACTED\": \"ha reagito\",\n\t\"ADDED\": \"aggiunto\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Sei sicuro di voler vedere contenuti non sicuri?\",\n\t\"REACT_TO_MESSAGE\": \"Reagisci a un messaggio\",\n\t\"UNBANNED\": \"ban rimosso\",\n\t\"MADE\": \"ha reso\",\n\t\"MISSED_AUDIO_CALL\": \"Chiamata audio persa\",\n\t\"ENTER_YOUR_PASSWORD\": \"Inserisci la tua password\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Apri documento per modificare contenuti insieme\",\n\t\"DOCS\": \"Documenti\",\n\t\"NO_RECORDS_FOUND\": \"Nessun record trovato\",\n\t\"LIVE_REACTION\": \"Reazione dal vivo\",\n\t\"SMILEY_PEOPLE\": \"Emoticon e persone\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Apri lavagna per disegnare insieme\",\n\t\"ANIMALES_NATURE\": \"Animali e natura\",\n\t\"FOOD_DRINK\": \"Cibo e bevande\",\n\t\"OPEN_DOCUMENT\": \"Apri documento\",\n\t\"ACTIVITY\": \"Attività\",\n\t\"TRAVEL_PLACES\": \"Viaggi e luoghi\",\n\t\"OBJECTS\": \"Oggetti\",\n\t\"SYMBOLS\": \"Simboli\",\n\t\"FLAGS\": \"Bandiere\",\n\t\"SENT\": \"Inviato\",\n\t\"SEEN\": \"Visto\",\n\t\"DELIVERED\": \"Consegnato\",\n\t\"READ\": \"Letto\",\n\t\"MESSAGE_INFORMATION\": \"Info messaggio\",\n\t\"TRANSLATE_MESSAGE\": \"Traduci messaggio\",\n\t\"TRANSLATED_MESSAGE\": \"Messaggio tradotto\",\n\t\"LEFT\": \"ha abbandonato\",\n\t\"KICKED\": \"espulso\",\n\t\"BANNED\": \"bannato\",\n\t\"NEW_MESSAGES\": \"nuovi messaggi\",\n\t\"NEW_MESSAGE\": \"nuovo messaggio\",\n\t\"JUMP\": \"Salta\",\n\t\"SELECT_VIDEO_SOURCE\": \"Seleziona sorgente video\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Seleziona sorgente audio input\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Seleziona sorgente audio output\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"ha avviato una chiamata audio\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"ha avviato una videochiamata\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Hai avviato una chiamata audio\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Hai avviato una videochiamata\",\n\t\"IGNORE\": \"Ignora\",\n\t\"ON_ANOTHER_CALL\": \"è in un'altra chiamata\",\n\t\"CREATING\": \"Creazione\",\n\t\"AVATAR\": \"Avatar\",\n\t\"ONGOING_CALL\": \"Chiamata in corso\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Sei già in una chiamata in corso\",\n\t\"RESIZE\": \"Ridimensiona\",\n\t\"READ_MORE\": \"Leggi di più\",\n\t\"SHOW_LESS\": \"Mostra meno\",\n\t\"SETTINGS\": \"Impostazioni\",\n\t\"ACTIONS\": \"Azioni\",\n\t\"VIEW_PROFILE\": \"Visualizza profilo\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Invia messaggio in privato\",\n\t\"DELETE\": \"Elimina\",\n\t\"DELETE_CONFIRM\": \"Sei sicuro di voler eliminare?\",\n\t\"DELETE_CHAT\": \"Eliminare questa chat?\",\n\t\"SURE_TO_DELETE_CHAT\": \"Sei sicuro di voler eliminare questa chat? Questa azione non può essere annullata.\",\n\t\"CANCEL\": \"Annulla\",\n\t\"LEAVE_CONFIRM\": \"Sei sicuro di voler abbandonare il gruppo?\",\n\t\"TRANSFER_CONFIRM\": \"Sei il proprietario del gruppo, trasferisci la proprietà a un membro prima di abbandonare il gruppo\",\n\t\"ADDING\": \"Aggiungendo...\",\n\t\"TRANSFER\": \"Trasferisci\",\n\t\"TRANSFER_OWNERSHIP\": \"Trasferisci proprietà\",\n\t\"TRANSFERRING\": \"Trasferimento\",\n\t\"YES\": \"Sì\",\n\t\"NO\": \"No\",\n\t\"ANSWER\": \"Risposta\",\n\t\"ADD_ANOTHER_ANSWER\": \"Aggiungi un'altra risposta\",\n\t\"SET_THE_ANSWERS\": \"IMPOSTA LE RISPOSTE\",\n\t\"TRY_AGAIN\": \"Riprova\",\n\t\"INVALID_GROUP_NAME\": \"Inserisci un nome valido per il gruppo e riprova\",\n\t\"INVALID_PASSWORD\": \"Inserisci una password valida per il gruppo e riprova\",\n\t\"INVALID_GROUP_TYPE\": \"Inserisci un tipo valido per il gruppo e riprova\",\n\t\"WRONG_PASSWORD\": \"Inserisci la password corretta e riprova\",\n\t\"INVALID_POLL_QUESTION\": \"Inserisci la domanda richiesta prima di creare un sondaggio\",\n\t\"INVALID_POLL_OPTION\": \"Inserisci la risposta richiesta prima di creare un sondaggio\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"La lingua selezionata per la traduzione è simile alla lingua del messaggio originale\",\n\t\"LEAVE\": \"Abbandona\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Clicca per iniziare una conversazione\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍Posizione\",\n\t\"SHARED_LOCATION\": \"Posizione condivisa\",\n\t\"IN_A_THREAD\": \"In un thread\",\n\t\"CALLS\": \"Chiamate\",\n\t\"CALL_DETAILS\": \"Dettagli chiamata\",\n\t\"OFFLINE\": \"Offline\",\n\t\"POLLS\": \"Sondaggi\",\n\t\"YOU\": \"Tu\",\n\t\"PRIVACY\": \"Privacy\",\n\t\"BLOCKED_USERS\": \"Utenti bloccati\",\n\t\"YOU'VE_BLOCKED\": \"Hai bloccato\",\n\t\"NO_PHOTOS\": \"Nessuna foto\",\n\t\"NO_VIDEOS\": \"Nessun video\",\n\t\"NO_DOCUMENTS\": \"Nessun documento\",\n\t\"JOIN\": \"Partecipa\",\n\t\"REMOVE\": \"Rimuovi\",\n\t\"BLOCK\": \"Blocca\",\n\t\"CHANGE_ROLE\": \"Cambia ruolo\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Puoi cambiare i ruoli per gestire permessi e responsabilità del gruppo.\",\n\t\"CHANGE_TO\": \"Cambia in\",\n\t\"NEW_CHAT\": \"Nuova chat\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Grazie per aver compilato il modulo.\",\n\t\"HISTORY\": \"Cronologia\",\n\t\"CANCELLED_AUDIO_CALL\": \"Chiamata audio annullata\",\n\t\"CANCELLED_VIDEO_CALL\": \"Videochiamata annullata\",\n\t\"REJECTED_AUDIO_CALL\": \"Chiamata audio rifiutata\",\n\t\"REJECTED_VIDEO_CALL\": \"Videochiamata rifiutata\",\n\t\"CANCELLED_CALL\": \"Chiamata annullata\",\n\t\"UNANSWERED_CALL\": \"Chiamata senza risposta\",\n\t\"RECORDING\": \"Registrazione\",\n\t\"PARTICIPANTS\": \"Partecipanti\",\n\t\"CALL_HISTORY\": \"Cronologia chiamate\",\n\t\"NO_CALLS_FOUND\": \"Nessuna chiamata trovata\",\n\t\"ONGOING_AUDIO_CALL\": \"Chiamata audio in corso\",\n\t\"ONGOING_VIDEO_CALL\": \"Videochiamata in corso\",\n\t\"CALL_DETAIL\": \"Dettaglio chiamata\",\n\t\"FORM\": \"Modulo\",\n\t\"CARD\": \"Scheda\",\n\t\"GENERATING_SUMMARY\": \"Generando riepilogo\",\n\t\"CONVERSATION_SUMMARY\": \"Riepilogo conversazione\",\n\t\"GENERATE_SUMMARY\": \"Genera un riepilogo\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Come posso aiutarti con questa conversazione? Fammi una domanda e ti darò un consiglio 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"Chiedi\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Chiedi ai bot IA\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"Bot IA\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Puoi menzionare fino a 10 utenti alla volta.\",\n\t\"ALL\": \"Tutti\",\n\t\"CLICK_TO_REMOVE\": \"Clicca per rimuovere\",\n\t\"OTHERS\": \"altri\",\n\t\"AND\": \"e\",\n\t\"ASK_QUESTION\": \"Fai una domanda\",\n\t\"ADD_OPTION\": \"Aggiungi opzione\",\n\t\"SEND\": \"Invia\",\n\t\"FULL_SCREEN_VIEWER\": \"Visualizzatore a schermo intero\",\n\t\"TEXT_TRANSLATE\": \"Traduzione testo\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Sembra che qualcosa sia andato storto\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Nessun riepilogo disponibile\",\n\t\"QUESTIONS\": \"Domande\",\n\t\"NO_STICKERS_AVAILABLE\": \"Nessun adesivo disponibile\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Nessun suggerimento disponibile\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Crea o partecipa ai gruppi per vederli elencati qui e iniziare a collaborare\",\n\t\"NO_USERS_AVAILABLE\": \"Nessun utente disponibile\",\n\t\"NO_GROUPS_AVAILABLE\": \"Nessun gruppo disponibile\",\n\t\"NO_CALL_LOGS\": \"Nessun registro chiamate\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Effettua o ricevi chiamate per vedere la tua cronologia chiamate qui\",\n\t\"NO_CONVERSATIONS\": \"Nessuna conversazione\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Inizia una nuova chat o invita altri a unirsi alla conversazione.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Nessun membro del gruppo disponibile\",\n\t\"OOPS!\": \"OOPS!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"In attesa che i destinatari ricevano e visualizzino il messaggio\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Aggiungi contatti per vederli elencati qui.\",\n\t\"REACHED_MAX_LIMIT\": \"Hai raggiunto il limite. Puoi aggiungere fino a 12 opzioni.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Compila tutti i campi obbligatori prima di creare un sondaggio.\",\n\t\"NOT_SUPPORTED\": \"Questo tipo di messaggio non è supportato\",\n\t\"NO_USERS_FOUND\": \"Nessun utente trovato\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Non siamo riusciti a trovare utenti corrispondenti alla tua ricerca. Prova a modificare la tua ricerca.\",\n\t\"SAVE\": \"Salva\",\n\t\"RETRY\": \"Riprova\",\n\t\"TAP_TO_START_CONVERSATION\": \"Tocca per iniziare una conversazione\",\n\t\"TEXT_TRANSLATED\": \"Testo tradotto\",\n\t\"CAMERA\": \"Fotocamera\",\n\t\"SHARE\": \"Condividi\",\n\t\"ATTACH_DOCUMENT\": \"Allega documento\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Sei sicuro di voler rimuovere \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Scrivi il tuo messaggio...\",\n\t\"TAP_TO_REMOVE\": \"Tocca per rimuovere\",\n\t\"ADD_CONTACTS\": \"Aggiungi contatti per iniziare conversazioni e vederli elencati qui.\",\n\t\"SCOPE_CHANGE_INFO\": \"Puoi cambiare i ruoli per gestire permessi e responsabilità del gruppo\",\n\t\"SOMETHING_WENT_WRONG\": \"Sembra che qualcosa sia andato storto.\",\n\t\"MENTION_UPTO\": \"Puoi menzionare fino a\",\n\t\"TIME\": \"volta\",\n\t\"TIMES\": \"volte\",\n\t\"AT_A_TIME\": \"alla volta\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Questa funzione richiede autorizzazioni specifiche. Abilita le autorizzazioni necessarie nelle impostazioni del dispositivo.\",\n\t\"MICROPHONE_PERMISSION\": \"Per registrare un messaggio vocale, abbiamo bisogno di accedere al tuo microfono. Tocca Impostazioni e attiva Microfono.\",\n\t\"CAMERA_PERMISSION\": \"Non abbiamo accesso alla tua fotocamera. Per abilitare l'accesso, tocca Impostazioni e attiva Fotocamera.\",\n\t\"OOPS\": \"Oops!\",\n\t\"ADD_OPTIONS\": \"Aggiungi opzioni\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Apri documento per disegnare insieme\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Apri lavagna per disegnare insieme\",\n\t\"TEXT\": \"Testo\",\n\t\"BAN_MEMBER_CONFIRM\": \"Sei sicuro di voler bannare \",\n\t\"LOGIN\": \"Accedi\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Continua con Google\",\n\t\"JOIN_GROUP\": \"Unisciti al gruppo\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"La password per il gruppo non è corretta!\",\n\t\"ENTER_PASSWORD\": \"Inserisci password\",\n\t\"TYPE\": \"Tipo\",\n\t\"USER_INFO\": \"Info utente\",\n\t\"DELETE_CHAT_TEXT\": \"Elimina chat\",\n\t\"UNBLOCK\": \"Sblocca\",\n\t\"UNBLOCK_CONTACT\": \"Sblocca questo contatto\",\n\t\"BLOCK_CONTACT\": \"Bloccare questo contatto?\",\n\t\"UNBLOCK_SURE\": \"Sei sicuro di voler sbloccare questo contatto? Inizierai a ricevere nuovamente messaggi da loro.\",\n\t\"BLOCK_SURE\": \"Sei sicuro di voler bloccare questo contatto? Non riceverai più messaggi da loro.\",\n\t\"VOICE\": \"Voce\",\n\t\"DELETE_AND_EXIT_SURE\": \"Sei sicuro di voler eliminare questa chat e uscire dal gruppo? Questa azione non può essere annullata.\",\n\t\"TRANSFER_SURE\": \"Sei sicuro di voler trasferire la proprietà? Questo non può essere annullato, e il nuovo proprietario avrà il pieno controllo.\",\n\t\"GROUP_INFO\": \"Info gruppo\",\n\t\"LEAVE_GROUP_TEXT\": \"Abbandona questo gruppo\",\n\t\"LEAVE_SURE\": \"Sei sicuro di voler abbandonare il gruppo? Non riceverai più messaggi da questa chat.\",\n\t\"UNBAN_SURE\": \"Sei sicuro di voler rimuovere il ban a questo utente?\",\n\t\"DELETE_THIS_CONVERSATION\": \"Eliminare questa conversazione?\",\n\t\"LINK\": \"Link\",\n\t\"DELETE_THIS_MESSAGE\": \"Eliminare questo messaggio?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Sei sicuro di voler eliminare questo messaggio? Questa azione non può essere annullata.\",\n\t\"APP_CREDENTIALS\": \"Credenziali app\",\n\t\"REGION\": \"Regione\",\n\t\"CHAT_PRIVATELY\": \"Chatta privatamente\",\n\t\"WRONG_TEXT\": \"Sembra che qualcosa sia andato storto.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Riprova.\",\n\t\"BLOCKED_USER_DESC\": \"Impossibile inviare un messaggio perché l'utente è bloccato\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} ha aggiunto ${onName} al gruppo\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} si è unito al gruppo\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} ha abbandonato il gruppo\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} ha espulso ${onName} dal gruppo\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} ha bannato ${onName} dal gruppo\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} ha rimosso il ban a ${onName}\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} ha reso ${onName} ${role}\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Non hai ancora nessuno sticker.\",\n\t\"meeting\": \"Riunione\",\n\t\"Flag_Message_Title\": \"Segnala un messaggio\",\n\t\"Flag_Message_Subtitle\": \"Segnala questa chat se viola i nostri Standard della comunità. Non informeremo l’account che hai segnalato.\",\n\t\"Flag_Message_Remark_Label\": \"Motivo\",\n\t\"Flag_Message_Remark_Optional\": \"Opzionale\",\n\t\"Flag_Message_Remark_Placeholder\": \"Fornisci contesto aggiuntivo per la tua segnalazione…\",\n\t\"Flag_Message_Confirm_Yes\": \"Segnala\",\n\t\"Flag_Message_Confirm_No\": \"Annulla\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / contenuto indesiderato\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Contenuto esplicito o inappropriato\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Comportamento offensivo o minaccioso\",\n\t\"Message_List_Option_Flag_Message\": \"Segnala\",\n    \"Flag_Error_Text\":\"Si è verificato un errore. Per favore controlla e riprova.\",\n\t\"Flag_Empty_Submit_Text\":\"Impossibile inviare. Seleziona un motivo prima di segnalare questo messaggio.\",\n\t\"START_REPORTING\": \"Avvia report\",\n\t\"LOGOUT\": \"Esci\",\n\t\"AI_ASSISTANTS\": \"Assistenti AI\",\n\t\"CREATE_CONVERSATION\": \"Crea conversazione\",\n\t\"EDIT_MODERATION\": \"Modifica non riuscita. Il tuo messaggio è stato bloccato a causa delle politiche di moderazione.\",\n    \"BLOCKED_MODERATION\":\"Il tuo messaggio è stato bloccato a causa delle politiche di moderazione.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Nessun storico delle conversazioni trovato\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Qualcosa è andato storto da parte nostra. Per favore, riprova.\",\n\t\"START_A_CHAT\": \"Inizia una chat toccando il pulsante 'Nuova conversazione'\",\n\t\"CHAT_HISTORY\": \"Cronologia delle conversazioni\",\n\t\"ASK_ANYTHING\": \"Chiedi qualsiasi cosa...\",\n\t\"AI_ASSISTANT\": \"Assistente AI\",\n\t\"SEARCH_PLACEHOLDER\": \"Cerca...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Nessun Risultato\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Inizia a digitare per cercare messaggi e conversazioni\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Nessun risultato trovato\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Non abbiamo trovato corrispondenze. Prova con un'altra parola chiave.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Errore nel caricamento delle conversazioni\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Errore nel caricamento dei messaggi\",\n\t\"SEARCH_TRY_AGAIN\": \"Riprova più tardi\",\n\t\"SEARCH_SEE_MORE\": \"Vedi Altro\",\n\t\"SEARCH_LOADING\": \"Caricamento...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Non letti\",\n\t\"SEARCH_FILTER_GROUPS\": \"Gruppi\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Foto\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Video\",\n\t\"SEARCH_FILTER_LINKS\": \"Link\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Documenti\",\n\t\"SEARCH_FILTER_AUDIO\": \"Audio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Conversazioni\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Messaggi\",\n\t\"SEARCH_NO_MESSAGES\": \"Nessun messaggio\",\n\t\"MESSAGE_REPORTED\": \"Messaggio segnalato\",\n\t\"MARK_AS_UNREAD\": \"Contrassegna non letto\",\n\t\"NEW\": \"Nuovo\",\n\t\"INSERT_LINK\": \"Inserisci link\",\n\t\"EDIT_LINK\": \"Modifica link\",\n\t\"LINK_TEXT\": \"Testo del link\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Inserisci\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Non puoi inviare messaggi a questo gruppo perché non sei più un membro.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/ja/translation.json",
    "content": "{\n\t\"INFO\": \"情報\",\n\t\"REACT\": \"リアクション\",\n\t\"EDIT\": \"編集\",\n\t\"MESSAGE_PRIVATELY\": \"プライベートメッセージ\",\n\t\"TRANSLATE\": \"翻訳\",\n\t\"USERS\": \"ユーザー\",\n\t\"CHATS\": \"チャット\",\n\t\"SEARCH_EMOJI\": \"絵文字を検索\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"この会話を削除しますか？この会話はすべてのデバイスから削除されます。\",\n\t\"GROUPS\": \"グループ\",\n\t\"SHARED\": \"共有\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"すべて\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"このグループの全員に通知する\",\n\t\"SOUND_MANAGER\": \"サウンド管理\",\n\t\"THEME\": \"テーマ\",\n\t\"DELETE_MSG_TEXT\": \"このメッセージは削除されました\",\n\t\"LOCALIZE\": \"ローカライズ\",\n\t\"CONVERSATION_LIST_ITEM\": \"会話リストアイテム\",\n\t\"DATA_ITEM\": \"データアイテム\",\n\t\"STATUS_INDICATOR\": \"ステータスインジケーター\",\n\t\"BADGE_COUNT\": \"バッジカウント\",\n\t\"MESSAGE_RECEIPT\": \"メッセージ受信確認\",\n\t\"MESSAGE\": \"メッセージ\",\n\t\"RECEIPT_INFORMATION\": \"受信情報\",\n\t\"NO_RECIPIENT\": \"受信者なし\",\n\t\"NO_RECIPIENTS\": \"受信者なし\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"メッセージのある会話\",\n\t\"CONVERSATIONS\": \"会話\",\n\t\"CONVERSATION_LIST\": \"会話リスト\",\n\t\"MESSAGES\": \"メッセージ\",\n\t\"WRONG_FILE_TYPE\": \"異なるタイプのファイルを選択しました。適切なファイルを選択してください。\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"このファイル形式は許可されていません。\",\n\t\"MESSAGE_HEADER\": \"メッセージヘッダー\",\n\t\"MESSAGE_LIST\": \"メッセージリスト\",\n\t\"MESSAGE_COMPOSER\": \"メッセージ作成\",\n\t\"USERS_WITH_MESSAGES\": \"メッセージのあるユーザー\",\n\t\"USER_LIST\": \"ユーザーリスト\",\n\t\"GROUP_LIST\": \"グループリスト\",\n\t\"GROUPS_WITH_MESSAGES\": \"メッセージのあるグループ\",\n\t\"NEW__GROUP\": \"新規グループ\",\n\t\"PASSWORD\": \"パスワード\",\n\t\"CONTINUE\": \"続ける\",\n\t\"NO_CHATS_SELECTED\": \"チャットが選択されていません\",\n\t\"NO_USERS_SELECTED\": \"ユーザーが選択されていません\",\n\t\"NO_GROUPS_SELECTED\": \"グループが選択されていません\",\n\t\"SELECT_DAY\": \"日を選択\",\n\t\"SELECT_TIME\": \"時間を選択\",\n\t\"NO_CALLS_SELECTED\": \"通話が選択されていません\",\n\t\"SELECT__GROUP\": \"メッセージを開始するグループを選択\",\n\t\"SELECT__USER\": \"メッセージを開始するユーザーを選択\",\n\t\"GROUP_PASSWORD_BLANK\": \"グループパスワードは空にできません\",\n\t\"GROUP_NAME_BLANK\": \"グループ名は空にできません\",\n\t\"GROUP_TYPE_BLANK\": \"グループタイプは空にできません\",\n\t\"DELETE_CONVERSATION\": \"会話を削除しますか？\",\n\t\"ADD_TO_CHAT\": \"チャットに追加\",\n\t\"MORE\": \"もっと\",\n\t\"COPY\": \"コピー\",\n\t\"VOICE_RECORDING\": \"音声録音\",\n\t\"MESSAGE_IMAGE\": \"画像\",\n\t\"MESSAGE_FILE\": \"ファイル\",\n\t\"MESSAGE_VIDEO\": \"動画\",\n\t\"MESSAGE_AUDIO\": \"音声\",\n\t\"CUSTOM_MESSAGE\": \"メッセージがあります\",\n\t\"SELECT_A_DATE\": \"日付を選択\",\n\t\"TIME_ZONE\": \"タイムゾーン\",\n\t\"MEETING_SCHEDULED\": \"ミーティングがスケジュールされました。\",\n\t\"SOMETHING_WRONG\": \"問題が発生しました。もう一度お試しください。\",\n\t\"MEETING_SLOT_BOOK\": \"このタイムスロットは利用できません。別のものを選んでください。\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"新しいスロットを予約\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"この日付に利用可能なタイムスロットがありません。別の日付をお試しください。\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"別の日付をお試しください。\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"利用可能なタイムスロットがありません\",\n\t\"SCHEDULE\": \"スケジュール\",\n\t\"MORE_TIMES\": \"その他の時間\",\n\t\"MISSED_VOICE_CALL\": \"不在音声通話\",\n\t\"MEET_WITH\": \"ミーティング相手\",\n\t\"CONVERSATION_DELETED\": \"会話が削除されました\",\n\t\"MEETING_SCHEDULER\": \"ミーティングスケジューラ\",\n\t\"MISSED_VIDEO_CALL\": \"不在ビデオ通話\",\n\t\"CUSTOM_MESSAGE_POLL\": \"投票\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"ステッカー\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"ドキュメント\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"ホワイトボード\",\n\t\"ONLINE\": \"オンライン\",\n\t\"ADMINISTRATOR\": \"管理者\",\n\t\"ADMIN\": \"管理者\",\n\t\"MODERATOR\": \"モデレーター\",\n\t\"PARTICIPANT\": \"参加者\",\n\t\"PUBLIC\": \"公開\",\n\t\"PRIVATE\": \"プライベート\",\n\t\"PASSWORD_PROTECTED\": \"パスワード保護\",\n\t\"PRIVACY_AND_SECURITY\": \"プライバシーとセキュリティ\",\n\t\"PREFERENCES\": \"設定\",\n\t\"MEMBERS\": \"メンバー\",\n\t\"MEMBER\": \"メンバー\",\n\t\"EDITED\": \"編集済み\",\n\t\"TODAY\": \"今日\",\n\t\"YESTERDAY\": \"昨日\",\n\t\"SUNDAY\": \"日曜日\",\n\t\"MONDAY\": \"月曜日\",\n\t\"TUESDAY\": \"火曜日\",\n\t\"WEDNESDAY\": \"水曜日\",\n\t\"THURSDAY\": \"木曜日\",\n\t\"FRIDAY\": \"金曜日\",\n\t\"SATURDAY\": \"土曜日\",\n\t\"TYPING\": \"入力中...\",\n\t\"IS_TYPING\": \"入力しています...\",\n\t\"CLOSE\": \"閉じる\",\n\t\"ENTER_GROUP_NAME\": \"グループ名を入力\",\n\t\"ADD_MEMBERS\": \"メンバーを追加\",\n\t\"SEND_MESSAGE\": \"メッセージを送信\",\n\t\"UNBLOCK_USER\": \"ユーザーのブロックを解除\",\n\t\"BLOCK_USER\": \"ユーザーをブロック\",\n\t\"DELETE_AND_EXIT\": \"削除して退出\",\n\t\"LEAVE_GROUP\": \"グループを退出\",\n\t\"CREATE_GROUP\": \"グループを作成\",\n\t\"SHARED_MEDIA\": \"共有メディア\",\n\t\"SHARED_FILE\": \"共有ファイル\",\n\t\"VIDEO_CALL\": \"ビデオ通話\",\n\t\"AUDIO_CALL\": \"音声通話\",\n\t\"LOADING\": \"読み込み中...\",\n\t\"REPLY\": \"返信\",\n\t\"REPLIES\": \"返信\",\n\t\"AI\": \"AI\",\n\t\"SMART_REPLIES\": \"スマート返信\",\n\t\"CONVERSATION_STARTER\": \"会話スターター\",\n\t\"LAUNCH\": \"起動\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"共同編集ドキュメントを共有しました\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"共同編集ホワイトボードを共有しました\",\n\t\"CREATED_WHITEBOARD\": \"新しい共同編集ホワイトボードを作成しました\",\n\t\"CREATED_DOCUMENT\": \"新しい共同編集ドキュメントを作成しました\",\n\t\"PHOTOS\": \"写真\",\n\t\"VIDEOS\": \"動画\",\n\t\"DOCUMENT\": \"ドキュメント\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ このメッセージを削除しました\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ このメッセージは削除されました\",\n\t\"MESSAGE_IS_DELETED\": \"メッセージは削除されました\",\n\t\"MESSAGE_COPIED\": \"メッセージをクリップボードにコピーしました。\",\n\t\"MESSAGE_EDITED\": \"メッセージが更新されました。\",\n\t\"MESSAGE_DELETED_TEXT\": \"メッセージが削除されました。\",\n\t\"MESSAGE_TRANSLATED\": \"メッセージが翻訳されました。\",\n\t\"VIEW_ON_YOUTUBE\": \"YouTubeで見る\",\n\t\"SEARCH\": \"検索\",\n\t\"ERROR\": \"エラー\",\n\t\"ERROR_TEXT\": \"問題が発生したようです。もう一度お試しください。\",\n\t\"NO_GROUPS_FOUND\": \"グループが見つかりません\",\n\t\"NO_CHATS_FOUND\": \"チャットが見つかりません\",\n\t\"CANT__LOAD__CHATS\": \"チャットを読み込めません\",\n\t\"MEDIA_MESSAGE\": \"メディアメッセージ\",\n\t\"INCOMING_AUDIO_CALL\": \"着信音声通話\",\n\t\"INCOMING_VIDEO_CALL\": \"着信ビデオ通話\",\n\t\"DECLINE\": \"拒否\",\n\t\"ACCEPT\": \"承諾\",\n\t\"INCOMING_CALL\": \"着信通話\",\n\t\"OUTGOING_CALL\": \"発信通話\",\n\t\"CALL_REJECTED\": \"通話拒否\",\n\t\"CALL_ANSWERED\": \"通話応答\",\n\t\"CALL_CANCELLED\": \"通話キャンセル\",\n\t\"MISSED_CALL\": \"不在着信\",\n\t\"CALL_UNANSWERED\": \"不応答通話\",\n\t\"CALL_INITIATED\": \"通話開始\",\n\t\"OUTGOING_AUDIO_CALL\": \"発信音声通話\",\n\t\"OUTGOING_VIDEO_CALL\": \"発信ビデオ通話\",\n\t\"REJECTED_CALL\": \"拒否された通話\",\n\t\"CALL_ACCEPTED\": \"通話受諾\",\n\t\"JOINED\": \"参加しました\",\n\t\"LEFT_THE_CALL\": \"通話から退出しました\",\n\t\"UNANSWERED_AUDIO_CALL\": \"応答のない音声通話\",\n\t\"UNANSWERED_VIDEO_CALL\": \"応答のないビデオ通話\",\n\t\"CALL_ENDED\": \"通話終了\",\n\t\"CALL_BUSY\": \"通話中\",\n\t\"CALLING\": \"発信中...\",\n\t\"ADD\": \"追加\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"禁止されたメンバーが見つかりません\",\n\t\"BANNED_MEMBERS\": \"禁止されたメンバー\",\n\t\"NAME\": \"名前\",\n\t\"SCOPE\": \"範囲\",\n\t\"UNBAN\": \"禁止解除\",\n\t\"SELECT_GROUP_TYPE\": \"グループタイプを選択\",\n\t\"ENTER_GROUP_PASSWORD\": \"グループパスワードを入力\",\n\t\"CREATE\": \"作成\",\n\t\"CREATE_POLL\": \"投票\",\n\t\"QUESTION\": \"質問\",\n\t\"ENTER_YOUR_QUESTION\": \"質問を入力\",\n\t\"OPTIONS\": \"オプション\",\n\t\"ENTER_YOUR_OPTION\": \"オプションを入力\",\n\t\"ADD_NEW_OPTION\": \"新しいオプションを追加\",\n\t\"VIEW_MEMBERS\": \"メンバーを表示\",\n\t\"DETAILS\": \"詳細\",\n\t\"NOTIFICATIONS\": \"通知\",\n\t\"OTHER\": \"その他\",\n\t\"HELP\": \"ヘルプ\",\n\t\"REPORT_PROBLEM\": \"問題を報告\",\n\t\"GROUP_MEMBERS\": \"グループメンバー\",\n\t\"BAN\": \"禁止\",\n\t\"KICK\": \"追放\",\n\t\"PICK_YOUR_EMOJI\": \"絵文字を選択\",\n\t\"PRIVATE_GROUP\": \"プライベートグループ\",\n\t\"PROTECTED_GROUP\": \"保護されたグループ\",\n\t\"VISIT\": \"訪問\",\n\t\"ATTACH\": \"添付\",\n\t\"OPEN_WHITEBOARD\": \"ホワイトボードを開く\",\n\t\"ATTACH_FILE\": \"ファイルを添付\",\n\t\"GENERATING_REPLIES\": \"返信を生成中\",\n\t\"ATTACH_VIDEO\": \"動画を添付\",\n\t\"ATTACH_AUDIO\": \"音声を添付\",\n\t\"ATTACH_IMAGE\": \"画像を添付\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"ドキュメントを使用して共同作業\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"ホワイトボードを使用して共同作業\",\n\t\"SUGGEST_A_REPLY\": \"返信を提案\",\n\t\"GENERATING_ICEBREAKERS\": \"アイスブレーカーを生成中\",\n\t\"EMOJI\": \"絵文字\",\n\t\"NO_REPLIES_FOUND\": \"返信が見つかりません\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"メッセージを入力してください\",\n\t\"NO_MESSAGES_FOUND\": \"メッセージが見つかりません\",\n\t\"THREAD\": \"スレッド\",\n\t\"COLLABORATIVE_DOCUMENT\": \"共同編集ドキュメント\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"共同編集ホワイトボード\",\n\t\"ADD_REACTION\": \"リアクションを追加\",\n\t\"NO_STICKERS_FOUND\": \"ステッカーが見つかりません\",\n\t\"REPLY_TO_THREAD\": \"スレッドに返信\",\n\t\"REPLY_IN_THREAD\": \"スレッド内で返信\",\n\t\"VIEW\": \"表示\",\n\t\"DELETE_MESSAGE\": \"メッセージを削除\",\n\t\"EDIT_MESSAGE\": \"メッセージを編集\",\n\t\"OWNER\": \"オーナー\",\n\t\"CHANGE_SCOPE\": \"範囲を変更\",\n\t\"STICKER\": \"ステッカー\",\n\t\"LAST_ACTIVE_AT\": \"最終アクティブ\",\n\t\"LAST_SEEN\": \"最終閲覧\",\n\t\"AT\": \"at\",\n\t\"VOICE_CALL\": \"音声通話\",\n\t\"VIEW_DETAIL\": \"詳細を表示\",\n\t\"VOTES\": \"投票\",\n\t\"VOTE\": \"投票\",\n\t\"NO_VOTE\": \"投票なし\",\n\t\"REACTED\": \"リアクションしました\",\n\t\"ADDED\": \"追加しました\",\n\t\"SHOW_UNSAFE_CONTENT\": \"安全でないコンテンツを表示してもよろしいですか？\",\n\t\"REACT_TO_MESSAGE\": \"メッセージにリアクション\",\n\t\"UNBANNED\": \"禁止解除\",\n\t\"MADE\": \"設定しました\",\n\t\"MISSED_AUDIO_CALL\": \"不在音声通話\",\n\t\"ENTER_YOUR_PASSWORD\": \"パスワードを入力\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"一緒にコンテンツを編集するためにドキュメントを開く\",\n\t\"DOCS\": \"ドキュメント\",\n\t\"NO_RECORDS_FOUND\": \"記録が見つかりません\",\n\t\"LIVE_REACTION\": \"ライブリアクション\",\n\t\"SMILEY_PEOPLE\": \"絵文字と人々\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"一緒に描くためにホワイトボードを開く\",\n\t\"ANIMALES_NATURE\": \"動物と自然\",\n\t\"FOOD_DRINK\": \"食べ物と飲み物\",\n\t\"OPEN_DOCUMENT\": \"ドキュメントを開く\",\n\t\"ACTIVITY\": \"アクティビティ\",\n\t\"TRAVEL_PLACES\": \"旅行と場所\",\n\t\"OBJECTS\": \"物\",\n\t\"SYMBOLS\": \"記号\",\n\t\"FLAGS\": \"旗\",\n\t\"SENT\": \"送信済み\",\n\t\"SEEN\": \"既読\",\n\t\"DELIVERED\": \"配信済み\",\n\t\"READ\": \"読み取り済み\",\n\t\"MESSAGE_INFORMATION\": \"メッセージ情報\",\n\t\"TRANSLATE_MESSAGE\": \"メッセージを翻訳\",\n\t\"TRANSLATED_MESSAGE\": \"翻訳されたメッセージ\",\n\t\"LEFT\": \"退出しました\",\n\t\"KICKED\": \"追放されました\",\n\t\"BANNED\": \"禁止されました\",\n\t\"NEW_MESSAGES\": \"新しいメッセージ\",\n\t\"NEW_MESSAGE\": \"新しいメッセージ\",\n\t\"JUMP\": \"ジャンプ\",\n\t\"SELECT_VIDEO_SOURCE\": \"ビデオソースを選択\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"入力音声ソースを選択\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"出力音声ソースを選択\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"音声通話を開始しました\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"ビデオ通話を開始しました\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"音声通話を開始しました\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"ビデオ通話を開始しました\",\n\t\"IGNORE\": \"無視\",\n\t\"ON_ANOTHER_CALL\": \"別の通話中です\",\n\t\"CREATING\": \"作成中\",\n\t\"AVATAR\": \"アバター\",\n\t\"ONGOING_CALL\": \"進行中の通話\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"すでに通話中です\",\n\t\"RESIZE\": \"サイズ変更\",\n\t\"READ_MORE\": \"もっと読む\",\n\t\"SHOW_LESS\": \"表示を減らす\",\n\t\"SETTINGS\": \"設定\",\n\t\"ACTIONS\": \"アクション\",\n\t\"VIEW_PROFILE\": \"プロフィールを表示\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"プライベートでメッセージを送信\",\n\t\"DELETE\": \"削除\",\n\t\"DELETE_CONFIRM\": \"削除してもよろしいですか？\",\n\t\"DELETE_CHAT\": \"このチャットを削除しますか？\",\n\t\"SURE_TO_DELETE_CHAT\": \"このチャットを削除してもよろしいですか？この操作は元に戻せません。\",\n\t\"CANCEL\": \"キャンセル\",\n\t\"LEAVE_CONFIRM\": \"グループを退出してもよろしいですか？\",\n\t\"TRANSFER_CONFIRM\": \"あなたはグループオーナーです。グループを退出する前にメンバーに所有権を移譲してください。\",\n\t\"ADDING\": \"追加中...\",\n\t\"TRANSFER\": \"移譲\",\n\t\"TRANSFER_OWNERSHIP\": \"所有権を移譲\",\n\t\"TRANSFERRING\": \"移譲中\",\n\t\"YES\": \"はい\",\n\t\"NO\": \"いいえ\",\n\t\"ANSWER\": \"回答\",\n\t\"ADD_ANOTHER_ANSWER\": \"別の回答を追加\",\n\t\"SET_THE_ANSWERS\": \"回答を設定\",\n\t\"TRY_AGAIN\": \"もう一度試す\",\n\t\"INVALID_GROUP_NAME\": \"グループの有効な名前を入力してもう一度お試しください\",\n\t\"INVALID_PASSWORD\": \"グループの有効なパスワードを入力してもう一度お試しください\",\n\t\"INVALID_GROUP_TYPE\": \"グループの有効なタイプを入力してもう一度お試しください\",\n\t\"WRONG_PASSWORD\": \"正しいパスワードを入力してもう一度お試しください\",\n\t\"INVALID_POLL_QUESTION\": \"投票を作成する前に必要な質問を入力してください\",\n\t\"INVALID_POLL_OPTION\": \"投票を作成する前に必要な回答を入力してください\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"翻訳用に選択された言語は元のメッセージの言語と似ています\",\n\t\"LEAVE\": \"退出\",\n\t\"CLICK_TO_START_CONVERSATION\": \"クリックして会話を開始\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍位置情報\",\n\t\"SHARED_LOCATION\": \"共有された位置情報\",\n\t\"IN_A_THREAD\": \"スレッド内\",\n\t\"CALLS\": \"通話\",\n\t\"CALL_DETAILS\": \"通話詳細\",\n\t\"OFFLINE\": \"オフライン\",\n\t\"POLLS\": \"投票\",\n\t\"YOU\": \"あなた\",\n\t\"PRIVACY\": \"プライバシー\",\n\t\"BLOCKED_USERS\": \"ブロックしたユーザー\",\n\t\"YOU'VE_BLOCKED\": \"ブロックしました\",\n\t\"NO_PHOTOS\": \"写真なし\",\n\t\"NO_VIDEOS\": \"動画なし\",\n\t\"NO_DOCUMENTS\": \"ドキュメントなし\",\n\t\"JOIN\": \"参加\",\n\t\"REMOVE\": \"削除\",\n\t\"BLOCK\": \"ブロック\",\n\t\"CHANGE_ROLE\": \"役割を変更\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"グループの権限と責任を管理するために役割を変更できます。\",\n\t\"CHANGE_TO\": \"変更先\",\n\t\"NEW_CHAT\": \"新規チャット\",\n\t\"FORM_COMPLETION_MESSAGE\": \"フォームの記入ありがとうございます。\",\n\t\"HISTORY\": \"履歴\",\n\t\"CANCELLED_AUDIO_CALL\": \"キャンセルされた音声通話\",\n\t\"CANCELLED_VIDEO_CALL\": \"キャンセルされたビデオ通話\",\n\t\"REJECTED_AUDIO_CALL\": \"拒否された音声通話\",\n\t\"REJECTED_VIDEO_CALL\": \"拒否されたビデオ通話\",\n\t\"CANCELLED_CALL\": \"キャンセルされた通話\",\n\t\"UNANSWERED_CALL\": \"応答のない通話\",\n\t\"RECORDING\": \"録音中\",\n\t\"PARTICIPANTS\": \"参加者\",\n\t\"CALL_HISTORY\": \"通話履歴\",\n\t\"NO_CALLS_FOUND\": \"通話が見つかりません\",\n\t\"ONGOING_AUDIO_CALL\": \"進行中の音声通話\",\n\t\"ONGOING_VIDEO_CALL\": \"進行中のビデオ通話\",\n\t\"CALL_DETAIL\": \"通話詳細\",\n\t\"FORM\": \"フォーム\",\n\t\"CARD\": \"カード\",\n\t\"GENERATING_SUMMARY\": \"要約を生成中\",\n\t\"CONVERSATION_SUMMARY\": \"会話の要約\",\n\t\"GENERATE_SUMMARY\": \"要約を生成\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"この会話でどのようにお手伝いできますか？質問をしていただければアドバイスします 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"質問する\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"AIボットに質問する\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AIボット\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"一度に最大10人のユーザーをメンションできます。\",\n\t\"ALL\": \"すべて\",\n\t\"CLICK_TO_REMOVE\": \"クリックして削除\",\n\t\"OTHERS\": \"その他\",\n\t\"AND\": \"と\",\n\t\"ASK_QUESTION\": \"質問する\",\n\t\"ADD_OPTION\": \"オプションを追加\",\n\t\"SEND\": \"送信\",\n\t\"FULL_SCREEN_VIEWER\": \"全画面表示\",\n\t\"TEXT_TRANSLATE\": \"テキスト翻訳\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"問題が発生したようです\",\n\t\"NO_SUMMARY_AVAILABLE\": \"要約がありません\",\n\t\"QUESTIONS\": \"質問\",\n\t\"NO_STICKERS_AVAILABLE\": \"利用可能なステッカーがありません\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"利用可能な提案がありません\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"グループを作成または参加して、ここに表示して共同作業を始めましょう\",\n\t\"NO_USERS_AVAILABLE\": \"利用可能なユーザーがいません\",\n\t\"NO_GROUPS_AVAILABLE\": \"利用可能なグループがありません\",\n\t\"NO_CALL_LOGS\": \"通話ログはまだありません\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"通話を発信または受信して、通話履歴をここに表示します\",\n\t\"NO_CONVERSATIONS\": \"会話はまだありません\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"新しいチャットを開始するか、他の人を会話に招待してください。\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"利用可能なグループメンバーがいません\",\n\t\"OOPS!\": \"おっと！\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"受信者がメッセージを受信して表示するのを待っています\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"連絡先を追加して、ここに表示してください。\",\n\t\"REACHED_MAX_LIMIT\": \"制限に達しました。最大12個のオプションを追加できます。\",\n\t\"REQUIRED_FIELDS_WARNING\": \"投票を作成する前にすべての必須フィールドに入力してください。\",\n\t\"NOT_SUPPORTED\": \"このメッセージタイプはサポートされていません\",\n\t\"NO_USERS_FOUND\": \"ユーザーが見つかりません\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"検索に一致するユーザーが見つかりませんでした。検索条件を調整してみてください。\",\n\t\"SAVE\": \"保存\",\n\t\"RETRY\": \"再試行\",\n\t\"TAP_TO_START_CONVERSATION\": \"タップして会話を開始\",\n\t\"TEXT_TRANSLATED\": \"テキストが翻訳されました\",\n\t\"CAMERA\": \"カメラ\",\n\t\"SHARE\": \"共有\",\n\t\"ATTACH_DOCUMENT\": \"ドキュメントを添付\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"削除してもよろしいですか \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"メッセージを入力...\",\n\t\"TAP_TO_REMOVE\": \"タップして削除\",\n\t\"ADD_CONTACTS\": \"連絡先を追加して会話を開始し、ここに表示してください。\",\n\t\"SCOPE_CHANGE_INFO\": \"グループの権限と責任を管理するために役割を変更できます\",\n\t\"SOMETHING_WENT_WRONG\": \"問題が発生したようです。\",\n\t\"MENTION_UPTO\": \"最大\",\n\t\"TIME\": \"回\",\n\t\"TIMES\": \"回\",\n\t\"AT_A_TIME\": \"一度に\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"この機能には特定のリソース権限が必要です。デバイス設定で必要な権限を有効にしてください。\",\n\t\"MICROPHONE_PERMISSION\": \"音声メッセージを録音するには、マイクへのアクセスが必要です。設定をタップしてマイクを有効にしてください。\",\n\t\"CAMERA_PERMISSION\": \"カメラへのアクセスがありません。アクセスを有効にするには、設定をタップしてカメラをオンにしてください。\",\n\t\"OOPS\": \"おっと！\",\n\t\"ADD_OPTIONS\": \"オプションを追加\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"一緒に描画するためにドキュメントを開く\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"一緒に描画するためにホワイトボードを開く\",\n\t\"TEXT\": \"テキスト\",\n\t\"BAN_MEMBER_CONFIRM\": \"禁止してもよろしいですか \",\n\t\"LOGIN\": \"ログイン\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Googleで続ける\",\n\t\"JOIN_GROUP\": \"グループに参加\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"グループのパスワードが正しくありません！\",\n\t\"ENTER_PASSWORD\": \"パスワードを入力\",\n\t\"TYPE\": \"タイプ\",\n\t\"USER_INFO\": \"ユーザー情報\",\n\t\"DELETE_CHAT_TEXT\": \"チャットを削除\",\n\t\"UNBLOCK\": \"ブロック解除\",\n\t\"UNBLOCK_CONTACT\": \"この連絡先のブロックを解除\",\n\t\"BLOCK_CONTACT\": \"この連絡先をブロックしますか？\",\n\t\"UNBLOCK_SURE\": \"この連絡先のブロックを解除してもよろしいですか？再びメッセージを受信するようになります。\",\n\t\"BLOCK_SURE\": \"この連絡先をブロックしてもよろしいですか？今後メッセージを受信しなくなります。\",\n\t\"VOICE\": \"音声\",\n\t\"DELETE_AND_EXIT_SURE\": \"このチャットを削除してグループを退出してもよろしいですか？この操作は元に戻せません。\",\n\t\"TRANSFER_SURE\": \"所有権を移譲してもよろしいですか？これは元に戻せず、新しいオーナーが完全に制御できるようになります。\",\n\t\"GROUP_INFO\": \"グループ情報\",\n\t\"LEAVE_GROUP_TEXT\": \"このグループを退出\",\n\t\"LEAVE_SURE\": \"グループを退出してもよろしいですか？このチャットからのメッセージを受信しなくなります。\",\n\t\"UNBAN_SURE\": \"このユーザーの禁止を解除してもよろしいですか？\",\n\t\"DELETE_THIS_CONVERSATION\": \"この会話を削除しますか？\",\n\t\"LINK\": \"リンク\",\n\t\"DELETE_THIS_MESSAGE\": \"このメッセージを削除しますか？\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"このメッセージを削除してもよろしいですか？この操作は元に戻せません。\",\n\t\"APP_CREDENTIALS\": \"アプリ資格情報\",\n\t\"REGION\": \"地域\",\n\t\"CHAT_PRIVATELY\": \"プライベートチャット\",\n\t\"WRONG_TEXT\": \"問題が発生したようです。\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"もう一度お試しください。\",\n\t\"BLOCKED_USER_DESC\": \"ユーザーがブロックされているためメッセージを送信できません\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName}が${onName}をグループに追加しました\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName}がグループに参加しました\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName}がグループを退出しました\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName}が${onName}をグループから追放しました\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName}が${onName}をグループから禁止しました\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName}が${onName}の禁止を解除しました\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName}が${onName}を${role}にしました\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"まだスタンプがありません。\",\n\t\"meeting\": \"ミーティング\",\n\t\"Flag_Message_Title\": \"メッセージを報告\",\n\t\"Flag_Message_Subtitle\": \"このチャットがコミュニティ基準に反している場合は報告してください。報告したことが相手アカウントに通知されることはありません。\",\n\t\"Flag_Message_Remark_Label\": \"理由\",\n\t\"Flag_Message_Remark_Optional\": \"任意\",\n\t\"Flag_Message_Remark_Placeholder\": \"報告の補足情報を入力してください…\",\n\t\"Flag_Message_Confirm_Yes\": \"報告する\",\n\t\"Flag_Message_Confirm_No\": \"キャンセル\",\n\t\"Flag_Message_Reason_Id_Spam\": \"スパム／望ましくない内容\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"露骨または不適切な内容\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"侮辱的または脅迫的な行為\",\n\t\"Message_List_Option_Flag_Message\": \"報告\",\n    \"Flag_Error_Text\":\"問題が発生しました。確認してもう一度お試しください。\",\n\t\"Flag_Empty_Submit_Text\":\"送信できません。 このメッセージを報告する前に理由を選択してください。\",\n\t\"START_REPORTING\": \"レポートを開始\",\n\t\"LOGOUT\": \"ログアウト\",\n\t\"AI_ASSISTANTS\": \"AIアシスタント\",\n\t\"CREATE_CONVERSATION\": \"会話を作成\",\n\t\"EDIT_MODERATION\": \"編集に失敗しました。あなたのメッセージはモデレーションポリシーによりブロックされました。\",\n    \"BLOCKED_MODERATION\":\"あなたのメッセージはモデレーションポリシーによりブロックされました。\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"チャット履歴が見つかりません\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"当社側で問題が発生しました。もう一度試してください\",\n\t\"START_A_CHAT\": \"'新しいチャット' ボタンをタップしてチャットを開始します\",\n\t\"CHAT_HISTORY\": \"チャット履歴\",\n\t\"ASK_ANYTHING\": \"何でも聞いてください...\",\n\t\"AI_ASSISTANT\": \"AIアシスタント\",\n\t\"SEARCH_PLACEHOLDER\": \"検索...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"結果なし\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"メッセージや会話を検索するには入力を開始してください\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"該当する結果がありません\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"一致するものが見つかりませんでした。別のキーワードをお試しください。\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"会話の読み込みエラー\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"メッセージの読み込みエラー\",\n\t\"SEARCH_TRY_AGAIN\": \"後でもう一度お試しください\",\n\t\"SEARCH_SEE_MORE\": \"さらに表示\",\n\t\"SEARCH_LOADING\": \"読み込み中...\",\n\t\"SEARCH_FILTER_UNREAD\": \"未読\",\n\t\"SEARCH_FILTER_GROUPS\": \"グループ\",\n\t\"SEARCH_FILTER_PHOTOS\": \"写真\",\n\t\"SEARCH_FILTER_VIDEOS\": \"動画\",\n\t\"SEARCH_FILTER_LINKS\": \"リンク\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"ドキュメント\",\n\t\"SEARCH_FILTER_AUDIO\": \"音声\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"会話\",\n\t\"SEARCH_FILTER_MESSAGES\": \"メッセージ\",\n\t\"SEARCH_NO_MESSAGES\": \"メッセージがありません\",\n\t\"MESSAGE_REPORTED\": \"メッセージが報告されました\",\n\t\"MARK_AS_UNREAD\": \"未読にする\",\n\t\"NEW\": \"新規\",\n\t\"INSERT_LINK\": \"リンクを挿入\",\n\t\"EDIT_LINK\": \"リンクを編集\",\n\t\"LINK_TEXT\": \"リンクテキスト\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"挿入\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"あなたはもうメンバーではないため、このグループにメッセージを送信できません。\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/ko/translation.json",
    "content": "{\n\t\"INFO\": \"정보\",\n\t\"REACT\": \"반응\",\n\t\"EDIT\": \"편집\",\n\t\"MESSAGE_PRIVATELY\": \"개인 메시지\",\n\t\"TRANSLATE\": \"번역\",\n\t\"USERS\": \"사용자\",\n\t\"CHATS\": \"채팅\",\n\t\"SEARCH_EMOJI\": \"이모티콘 검색\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"이 대화를 삭제하시겠습니까? 이 대화는 모든 기기에서 삭제됩니다.\",\n\t\"GROUPS\": \"그룹\",\n\t\"SHARED\": \"공유됨\",\n\t\"SOUND_MANAGER\": \"소리 관리\",\n\t\"THEME\": \"테마\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"모두\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"이 그룹의 모든 사람에게 알리기\",\n\t\"DELETE_MSG_TEXT\": \"이 메시지는 삭제되었습니다\",\n\t\"LOCALIZE\": \"현지화\",\n\t\"CONVERSATION_LIST_ITEM\": \"대화 목록 항목\",\n\t\"DATA_ITEM\": \"데이터 항목\",\n\t\"STATUS_INDICATOR\": \"상태 표시기\",\n\t\"BADGE_COUNT\": \"배지 수\",\n\t\"MESSAGE_RECEIPT\": \"메시지 수신 확인\",\n\t\"MESSAGE\": \"메시지\",\n\t\"RECEIPT_INFORMATION\": \"수신 정보\",\n\t\"NO_RECIPIENT\": \"받는 사람 없음\",\n\t\"NO_RECIPIENTS\": \"받는 사람 없음\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"메시지가 있는 대화\",\n\t\"CONVERSATIONS\": \"대화\",\n\t\"CONVERSATION_LIST\": \"대화 목록\",\n\t\"MESSAGES\": \"메시지\",\n\t\"WRONG_FILE_TYPE\": \"다른 유형의 파일을 선택했습니다. 적절한 파일을 선택해 주세요.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"이 파일 형식은 허용되지 않습니다.\",\n\t\"MESSAGE_HEADER\": \"메시지 헤더\",\n\t\"MESSAGE_LIST\": \"메시지 목록\",\n\t\"MESSAGE_COMPOSER\": \"메시지 작성기\",\n\t\"USERS_WITH_MESSAGES\": \"메시지가 있는 사용자\",\n\t\"USER_LIST\": \"사용자 목록\",\n\t\"GROUP_LIST\": \"그룹 목록\",\n\t\"GROUPS_WITH_MESSAGES\": \"메시지가 있는 그룹\",\n\t\"NEW__GROUP\": \"새 그룹\",\n\t\"PASSWORD\": \"비밀번호\",\n\t\"CONTINUE\": \"계속\",\n\t\"NO_CHATS_SELECTED\": \"선택된 채팅 없음\",\n\t\"NO_USERS_SELECTED\": \"선택된 사용자 없음\",\n\t\"NO_GROUPS_SELECTED\": \"선택된 그룹 없음\",\n\t\"SELECT_DAY\": \"요일 선택\",\n\t\"SELECT_TIME\": \"시간 선택\",\n\t\"NO_CALLS_SELECTED\": \"선택된 통화 없음\",\n\t\"SELECT__GROUP\": \"메시지를 시작할 그룹 선택\",\n\t\"SELECT__USER\": \"메시지를 시작할 사용자 선택\",\n\t\"GROUP_PASSWORD_BLANK\": \"그룹 비밀번호는 비워둘 수 없습니다\",\n\t\"GROUP_NAME_BLANK\": \"그룹 이름은 비워둘 수 없습니다\",\n\t\"GROUP_TYPE_BLANK\": \"그룹 유형은 비워둘 수 없습니다\",\n\t\"DELETE_CONVERSATION\": \"대화를 삭제하시겠습니까?\",\n\t\"ADD_TO_CHAT\": \"채팅에 추가\",\n\t\"MORE\": \"더 보기\",\n\t\"COPY\": \"복사\",\n\t\"VOICE_RECORDING\": \"음성 녹음\",\n\t\"MESSAGE_IMAGE\": \"이미지\",\n\t\"MESSAGE_FILE\": \"파일\",\n\t\"MESSAGE_VIDEO\": \"비디오\",\n\t\"MESSAGE_AUDIO\": \"오디오\",\n\t\"CUSTOM_MESSAGE\": \"메시지가 있습니다\",\n\t\"SELECT_A_DATE\": \"날짜 선택\",\n\t\"TIME_ZONE\": \"시간대\",\n\t\"MEETING_SCHEDULED\": \"회의가 예약되었습니다.\",\n\t\"SOMETHING_WRONG\": \"문제가 발생했습니다. 다시 시도해 주세요.\",\n\t\"MEETING_SLOT_BOOK\": \"시간 슬롯이 더 이상 사용할 수 없습니다. 다른 시간을 선택해 주세요.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"새 슬롯 예약\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"이 날짜에 사용 가능한 시간 슬롯이 없습니다. 다른 날짜를 시도해 주세요.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"다른 날짜를 시도해 주세요.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"사용 가능한 시간 슬롯 없음\",\n\t\"SCHEDULE\": \"일정\",\n\t\"MORE_TIMES\": \"더 많은 시간\",\n\t\"MISSED_VOICE_CALL\": \"부재중 음성 통화\",\n\t\"MEET_WITH\": \"회의 대상\",\n\t\"CONVERSATION_DELETED\": \"대화가 삭제되었습니다\",\n\t\"MEETING_SCHEDULER\": \"회의 스케줄러\",\n\t\"MISSED_VIDEO_CALL\": \"부재중 영상 통화\",\n\t\"CUSTOM_MESSAGE_POLL\": \"투표\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"스티커\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"문서\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"화이트보드\",\n\t\"ONLINE\": \"온라인\",\n\t\"ADMINISTRATOR\": \"관리자\",\n\t\"ADMIN\": \"관리자\",\n\t\"MODERATOR\": \"중재자\",\n\t\"PARTICIPANT\": \"참가자\",\n\t\"PUBLIC\": \"공개\",\n\t\"PRIVATE\": \"비공개\",\n\t\"PASSWORD_PROTECTED\": \"비밀번호 보호\",\n\t\"PRIVACY_AND_SECURITY\": \"개인 정보 보호 및 보안\",\n\t\"PREFERENCES\": \"환경 설정\",\n\t\"MEMBERS\": \"멤버\",\n\t\"MEMBER\": \"멤버\",\n\t\"EDITED\": \"편집됨\",\n\t\"TODAY\": \"오늘\",\n\t\"YESTERDAY\": \"어제\",\n\t\"SUNDAY\": \"일요일\",\n\t\"MONDAY\": \"월요일\",\n\t\"TUESDAY\": \"화요일\",\n\t\"WEDNESDAY\": \"수요일\",\n\t\"THURSDAY\": \"목요일\",\n\t\"FRIDAY\": \"금요일\",\n\t\"SATURDAY\": \"토요일\",\n\t\"TYPING\": \"입력 중...\",\n\t\"IS_TYPING\": \"입력 중...\",\n\t\"CLOSE\": \"닫기\",\n\t\"ENTER_GROUP_NAME\": \"그룹 이름 입력\",\n\t\"ADD_MEMBERS\": \"멤버 추가\",\n\t\"SEND_MESSAGE\": \"메시지 보내기\",\n\t\"UNBLOCK_USER\": \"사용자 차단 해제\",\n\t\"BLOCK_USER\": \"사용자 차단\",\n\t\"DELETE_AND_EXIT\": \"삭제 및 나가기\",\n\t\"LEAVE_GROUP\": \"그룹 나가기\",\n\t\"CREATE_GROUP\": \"그룹 만들기\",\n\t\"SHARED_MEDIA\": \"공유 미디어\",\n\t\"SHARED_FILE\": \"공유 파일\",\n\t\"VIDEO_CALL\": \"영상 통화\",\n\t\"AUDIO_CALL\": \"음성 통화\",\n\t\"LOADING\": \"로딩 중...\",\n\t\"REPLY\": \"답장\",\n\t\"REPLIES\": \"답장\",\n\t\"AI\": \"AI\",\n\t\"SMART_REPLIES\": \"스마트 답장\",\n\t\"CONVERSATION_STARTER\": \"대화 시작\",\n\t\"LAUNCH\": \"시작\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"협업 문서를 공유했습니다\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"협업 화이트보드를 공유했습니다\",\n\t\"CREATED_WHITEBOARD\": \"새로운 협업 화이트보드를 만들었습니다\",\n\t\"CREATED_DOCUMENT\": \"새로운 협업 문서를 만들었습니다\",\n\t\"PHOTOS\": \"사진\",\n\t\"VIDEOS\": \"비디오\",\n\t\"DOCUMENT\": \"문서\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ 이 메시지를 삭제했습니다\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ 이 메시지는 삭제되었습니다\",\n\t\"MESSAGE_IS_DELETED\": \"메시지가 삭제되었습니다\",\n\t\"MESSAGE_COPIED\": \"메시지가 클립보드에 복사되었습니다.\",\n\t\"MESSAGE_EDITED\": \"메시지가 성공적으로 업데이트되었습니다.\",\n\t\"MESSAGE_DELETED_TEXT\": \"메시지가 성공적으로 삭제되었습니다.\",\n\t\"MESSAGE_TRANSLATED\": \"메시지가 성공적으로 번역되었습니다.\",\n\t\"VIEW_ON_YOUTUBE\": \"YouTube에서 보기\",\n\t\"SEARCH\": \"검색\",\n\t\"ERROR\": \"오류\",\n\t\"ERROR_TEXT\": \"문제가 발생한 것 같습니다. 다시 시도해 주세요.\",\n\t\"NO_GROUPS_FOUND\": \"그룹을 찾을 수 없습니다\",\n\t\"NO_CHATS_FOUND\": \"채팅을 찾을 수 없습니다\",\n\t\"CANT__LOAD__CHATS\": \"채팅을 로드할 수 없습니다\",\n\t\"MEDIA_MESSAGE\": \"미디어 메시지\",\n\t\"INCOMING_AUDIO_CALL\": \"수신 음성 통화\",\n\t\"INCOMING_VIDEO_CALL\": \"수신 영상 통화\",\n\t\"DECLINE\": \"거절\",\n\t\"ACCEPT\": \"수락\",\n\t\"INCOMING_CALL\": \"수신 통화\",\n\t\"OUTGOING_CALL\": \"발신 통화\",\n\t\"CALL_REJECTED\": \"통화 거절됨\",\n\t\"CALL_ANSWERED\": \"통화 응답됨\",\n\t\"CALL_CANCELLED\": \"통화 취소됨\",\n\t\"MISSED_CALL\": \"부재중 통화\",\n\t\"CALL_UNANSWERED\": \"응답 없는 통화\",\n\t\"CALL_INITIATED\": \"통화 시작됨\",\n\t\"OUTGOING_AUDIO_CALL\": \"발신 음성 통화\",\n\t\"OUTGOING_VIDEO_CALL\": \"발신 영상 통화\",\n\t\"REJECTED_CALL\": \"거절된 통화\",\n\t\"CALL_ACCEPTED\": \"통화 수락됨\",\n\t\"JOINED\": \"참여함\",\n\t\"LEFT_THE_CALL\": \"통화를 떠났습니다\",\n\t\"UNANSWERED_AUDIO_CALL\": \"응답 없는 음성 통화\",\n\t\"UNANSWERED_VIDEO_CALL\": \"응답 없는 영상 통화\",\n\t\"CALL_ENDED\": \"통화 종료됨\",\n\t\"CALL_BUSY\": \"통화 중\",\n\t\"CALLING\": \"전화 거는 중...\",\n\t\"ADD\": \"추가\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"차단된 멤버를 찾을 수 없습니다\",\n\t\"BANNED_MEMBERS\": \"차단된 멤버\",\n\t\"NAME\": \"이름\",\n\t\"SCOPE\": \"범위\",\n\t\"UNBAN\": \"차단 해제\",\n\t\"SELECT_GROUP_TYPE\": \"그룹 유형 선택\",\n\t\"ENTER_GROUP_PASSWORD\": \"그룹 비밀번호 입력\",\n\t\"CREATE\": \"만들기\",\n\t\"CREATE_POLL\": \"투표\",\n\t\"QUESTION\": \"질문\",\n\t\"ENTER_YOUR_QUESTION\": \"질문 입력\",\n\t\"OPTIONS\": \"옵션\",\n\t\"ENTER_YOUR_OPTION\": \"옵션 입력\",\n\t\"ADD_NEW_OPTION\": \"새 옵션 추가\",\n\t\"VIEW_MEMBERS\": \"멤버 보기\",\n\t\"DETAILS\": \"세부 정보\",\n\t\"NOTIFICATIONS\": \"알림\",\n\t\"OTHER\": \"기타\",\n\t\"HELP\": \"도움말\",\n\t\"REPORT_PROBLEM\": \"문제 신고\",\n\t\"GROUP_MEMBERS\": \"그룹 멤버\",\n\t\"BAN\": \"차단\",\n\t\"KICK\": \"강퇴\",\n\t\"PICK_YOUR_EMOJI\": \"이모티콘 선택\",\n\t\"PRIVATE_GROUP\": \"비공개 그룹\",\n\t\"PROTECTED_GROUP\": \"보호된 그룹\",\n\t\"VISIT\": \"방문\",\n\t\"ATTACH\": \"첨부\",\n\t\"OPEN_WHITEBOARD\": \"화이트보드 열기\",\n\t\"ATTACH_FILE\": \"파일 첨부\",\n\t\"GENERATING_REPLIES\": \"답장 생성 중\",\n\t\"ATTACH_VIDEO\": \"비디오 첨부\",\n\t\"ATTACH_AUDIO\": \"오디오 첨부\",\n\t\"ATTACH_IMAGE\": \"이미지 첨부\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"문서를 사용하여 협업\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"화이트보드를 사용하여 협업\",\n\t\"SUGGEST_A_REPLY\": \"답장 제안\",\n\t\"GENERATING_ICEBREAKERS\": \"아이스브레이커 생성 중\",\n\t\"EMOJI\": \"이모티콘\",\n\t\"NO_REPLIES_FOUND\": \"답장을 찾을 수 없습니다\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"여기에 메시지를 입력하세요\",\n\t\"NO_MESSAGES_FOUND\": \"메시지를 찾을 수 없습니다\",\n\t\"THREAD\": \"스레드\",\n\t\"COLLABORATIVE_DOCUMENT\": \"협업 문서\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"협업 화이트보드\",\n\t\"ADD_REACTION\": \"반응 추가\",\n\t\"NO_STICKERS_FOUND\": \"스티커를 찾을 수 없습니다\",\n\t\"REPLY_TO_THREAD\": \"스레드에 답장\",\n\t\"REPLY_IN_THREAD\": \"스레드에서 답장\",\n\t\"VIEW\": \"보기\",\n\t\"DELETE_MESSAGE\": \"메시지 삭제\",\n\t\"EDIT_MESSAGE\": \"메시지 편집\",\n\t\"OWNER\": \"소유자\",\n\t\"CHANGE_SCOPE\": \"범위 변경\",\n\t\"STICKER\": \"스티커\",\n\t\"LAST_ACTIVE_AT\": \"마지막 활동\",\n\t\"LAST_SEEN\": \"마지막 접속\",\n\t\"AT\": \"에\",\n\t\"VOICE_CALL\": \"음성 통화\",\n\t\"VIEW_DETAIL\": \"세부 정보 보기\",\n\t\"VOTES\": \"투표\",\n\t\"VOTE\": \"투표\",\n\t\"NO_VOTE\": \"투표 없음\",\n\t\"REACTED\": \"반응함\",\n\t\"ADDED\": \"추가됨\",\n\t\"SHOW_UNSAFE_CONTENT\": \"안전하지 않은 콘텐츠를 보시겠습니까?\",\n\t\"REACT_TO_MESSAGE\": \"메시지에 반응\",\n\t\"UNBANNED\": \"차단 해제됨\",\n\t\"MADE\": \"만듦\",\n\t\"MISSED_AUDIO_CALL\": \"부재중 음성 통화\",\n\t\"ENTER_YOUR_PASSWORD\": \"비밀번호 입력\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"함께 콘텐츠를 편집하기 위해 문서 열기\",\n\t\"DOCS\": \"문서\",\n\t\"NO_RECORDS_FOUND\": \"기록을 찾을 수 없습니다\",\n\t\"LIVE_REACTION\": \"실시간 반응\",\n\t\"SMILEY_PEOPLE\": \"이모티콘 및 사람\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"함께 그리기 위해 화이트보드 열기\",\n\t\"ANIMALES_NATURE\": \"동물 및 자연\",\n\t\"FOOD_DRINK\": \"음식 및 음료\",\n\t\"OPEN_DOCUMENT\": \"문서 열기\",\n\t\"ACTIVITY\": \"활동\",\n\t\"TRAVEL_PLACES\": \"여행 및 장소\",\n\t\"OBJECTS\": \"물건\",\n\t\"SYMBOLS\": \"기호\",\n\t\"FLAGS\": \"깃발\",\n\t\"SENT\": \"보냄\",\n\t\"SEEN\": \"읽음\",\n\t\"DELIVERED\": \"전달됨\",\n\t\"READ\": \"읽음\",\n\t\"MESSAGE_INFORMATION\": \"메시지 정보\",\n\t\"TRANSLATE_MESSAGE\": \"메시지 번역\",\n\t\"TRANSLATED_MESSAGE\": \"번역된 메시지\",\n\t\"LEFT\": \"나감\",\n\t\"KICKED\": \"강퇴됨\",\n\t\"BANNED\": \"차단됨\",\n\t\"NEW_MESSAGES\": \"새 메시지\",\n\t\"NEW_MESSAGE\": \"새 메시지\",\n\t\"JUMP\": \"이동\",\n\t\"SELECT_VIDEO_SOURCE\": \"비디오 소스 선택\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"입력 오디오 소스 선택\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"출력 오디오 소스 선택\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"음성 통화를 시작했습니다\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"영상 통화를 시작했습니다\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"음성 통화를 시작했습니다\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"영상 통화를 시작했습니다\",\n\t\"IGNORE\": \"무시\",\n\t\"ON_ANOTHER_CALL\": \"다른 통화 중입니다\",\n\t\"CREATING\": \"생성 중\",\n\t\"AVATAR\": \"아바타\",\n\t\"ONGOING_CALL\": \"진행 중인 통화\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"이미 진행 중인 통화가 있습니다\",\n\t\"RESIZE\": \"크기 조정\",\n\t\"READ_MORE\": \"더 읽기\",\n\t\"SHOW_LESS\": \"줄이기\",\n\t\"SETTINGS\": \"설정\",\n\t\"ACTIONS\": \"작업\",\n\t\"VIEW_PROFILE\": \"프로필 보기\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"개인적으로 메시지 보내기\",\n\t\"DELETE\": \"삭제\",\n\t\"DELETE_CONFIRM\": \"삭제하시겠습니까?\",\n\t\"DELETE_CHAT\": \"이 채팅을 삭제하시겠습니까?\",\n\t\"SURE_TO_DELETE_CHAT\": \"이 채팅을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.\",\n\t\"CANCEL\": \"취소\",\n\t\"LEAVE_CONFIRM\": \"그룹을 나가시겠습니까?\",\n\t\"TRANSFER_CONFIRM\": \"그룹 소유자입니다. 그룹을 나가기 전에 멤버에게 소유권을 이전해 주세요\",\n\t\"ADDING\": \"추가 중...\",\n\t\"TRANSFER\": \"이전\",\n\t\"TRANSFER_OWNERSHIP\": \"소유권 이전\",\n\t\"TRANSFERRING\": \"이전 중\",\n\t\"YES\": \"예\",\n\t\"NO\": \"아니요\",\n\t\"ANSWER\": \"답변\",\n\t\"ADD_ANOTHER_ANSWER\": \"다른 답변 추가\",\n\t\"SET_THE_ANSWERS\": \"답변 설정\",\n\t\"TRY_AGAIN\": \"다시 시도\",\n\t\"INVALID_GROUP_NAME\": \"그룹에 유효한 이름을 입력하고 다시 시도해 주세요\",\n\t\"INVALID_PASSWORD\": \"그룹에 유효한 비밀번호를 입력하고 다시 시도해 주세요\",\n\t\"INVALID_GROUP_TYPE\": \"그룹에 유효한 유형을 입력하고 다시 시도해 주세요\",\n\t\"WRONG_PASSWORD\": \"올바른 비밀번호를 입력하고 다시 시도해 주세요\",\n\t\"INVALID_POLL_QUESTION\": \"투표를 만들기 전에 필요한 질문을 입력해 주세요\",\n\t\"INVALID_POLL_OPTION\": \"투표를 만들기 전에 필요한 답변을 입력해 주세요\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"번역을 위해 선택한 언어가 원본 메시지의 언어와 유사합니다\",\n\t\"LEAVE\": \"나가기\",\n\t\"CLICK_TO_START_CONVERSATION\": \"클릭하여 대화 시작\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍위치\",\n\t\"SHARED_LOCATION\": \"공유된 위치\",\n\t\"IN_A_THREAD\": \"스레드에서\",\n\t\"CALLS\": \"통화\",\n\t\"CALL_DETAILS\": \"통화 세부 정보\",\n\t\"OFFLINE\": \"오프라인\",\n\t\"POLLS\": \"투표\",\n\t\"YOU\": \"나\",\n\t\"PRIVACY\": \"개인 정보\",\n\t\"BLOCKED_USERS\": \"차단된 사용자\",\n\t\"YOU'VE_BLOCKED\": \"차단한 사용자\",\n\t\"NO_PHOTOS\": \"사진 없음\",\n\t\"NO_VIDEOS\": \"비디오 없음\",\n\t\"NO_DOCUMENTS\": \"문서 없음\",\n\t\"JOIN\": \"참여\",\n\t\"REMOVE\": \"제거\",\n\t\"BLOCK\": \"차단\",\n\t\"CHANGE_ROLE\": \"역할 변경\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"그룹 권한과 책임을 관리하기 위해 역할을 변경할 수 있습니다.\",\n\t\"CHANGE_TO\": \"변경할 대상\",\n\t\"NEW_CHAT\": \"새 채팅\",\n\t\"FORM_COMPLETION_MESSAGE\": \"양식을 작성해 주셔서 감사합니다.\",\n\t\"HISTORY\": \"기록\",\n\t\"CANCELLED_AUDIO_CALL\": \"취소된 음성 통화\",\n\t\"CANCELLED_VIDEO_CALL\": \"취소된 영상 통화\",\n\t\"REJECTED_AUDIO_CALL\": \"거절된 음성 통화\",\n\t\"REJECTED_VIDEO_CALL\": \"거절된 영상 통화\",\n\t\"CANCELLED_CALL\": \"취소된 통화\",\n\t\"UNANSWERED_CALL\": \"응답 없는 통화\",\n\t\"RECORDING\": \"녹음\",\n\t\"PARTICIPANTS\": \"참가자\",\n\t\"CALL_HISTORY\": \"통화 기록\",\n\t\"NO_CALLS_FOUND\": \"통화를 찾을 수 없습니다\",\n\t\"ONGOING_AUDIO_CALL\": \"진행 중인 음성 통화\",\n\t\"ONGOING_VIDEO_CALL\": \"진행 중인 영상 통화\",\n\t\"CALL_DETAIL\": \"통화 세부 정보\",\n\t\"FORM\": \"양식\",\n\t\"CARD\": \"카드\",\n\t\"GENERATING_SUMMARY\": \"요약 생성 중\",\n\t\"CONVERSATION_SUMMARY\": \"대화 요약\",\n\t\"GENERATE_SUMMARY\": \"요약 생성\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"이 대화에서 어떻게 도와드릴까요? 질문을 하시면 조언해 드리겠습니다 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"질문하기\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"AI 봇에게 질문하기\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI 봇\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"한 번에 최대 10명의 사용자를 언급할 수 있습니다.\",\n\t\"ALL\": \"모두\",\n\t\"CLICK_TO_REMOVE\": \"클릭하여 제거\",\n\t\"OTHERS\": \"기타\",\n\t\"AND\": \"그리고\",\n\t\"ASK_QUESTION\": \"질문하기\",\n\t\"ADD_OPTION\": \"옵션 추가\",\n\t\"SEND\": \"보내기\",\n\t\"FULL_SCREEN_VIEWER\": \"전체 화면 뷰어\",\n\t\"TEXT_TRANSLATE\": \"텍스트 번역\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"문제가 발생한 것 같습니다\",\n\t\"NO_SUMMARY_AVAILABLE\": \"사용 가능한 요약 없음\",\n\t\"QUESTIONS\": \"질문\",\n\t\"NO_STICKERS_AVAILABLE\": \"사용 가능한 스티커 없음\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"사용 가능한 제안 없음\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"그룹을 만들거나 참여하여 여기에 나열하고 협업을 시작하세요\",\n\t\"NO_USERS_AVAILABLE\": \"사용 가능한 사용자 없음\",\n\t\"NO_GROUPS_AVAILABLE\": \"사용 가능한 그룹 없음\",\n\t\"NO_CALL_LOGS\": \"아직 통화 기록 없음\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"통화를 발신하거나 수신하여 여기에 통화 기록 목록을 확인하세요\",\n\t\"NO_CONVERSATIONS\": \"아직 대화 없음\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"새 채팅을 시작하거나 다른 사람을 대화에 초대하세요.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"사용 가능한 그룹 멤버 없음\",\n\t\"OOPS!\": \"이런!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"수신자가 메시지를 수신하고 확인하기를 기다리는 중\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"연락처를 추가하여 여기에 나열되도록 하세요.\",\n\t\"REACHED_MAX_LIMIT\": \"제한에 도달했습니다. 최대 12개의 옵션을 추가할 수 있습니다.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"투표를 만들기 전에 모든 필수 필드를 작성해 주세요.\",\n\t\"NOT_SUPPORTED\": \"이 메시지 유형은 지원되지 않습니다\",\n\t\"NO_USERS_FOUND\": \"사용자를 찾을 수 없습니다\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"검색과 일치하는 사용자를 찾을 수 없습니다. 검색어를 조정해 보세요.\",\n\t\"SAVE\": \"저장\",\n\t\"RETRY\": \"다시 시도\",\n\t\"TAP_TO_START_CONVERSATION\": \"탭하여 대화 시작\",\n\t\"TEXT_TRANSLATED\": \"텍스트 번역됨\",\n\t\"CAMERA\": \"카메라\",\n\t\"SHARE\": \"공유\",\n\t\"ATTACH_DOCUMENT\": \"문서 첨부\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"제거하시겠습니까 \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"메시지 입력...\",\n\t\"TAP_TO_REMOVE\": \"탭하여 제거\",\n\t\"ADD_CONTACTS\": \"대화를 시작하고 여기에 표시하려면 연락처를 추가하세요.\",\n\t\"SCOPE_CHANGE_INFO\": \"그룹 권한과 책임을 관리하기 위해 역할을 변경할 수 있습니다\",\n\t\"SOMETHING_WENT_WRONG\": \"문제가 발생한 것 같습니다.\",\n\t\"MENTION_UPTO\": \"최대\",\n\t\"TIME\": \"번\",\n\t\"TIMES\": \"번\",\n\t\"AT_A_TIME\": \"한 번에\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"이 기능은 특정 리소스 권한이 필요합니다. 기기 설정에서 필요한 권한을 활성화하세요.\",\n\t\"MICROPHONE_PERMISSION\": \"음성 메시지를 녹음하려면 마이크 접근 권한이 필요합니다. 설정을 탭하고 마이크를 켜세요.\",\n\t\"CAMERA_PERMISSION\": \"카메라에 대한 접근 권한이 없습니다. 접근을 활성화하려면 설정을 탭하고 카메라를 켜세요.\",\n\t\"OOPS\": \"이런!\",\n\t\"ADD_OPTIONS\": \"옵션 추가\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"함께 그리기 위해 문서 열기\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"함께 그리기 위해 화이트보드 열기\",\n\t\"TEXT\": \"텍스트\",\n\t\"BAN_MEMBER_CONFIRM\": \"차단하시겠습니까 \",\n\t\"LOGIN\": \"로그인\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Google로 계속하기\",\n\t\"JOIN_GROUP\": \"그룹 참여\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"그룹 비밀번호가 올바르지 않습니다!\",\n\t\"ENTER_PASSWORD\": \"비밀번호 입력\",\n\t\"TYPE\": \"유형\",\n\t\"USER_INFO\": \"사용자 정보\",\n\t\"DELETE_CHAT_TEXT\": \"채팅 삭제\",\n\t\"UNBLOCK\": \"차단 해제\",\n\t\"UNBLOCK_CONTACT\": \"이 연락처 차단 해제\",\n\t\"BLOCK_CONTACT\": \"이 연락처를 차단하시겠습니까?\",\n\t\"UNBLOCK_SURE\": \"이 연락처의 차단을 해제하시겠습니까? 다시 메시지를 받기 시작합니다.\",\n\t\"BLOCK_SURE\": \"이 연락처를 차단하시겠습니까? 더 이상 메시지를 받지 않습니다.\",\n\t\"VOICE\": \"음성\",\n\t\"DELETE_AND_EXIT_SURE\": \"이 채팅을 삭제하고 그룹을 나가시겠습니까? 이 작업은 취소할 수 없습니다.\",\n\t\"TRANSFER_SURE\": \"소유권을 이전하시겠습니까? 이 작업은 취소할 수 없으며, 새 소유자가 완전한 제어권을 갖게 됩니다.\",\n\t\"GROUP_INFO\": \"그룹 정보\",\n\t\"LEAVE_GROUP_TEXT\": \"이 그룹 나가기\",\n\t\"LEAVE_SURE\": \"그룹을 나가시겠습니까? 더 이상 이 채팅에서 메시지를 받지 않습니다.\",\n\t\"UNBAN_SURE\": \"이 사용자의 차단을 해제하시겠습니까?\",\n\t\"DELETE_THIS_CONVERSATION\": \"이 대화를 삭제하시겠습니까?\",\n\t\"LINK\": \"링크\",\n\t\"DELETE_THIS_MESSAGE\": \"이 메시지를 삭제하시겠습니까?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"이 메시지를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.\",\n\t\"APP_CREDENTIALS\": \"앱 자격 증명\",\n\t\"REGION\": \"지역\",\n\t\"CHAT_PRIVATELY\": \"비공개 채팅\",\n\t\"WRONG_TEXT\": \"문제가 발생한 것 같습니다.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"다시 시도해 주세요.\",\n\t\"BLOCKED_USER_DESC\": \"사용자가 차단되어 메시지를 보낼 수 없습니다\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName}님이 ${onName}님을 그룹에 추가했습니다\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName}님이 그룹에 참여했습니다\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName}님이 그룹을 나갔습니다\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName}님이 ${onName}님을 그룹에서 강퇴했습니다\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName}님이 ${onName}님을 그룹에서 차단했습니다\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName}님이 ${onName}님의 차단을 해제했습니다\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName}님이 ${onName}님을 ${role} 역할로 설정했습니다\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"아직 스티커가 없습니다.\",\n\t\"meeting\": \"회의\",\n\t\"Flag_Message_Title\": \"메시지 신고\",\n\t\"Flag_Message_Subtitle\": \"이 채팅이 커뮤니티 기준에 위배되는 경우 신고해주세요. 신고한 사실이 상대 계정에 통보되지 않습니다.\",\n\t\"Flag_Message_Remark_Label\": \"사유\",\n\t\"Flag_Message_Remark_Optional\": \"선택\",\n\t\"Flag_Message_Remark_Placeholder\": \"신고에 대한 추가 설명을 입력하세요…\",\n\t\"Flag_Message_Confirm_Yes\": \"신고\",\n\t\"Flag_Message_Confirm_No\": \"취소\",\n\t\"Flag_Message_Reason_Id_Spam\": \"스팸 / 원치 않는 콘텐츠\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"명백하거나 부적절한 콘텐츠\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"모욕적이거나 위협적인 행동\",\n\t\"Message_List_Option_Flag_Message\": \"신고\",\n    \"Flag_Error_Text\":\"문제가 발생했습니다. 확인 후 다시 시도해 주세요.\",\n\t\"Flag_Empty_Submit_Text\":\"제출할 수 없습니다. 이 메시지를 신고하기 전에 이유를 선택하세요.\",\n\t\"START_REPORTING\": \"보고 시작\",\n\t\"LOGOUT\": \"로그아웃\",\n\t\"AI_ASSISTANTS\": \"AI 도우미\",\n\t\"CREATE_CONVERSATION\": \"대화 생성\",\n\t\"EDIT_MODERATION\": \"편집 실패. 귀하의 메시지는 관리 정책으로 인해 차단되었습니다.\",\n    \"BLOCKED_MODERATION\":\"귀하의 메시지는 관리 정책으로 인해 차단되었습니다.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"대화 기록이 없습니다\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"우리 측에서 문제가 발생했습니다. 다시 시도해주세요\",\n\t\"START_A_CHAT\": \"'새로운 채팅' 버튼을 눌러 채팅을 시작하세요\",\n\t\"CHAT_HISTORY\": \"채팅 기록\",\n\t\"ASK_ANYTHING\": \"무엇이든 물어보세요...\",\n\t\"AI_ASSISTANT\": \"AI 어시스턴트\",\n\t\"SEARCH_PLACEHOLDER\": \"검색...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"결과 없음\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"메시지와 대화를 검색하려면 입력을 시작하세요\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"검색 결과 없음\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"일치하는 항목을 찾지 못했습니다. 다른 키워드를 사용해보세요.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"대화 불러오기 오류\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"메시지 불러오기 오류\",\n\t\"SEARCH_TRY_AGAIN\": \"나중에 다시 시도해주세요\",\n\t\"SEARCH_SEE_MORE\": \"더 보기\",\n\t\"SEARCH_LOADING\": \"불러오는 중...\",\n\t\"SEARCH_FILTER_UNREAD\": \"읽지 않음\",\n\t\"SEARCH_FILTER_GROUPS\": \"그룹\",\n\t\"SEARCH_FILTER_PHOTOS\": \"사진\",\n\t\"SEARCH_FILTER_VIDEOS\": \"비디오\",\n\t\"SEARCH_FILTER_LINKS\": \"링크\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"문서\",\n\t\"SEARCH_FILTER_AUDIO\": \"오디오\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"대화\",\n\t\"SEARCH_FILTER_MESSAGES\": \"메시지\",\n\t\"SEARCH_NO_MESSAGES\": \"메시지가 없습니다\",\n\t\"MESSAGE_REPORTED\": \"메시지가 신고되었습니다\",\n\t\"MARK_AS_UNREAD\": \"읽지 않음 표시\",\n\t\"NEW\": \"새로운\",\n\t\"INSERT_LINK\": \"링크 삽입\",\n\t\"EDIT_LINK\": \"링크 편집\",\n\t\"LINK_TEXT\": \"링크 텍스트\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"삽입\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"더 이상 멤버가 아니므로 이 그룹에 메시지를 보낼 수 없습니다.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/lt/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Accetta\",\n\t\"ACTIONS\": \"Azioni\",\n\t\"ACTIVITY\": \"Attività\",\n\t\"ADD\": \"Inserisci\",\n\t\"ADDED\": \"aggiunto\",\n\t\"ADDING\": \"Aggiungendo...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Aggiungi un'altra risposta\",\n\t\"ADD_CONTACTS\": \"Aggiungi contatti per iniziare conversazioni e vederli elencati qui.\",\n\t\"ADD_MEMBERS\": \"Aggiungi membri\",\n\t\"ADD_NEW_OPTION\": \"Aggiungi nuova opzione\",\n\t\"ADD_OPTION\": \"Aggiungi opzione\",\n\t\"ADD_OPTIONS\": \"Aggiungi opzioni\",\n\t\"ADD_REACTION\": \"Aggiungi reazione\",\n\t\"ADD_TO_CHAT\": \"Aggiungi alla chat\",\n\t\"ADMIN\": \"Amministratore\",\n\t\"ADMINISTRATOR\": \"Amministratore\",\n\t\"AI\": \"AI\",\n\t\"ALL\": \"Tutti\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Tutti\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notifica a tutti in questo gruppo\",\n\t\"AND\": \"un\",\n\t\"ANIMALES_NATURE\": \"Animali e natura\",\n\t\"ANSWER\": \"Risposta\",\n\t\"APP_CREDENTIALS\": \"Programėlės prisijungimo duomenys\",\n\t\"ASK_QUESTION\": \"Fai una domanda\",\n\t\"AT\": \"al\",\n\t\"ATTACH\": \"Allega\",\n\t\"ATTACH_AUDIO\": \"Allega audio\",\n\t\"ATTACH_DOCUMENT\": \"Allega documento\",\n\t\"ATTACH_FILE\": \"Allega file\",\n\t\"ATTACH_IMAGE\": \"Allega immagine\",\n\t\"ATTACH_VIDEO\": \"Allega video\",\n\t\"AT_A_TIME\": \"vienu metu\",\n\t\"AUDIO_CALL\": \"Chiamata audio\",\n\t\"AVATAR\": \"Avatar\",\n\t\"BADGE_COUNT\": \"Numero di distintivi\",\n\t\"BAN\": \"Divieto\",\n\t\"BANNED\": \"vietato\",\n\t\"BANNED_MEMBERS\": \"Membri esclusi\",\n\t\"BAN_MEMBER_CONFIRM\": \"Sei sicuro di voler vietare \",\n\t\"BLOCK\": \"Blocca\",\n\t\"BLOCKED_USERS\": \"Utenti bloccati\",\n\t\"BLOCKED_USER_DESC\": \"Negalima išsiųsti žinutės, nes naudotojas yra užblokuotas\",\n\t\"BLOCK_CONTACT\": \"Blokuoti šį kontaktą?\",\n\t\"BLOCK_SURE\": \"Ar tikrai norite blokuoti šį kontaktą? Nesiimsite daugiau pranešimų iš jo.\",\n\t\"BLOCK_USER\": \"Blocca utente\",\n\t\"CALLING\": \"Chiamata...\",\n\t\"CALLS\": \"Chiamate\",\n\t\"CALL_ACCEPTED\": \"Chiamata accettata\",\n\t\"CALL_ANSWERED\": \"Chiamata con risposta\",\n\t\"CALL_BUSY\": \"Chiamata occupata\",\n\t\"CALL_CANCELLED\": \"Chiamata annullata\",\n\t\"CALL_DETAIL\": \"Dettagli della chiamata\",\n\t\"CALL_DETAILS\": \"Dettagli della chiamata\",\n\t\"CALL_ENDED\": \"Chiamata terminata\",\n\t\"CALL_HISTORY\": \"Cronologia chiamate\",\n\t\"CALL_INITIATED\": \"Chiamata avviata\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Effettua o ricevi chiamate per visualizzare la cronologia delle chiamate elencata qui\",\n\t\"CALL_REJECTED\": \"Chiamata rifiutata\",\n\t\"CALL_UNANSWERED\": \"Chiamata senza risposta\",\n\t\"CAMERA\": \"fotocamera\",\n\t\"CAMERA_PERMISSION\": \"Non abbiamo accesso alla tua fotocamera. Per abilitare l'accesso, tocca Impostazioni e attiva Fotocamera.\",\n\t\"CANCEL\": \"Annulla\",\n\t\"CANCELLED_AUDIO_CALL\": \"Chiamata audio annullata\",\n\t\"CANCELLED_CALL\": \"Chiamata annullata\",\n\t\"CANCELLED_VIDEO_CALL\": \"Videochiamata annullata\",\n\t\"CANT__LOAD__CHATS\": \"Impossibile caricare le chat\",\n\t\"CARD\": \"Carta\",\n\t\"CHANGE_ROLE\": \"Cambia ruolo\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"È possibile modificare i ruoli per gestire le autorizzazioni e le responsabilità del gruppo.\",\n\t\"CHANGE_SCOPE\": \"Ambito di modifica\",\n\t\"CHANGE_TO\": \"Cambia in\",\n\t\"CHATS\": \"Chat\",\n\t\"CHAT_PRIVATELY\": \"Pokalbiai privačiai\",\n\t\"CLICK_TO_REMOVE\": \"Fai clic per rimuovere\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Fai clic per iniziare la conversazione\",\n\t\"CLOSE\": \"Chiudi\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Collabora utilizzando un documento\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Collabora utilizzando una lavagna\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Documento collaborativo\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Lavagna collaborativa\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Chiedi ai bot AI\",\n\t\"COMETCHAT_ASK_BOT\": \"Chiedere\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"Bot AI\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Come posso aiutarti in questa conversazione? Per favore, fammi una domanda e ti consiglierò 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Inserisci qui il tuo messaggio\",\n\t\"CONTINUE\": \"Continua\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Tęsti su Google\",\n\t\"CONVERSATIONS\": \"Conversazioni\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Inizia una nuova chat o invita altri a partecipare alla conversazione.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Conversazioni con messaggi\",\n\t\"CONVERSATION_DELETED\": \"Conversazione eliminata\",\n\t\"CONVERSATION_LIST\": \"Elenco delle conversazioni\",\n\t\"CONVERSATION_LIST_ITEM\": \"Elemento dell'elenco delle conversazioni\",\n\t\"CONVERSATION_STARTER\": \"Avvio della conversazione\",\n\t\"CONVERSATION_SUMMARY\": \"Riepilogo della conversazione\",\n\t\"COPY\": \"Copia\",\n\t\"CREATE\": \"Crea\",\n\t\"CREATED_DOCUMENT\": \"Hai creato un nuovo documento collaborativo\",\n\t\"CREATED_WHITEBOARD\": \"Hai creato una nuova lavagna collaborativa\",\n\t\"CREATE_GROUP\": \"Crea gruppo\",\n\t\"CREATE_POLL\": \"Apklausa\",\n\t\"CREATING\": \"Creando\",\n\t\"CUSTOM_MESSAGE\": \"Hai un messaggio\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Documento\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Posizione\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Sondaggio\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Adesivo\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Lavagna\",\n\t\"DATA_ITEM\": \"Elemento di dati\",\n\t\"DECLINE\": \"Declino\",\n\t\"DELETE\": \"Elimina\",\n\t\"DELETE_AND_EXIT\": \"Elimina ed esci\",\n\t\"DELETE_AND_EXIT_SURE\": \"Ar tikrai norite ištrinti šį pokalbį ir išeiti iš grupės? Šis veiksmas negrįžtamas.\",\n\t\"DELETE_CHAT\": \"Eliminare questa chat?\",\n\t\"DELETE_CHAT_TEXT\": \"Ištrinti pokalbį\",\n\t\"DELETE_CONFIRM\": \"Sei sicuro di volerlo eliminare?\",\n\t\"DELETE_CONVERSATION\": \"Eliminare una conversazione?\",\n\t\"DELETE_MESSAGE\": \"Elimina messaggio\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Ar tikrai norite ištrinti šią žinutę? Šio veiksmo negalima atšaukti.\",\n\t\"DELETE_MSG_TEXT\": \"Questo messaggio è stato eliminato\",\n\t\"DELETE_THIS_CONVERSATION\": \" Ištrinti šį pokalbį?\",\n\t\"DELETE_THIS_MESSAGE\": \"Ištrinti šią žinutę?\",\n\t\"DELIVERED\": \"Consegnato\",\n\t\"DETAILS\": \"Dettagli\",\n\t\"DOCS\": \"Documenti\",\n\t\"DOCUMENT\": \"Documento\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Apri il documento per modificare i contenuti insieme\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Apri la lavagna per disegnare insieme\",\n\t\"EDIT\": \"Modifica\",\n\t\"EDITED\": \"Modificato\",\n\t\"EDIT_MESSAGE\": \"Modifica messaggio\",\n\t\"EMOJI\": \"emoji\",\n\t\"ENTER_GROUP_NAME\": \"Inserisci il nome del gruppo\",\n\t\"ENTER_GROUP_PASSWORD\": \"Inserisci la password del gruppo\",\n\t\"ENTER_PASSWORD\": \"Įveskite slaptažodį\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Įveskite savo pranešimą čia\",\n\t\"ENTER_YOUR_OPTION\": \"Inserisci la tua opzione\",\n\t\"ENTER_YOUR_PASSWORD\": \"Inserisci la tua password\",\n\t\"ENTER_YOUR_QUESTION\": \"Inserisci la tua domanda\",\n\t\"ERROR\": \"Errore\",\n\t\"ERROR_TEXT\": \"Sembra che qualcosa sia andato storto. Per favore riprova.\",\n\t\"FLAGS\": \"Bandiere\",\n\t\"FOOD_DRINK\": \"Cibi e bevande\",\n\t\"FORM\": \"Modulo\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Grazie per aver compilato il modulo.\",\n\t\"FRIDAY\": \"Venerdì\",\n\t\"FULL_SCREEN_VIEWER\": \"Visualizzatore a schermo intero\",\n\t\"GENERATE_SUMMARY\": \"Genera un riepilogo\",\n\t\"GENERATING_ICEBREAKERS\": \"Generazione di rompighiaccio\",\n\t\"GENERATING_REPLIES\": \"Generazione di risposte\",\n\t\"GENERATING_SUMMARY\": \"Generazione del riassunto\",\n\t\"GROUPS\": \"Gruppi\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Crea o unisciti a gruppi per vederli elencati qui e iniziare a collaborare\",\n\t\"GROUPS_WITH_MESSAGES\": \"Gruppi con messaggi\",\n\t\"GROUP_INFO\": \"Grupės informacija\",\n\t\"GROUP_LIST\": \"Elenco dei gruppi\",\n\t\"GROUP_MEMBERS\": \"Membri del gruppo\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Aggiungi i contatti per vederli elencati qui.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"In attesa che i destinatari ricevano e visualizzino il messaggio\",\n\t\"GROUP_NAME_BLANK\": \"Il nome del gruppo non può essere vuoto\",\n\t\"GROUP_PASSWORD_BLANK\": \"La password di gruppo non può essere vuota\",\n\t\"GROUP_TYPE_BLANK\": \"Il tipo di gruppo non può essere vuoto\",\n\t\"HELP\": \"Aiuto\",\n\t\"HISTORY\": \"Storia\",\n\t\"IGNORE\": \"Ignora\",\n\t\"INCOMING_AUDIO_CALL\": \"Chiamata audio in arrivo\",\n\t\"INCOMING_CALL\": \"Chiamata in arrivo\",\n\t\"INCOMING_VIDEO_CALL\": \"Videochiamata in arrivo\",\n\t\"INFO\": \"Informazioni\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"ha avviato una chiamata audio\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"ha avviato una videochiamata\",\n\t\"INVALID_GROUP_NAME\": \"Inserisci un nome valido per il gruppo e riprova\",\n\t\"INVALID_GROUP_TYPE\": \"Inserisci un tipo valido per il gruppo e riprova\",\n\t\"INVALID_PASSWORD\": \"Inserisci una password valida per il gruppo e riprova\",\n\t\"INVALID_POLL_OPTION\": \"Inserisci la risposta richiesta prima di creare un sondaggio\",\n\t\"INVALID_POLL_QUESTION\": \"Inserisci la domanda richiesta prima di creare un sondaggio\",\n\t\"IN_A_THREAD\": \"In un thread\",\n\t\"IS_TYPING\": \"sta digitando...\",\n\t\"JOIN\": \"Unisciti\",\n\t\"JOINED\": \"uniti\",\n\t\"JOIN_GROUP\": \"Prisijungti prie grupės\",\n\t\"JUMP\": \"Salta\",\n\t\"KICK\": \"Calciare\",\n\t\"KICKED\": \"calciato\",\n\t\"LAST_ACTIVE_AT\": \"Ultimo attivo a\",\n\t\"LAST_SEEN\": \"Visto l'ultima volta\",\n\t\"LAUNCH\": \"Lanciare\",\n\t\"LEAVE\": \"Lasciare\",\n\t\"LEAVE_CONFIRM\": \"Sei sicuro di voler lasciare il gruppo?\",\n\t\"LEAVE_GROUP\": \"Lascia il gruppo\",\n\t\"LEAVE_GROUP_TEXT\": \"Išeiti iš šios grupės\",\n\t\"LEAVE_SURE\": \"Ar tikrai norite išeiti iš grupės? Nebeturėsite jokių pranešimų iš šio pokalbio.\",\n\t\"LEFT\": \"sinistra\",\n\t\"LEFT_THE_CALL\": \"ha lasciato la chiamata\",\n\t\"LINK\": \"Nuoroda\",\n\t\"LIVE_REACTION\": \"Reazione dal vivo\",\n\t\"LOADING\": \"Caricamento...\",\n\t\"LOCALIZE\": \"Localizza\",\n\t\"LOGIN\": \"Prisijungti\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Sembra che qualcosa sia andato storto\",\n\t\"MADE\": \"fatto\",\n\t\"MEDIA_MESSAGE\": \"Messaggio multimediale\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Prenota un nuovo slot\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Nessuna fascia oraria disponibile per questa data. Prova con un'altra data.\",\n\t\"MEETING_SCHEDULED\": \"La tua riunione è stata programmata.\",\n\t\"MEETING_SCHEDULER\": \"Pianificatore di riunioni\",\n\t\"MEETING_SLOT_BOOK\": \"Fascia oraria non più disponibile. Per favore scegline un altro.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Prova con una data diversa.\",\n\t\"MEET_WITH\": \"Incontro con\",\n\t\"MEMBER\": \"Membro\",\n\t\"MEMBERS\": \"Membri\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Puoi menzionare fino a 10 utenti contemporaneamente.\",\n\t\"MENTION_UPTO\": \"Puoi menzionare fino a\",\n\t\"MESSAGE\": \"Messaggio\",\n\t\"MESSAGES\": \"Messaggi\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"MESSAGE_COMPOSER\": \"Compositore di messaggi\",\n\t\"MESSAGE_COPIED\": \"Messaggio copiato negli appunti.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Messaggio eliminato con successo.\",\n\t\"MESSAGE_EDITED\": \"Messaggio aggiornato con successo.\",\n\t\"MESSAGE_FILE\": \"Fascicolo\",\n\t\"MESSAGE_HEADER\": \"Intestazione del messaggio\",\n\t\"MESSAGE_IMAGE\": \"Immagine\",\n\t\"MESSAGE_INFORMATION\": \"Informazioni sui messaggi\",\n\t\"MESSAGE_IS_DELETED\": \"Il messaggio è eliminato\",\n\t\"MESSAGE_LIST\": \"Elenco messaggi\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} pridėjo ${onName} į grupę\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} užblokavo ${onName} grupėje\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} prisijungė prie grupės\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} pašalino ${onName} iš grupės\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} paliko grupę\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} paskyrė ${onName} kaip ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} atblokavo ${onName}\",\n\t\"MESSAGE_PRIVATELY\": \"Messaggio privato\",\n\t\"MESSAGE_RECEIPT\": \"Ricevuta del messaggio\",\n\t\"MESSAGE_TRANSLATED\": \"Messaggio tradotto con successo.\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MICROPHONE_PERMISSION\": \"Per registrare un messaggio vocale, abbiamo bisogno di accedere al tuo microfono. Tocca Impostazioni e attiva il microfono.\",\n\t\"MISSED_AUDIO_CALL\": \"Chiamata audio persa\",\n\t\"MISSED_CALL\": \"Chiamata persa\",\n\t\"MISSED_VIDEO_CALL\": \"Videochiamata persa\",\n\t\"MISSED_VOICE_CALL\": \"Chiamata vocale persa\",\n\t\"MODERATOR\": \"presentatrice\",\n\t\"MONDAY\": \"Lunedì\",\n\t\"MORE\": \"Altro\",\n\t\"MORE_TIMES\": \"Più volte\",\n\t\"NAME\": \"Nome\",\n\t\"NEW_CHAT\": \"Nuova chat\",\n\t\"NEW_MESSAGE\": \"nuovo messaggio\",\n\t\"NEW_MESSAGES\": \"nuovi messaggi\",\n\t\"NEW__GROUP\": \"Nuovo gruppo\",\n\t\"NO\": \"No\",\n\t\"NOTIFICATIONS\": \"Notifiche\",\n\t\"NOT_SUPPORTED\": \"Questo tipo di messaggio non è supportato\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Nessun membro bannato trovato\",\n\t\"NO_CALLS_FOUND\": \"Nessuna chiamata trovata\",\n\t\"NO_CALLS_SELECTED\": \"Nessuna chiamata selezionata\",\n\t\"NO_CALL_LOGS\": \"Ancora nessun registro delle chiamate\",\n\t\"NO_CHATS_FOUND\": \"Nessuna chat trovata\",\n\t\"NO_CHATS_SELECTED\": \"Nessuna chat selezionata\",\n\t\"NO_CONVERSATIONS\": \"Ancora nessuna conversazione»;  \",\n\t\"NO_DOCUMENTS\": \"Nessun documento\",\n\t\"NO_GROUPS_AVAILABLE\": \"Nessun gruppo disponibile\",\n\t\"NO_GROUPS_FOUND\": \"Nessun gruppo trovato\",\n\t\"NO_GROUPS_SELECTED\": \"Nessun gruppo selezionato\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Nessun membro del gruppo disponibile\",\n\t\"NO_MESSAGES_FOUND\": \"Nessun messaggio trovato\",\n\t\"NO_PHOTOS\": \"Nessuna foto\",\n\t\"NO_RECIPIENT\": \"Nessun destinatario\",\n\t\"NO_RECIPIENTS\": \"Nessun destinatario\",\n\t\"NO_RECORDS_FOUND\": \"Nessun record trovato\",\n\t\"NO_REPLIES_FOUND\": \"Nessuna risposta trovata\",\n\t\"NO_STICKERS_AVAILABLE\": \"Nessun adesivo disponibile\",\n\t\"NO_STICKERS_FOUND\": \"Nessun adesivo trovato\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Nessun suggerimento disponibile\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Nessun riepilogo disponibile\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Nessuna fascia oraria disponibile\",\n\t\"NO_USERS_AVAILABLE\": \"Nessun utente disponibile\",\n\t\"NO_USERS_FOUND\": \"Nessun utente trovato\",\n\t\"NO_USERS_SELECTED\": \"Nessun utente selezionato\",\n\t\"NO_VIDEOS\": \"Nessun video\",\n\t\"NO_VOTE\": \"Nessun voto\",\n\t\"OBJECTS\": \"Oggetti\",\n\t\"OFFLINE\": \"Offline\",\n\t\"ONGOING_AUDIO_CALL\": \"Chiamata audio in corso\",\n\t\"ONGOING_CALL\": \"Chiamata in corso\",\n\t\"ONGOING_VIDEO_CALL\": \"Videochiamata in corso\",\n\t\"ONLINE\": \"Online\",\n\t\"ON_ANOTHER_CALL\": \"è in un'altra chiamata\",\n\t\"OOPS\": \"OPS\",\n\t\"OOPS!\": \"OPS!\",\n\t\"OPEN_DOCUMENT\": \"Apri documento\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Atidarykite dokumentą, kad galėtumėte piešti kartu\",\n\t\"OPEN_WHITEBOARD\": \"Apri lavagna\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Atidarykite lentą, kad galėtumėte piešti kartu\",\n\t\"OPTIONS\": \"Opzioni\",\n\t\"OTHER\": \"Altro\",\n\t\"OTHERS\": \"altri\",\n\t\"OUTGOING_AUDIO_CALL\": \"Chiamata audio in uscita\",\n\t\"OUTGOING_CALL\": \"Chiamata in uscita\",\n\t\"OUTGOING_VIDEO_CALL\": \"Videochiamata in uscita\",\n\t\"OWNER\": \"Proprietario\",\n\t\"PARTICIPANT\": \"Partecipante\",\n\t\"PARTICIPANTS\": \"Partecipanti\",\n\t\"PASSWORD\": \"Password\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Grupės slaptažodis neteisingas!\",\n\t\"PASSWORD_PROTECTED\": \"Protetto con password\",\n\t\"PHOTOS\": \"Foto\",\n\t\"PICK_YOUR_EMOJI\": \"Scegli la tua emoji\",\n\t\"POLLS\": \"Sondaggi\",\n\t\"PREFERENCES\": \"Preferenze\",\n\t\"PRIVACY\": \"Privacy\",\n\t\"PRIVACY_AND_SECURITY\": \"Privacy e sicurezza\",\n\t\"PRIVATE\": \"Privato\",\n\t\"PRIVATE_GROUP\": \"Gruppo privato\",\n\t\"PROTECTED_GROUP\": \"Gruppo protetto\",\n\t\"PUBLIC\": \"Pubblico\",\n\t\"QUESTION\": \"Domanda\",\n\t\"QUESTIONS\": \"Domande\",\n\t\"REACHED_MAX_LIMIT\": \"Hai raggiunto il limite. Puoi aggiungere fino a 12 opzioni.\",\n\t\"REACT\": \"Reagire\",\n\t\"REACTED\": \"reagito\",\n\t\"REACT_TO_MESSAGE\": \"Reagire a un messaggio\",\n\t\"READ\": \"Leggi\",\n\t\"READ_MORE\": \"Baca lebih lanjut\",\n\t\"RECEIPT_INFORMATION\": \"Informazioni sulla ricevuta\",\n\t\"RECORDING\": \"Registrazione\",\n\t\"REGION\": \"Regionas\",\n\t\"REJECTED_AUDIO_CALL\": \"Chiamata audio rifiutata\",\n\t\"REJECTED_CALL\": \"chiamata rifiutata\",\n\t\"REJECTED_VIDEO_CALL\": \"Videochiamata rifiutata\",\n\t\"REMOVE\": \"Rimuovi\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Sei sicuro di voler rimuovere\",\n\t\"REPLIES\": \"risposte\",\n\t\"REPLY\": \"rispondere\",\n\t\"REPLY_IN_THREAD\": \"Rispondi nel thread\",\n\t\"REPLY_TO_THREAD\": \"Rispondi al thread\",\n\t\"REPORT_PROBLEM\": \"Segnalare un problema\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Compila tutti i campi obbligatori prima di creare un sondaggio\",\n\t\"RESIZE\": \"Ridimensiona\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Questa funzionalità richiede autorizzazioni specifiche per le risorse. Abilita le autorizzazioni necessarie nelle impostazioni del tuo dispositivo\",\n\t\"RETRY\": \"Riprova\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"La lingua selezionata per la traduzione è simile alla lingua del messaggio originale\",\n\t\"SATURDAY\": \"Sabato\",\n\t\"SAVE\": \"Salva\",\n\t\"SCHEDULE\": \"Pianificazione\",\n\t\"SCOPE\": \"Ambito\",\n\t\"SCOPE_CHANGE_INFO\": \"Puoi cambiare ruoli per gestire le autorizzazioni e le responsabilità del gruppo\",\n\t\"SEARCH\": \"Cerca\",\n\t\"SEARCH_EMOJI\": \"Cerca emoji\",\n\t\"SEEN\": \"Visto\",\n\t\"SELECT_A_DATE\": \"Seleziona una data\",\n\t\"SELECT_DAY\": \"Seleziona un giorno\",\n\t\"SELECT_GROUP_TYPE\": \"Seleziona il tipo di gruppo\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Seleziona la sorgente audio in ingresso\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Seleziona la sorgente audio di uscita\",\n\t\"SELECT_TIME\": \"Seleziona un orario\",\n\t\"SELECT_VIDEO_SOURCE\": \"Seleziona la sorgente video\",\n\t\"SELECT__GROUP\": \"Seleziona un gruppo per iniziare a inviare messaggi\",\n\t\"SELECT__USER\": \"Seleziona un utente per iniziare a inviare messaggi\",\n\t\"SEND\": \"Inviare\",\n\t\"SEND_MESSAGE\": \"Invia messaggio\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Invia un messaggio in privato\",\n\t\"SENT\": \"Inviato\",\n\t\"SETTINGS\": \"Impostazioni\",\n\t\"SET_THE_ANSWERS\": \"IMPOSTA LE RISPOSTE\",\n\t\"SHARE\": \"Dalintis\",\n\t\"SHARED\": \"Condiviso\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"ha condiviso un documento collaborativo\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"ha condiviso una lavagna collaborativa\",\n\t\"SHARED_FILE\": \"File condiviso\",\n\t\"SHARED_LOCATION\": \"Posizione condivisa\",\n\t\"SHARED_MEDIA\": \"Media condivisi\",\n\t\"SHOW_LESS\": \"Tunjukkan kurang\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Sei sicuro di voler vedere contenuti non sicuri?\",\n\t\"SMART_REPLIES\": \"Risposte intelligenti\",\n\t\"SMILEY_PEOPLE\": \"Smiley e persone\",\n\t\"SOMETHING_WENT_WRONG\": \"Atrodo, kažkas nutiko ne taip.\",\n\t\"SOMETHING_WRONG\": \"Qualcosa è andato storto. Per favore riprova.\",\n\t\"SOUND_MANAGER\": \"Gestore del suono\",\n\t\"STATUS_INDICATOR\": \"Indicatore di stato\",\n\t\"STICKER\": \"Adesivo\",\n\t\"SUGGEST_A_REPLY\": \"Suggerisci una risposta\",\n\t\"SUNDAY\": \"Domenica\",\n\t\"SURE_TO_DELETE_CHAT\": \"Sei sicuro di voler eliminare questa chat? Questa azione non può essere annullata.\",\n\t\"SYMBOLS\": \"Simboli\",\n\t\"TAP_TO_REMOVE\": \"Tocca per rimuovere\",\n\t\"TAP_TO_START_CONVERSATION\": \"Tocca per avviare la conversazione\",\n\t\"TEXT\": \"Tekstas\",\n\t\"TEXT_TRANSLATE\": \"Traduzione del testo\",\n\t\"TEXT_TRANSLATED\": \"Testo tradotto\",\n\t\"THEME\": \"Tema\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Questo messaggio è stato eliminato\",\n\t\"THREAD\": \"Discussione\",\n\t\"THURSDAY\": \"Giovedì\",\n\t\"TIME\": \"tempo\",\n\t\"TIMES\": \"volte\",\n\t\"TIME_ZONE\": \"Fuso orario\",\n\t\"TODAY\": \"Oggi\",\n\t\"TRANSFER\": \"Trasferimento\",\n\t\"TRANSFERRING\": \"Trasferimento\",\n\t\"TRANSFER_CONFIRM\": \"Sei il proprietario del gruppo; trasferisci la proprietà a un membro prima di lasciare il gruppo\",\n\t\"TRANSFER_OWNERSHIP\": \"Trasferisci la proprietà\",\n\t\"TRANSFER_SURE\": \"Ar tikrai norite perduoti nuosavybės teises? Šio veiksmo negalima atšaukti, o naujas savininkas įgis visišką kontrolę.\",\n\t\"TRANSLATE\": \"Tradurre\",\n\t\"TRANSLATED_MESSAGE\": \"Messaggio tradotto\",\n\t\"TRANSLATE_MESSAGE\": \"Traduci messaggio\",\n\t\"TRAVEL_PLACES\": \"Viaggi e luoghi\",\n\t\"TRY_AGAIN\": \"Riprova\",\n\t\"TUESDAY\": \"martedì\",\n\t\"TYPE\": \"Tipas\",\n\t\"TYPING\": \"Digitando...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Chiamata audio senza risposta\",\n\t\"UNANSWERED_CALL\": \"Chiamata senza risposta\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Videochiamata senza risposta\",\n\t\"UNBAN\": \"Unban\",\n\t\"UNBANNED\": \"sbandato\",\n\t\"UNBAN_SURE\": \"Ar tikrai norite panaikinti šio naudotojo blokavimą?\",\n\t\"UNBLOCK\": \"Atblokuoti\",\n\t\"UNBLOCK_CONTACT\": \"Atblokuoti šį kontaktą\",\n\t\"UNBLOCK_SURE\": \"Ar tikrai norite atblokuoti šį kontaktą? Vėl pradėsite gauti nuo jo pranešimus.\",\n\t\"UNBLOCK_USER\": \"Sblocca utente\",\n\t\"USERS\": \"Utenti\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Non siamo riusciti a trovare utenti corrispondenti alla tua ricerca. Prova a modificare la tua ricerca.\",\n\t\"USERS_WITH_MESSAGES\": \"Utenti con messaggi\",\n\t\"USER_INFO\": \"Vartotojo informacija\",\n\t\"USER_LIST\": \"Elenco utenti\",\n\t\"VIDEOS\": \"Video\",\n\t\"VIDEO_CALL\": \"Videochiamata\",\n\t\"VIEW\": \"Visualizza\",\n\t\"VIEW_DETAIL\": \"Visualizza dettagli\",\n\t\"VIEW_MEMBERS\": \"Visualizza i membri\",\n\t\"VIEW_ON_YOUTUBE\": \"Visualizza su Youtube\",\n\t\"VIEW_PROFILE\": \"Visualizza profilo\",\n\t\"VISIT\": \"Visita\",\n\t\"VOICE\": \"Balsas\",\n\t\"VOICE_CALL\": \"Chiamata vocale\",\n\t\"VOICE_RECORDING\": \"Registrazione vocale\",\n\t\"VOTE\": \"voto\",\n\t\"VOTES\": \"voti\",\n\t\"WEDNESDAY\": \"Mercoledì\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Vuoi eliminare questa conversazione? Questa conversazione verrà eliminata da tutti i tuoi dispositivi.\",\n\t\"WRONG_FILE_TYPE\": \"Hai selezionato un tipo di file diverso. Scegli il file appropriato.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Šis failo tipas neleidžiamas.\",\n\t\"WRONG_PASSWORD\": \"Inserisci la password corretta e riprova\",\n\t\"WRONG_TEXT\": \"Atrodo, kažkas nutiko ne taip.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Prašome bandyti dar kartą.\",\n\t\"YES\": \"sì\",\n\t\"YESTERDAY\": \"Ieri\",\n\t\"YOU\": \"Tu\",\n\t\"YOU'VE_BLOCKED\": \"Hai bloccato\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"È già in corso una chiamata\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Hai eliminato questo messaggio\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Jūs dar neturite jokių lipdukų.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Hai avviato una chiamata audio\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Hai avviato una videochiamata\",\n\t\"meeting\": \"Susitikimas\",\n\t\"Flag_Message_Title\": \"Pranešti žinutę\",\n\t\"Flag_Message_Subtitle\": \"Praneškite apie šį pokalbį, jei jis pažeidžia mūsų Bendruomenės standartus. Mes neinformuosime paskyros, kurią jūs pranešėte.\",\n\t\"Flag_Message_Remark_Label\": \"Priežastis\",\n\t\"Flag_Message_Remark_Optional\": \"Pasirinktinai\",\n\t\"Flag_Message_Remark_Placeholder\": \"Pateikite papildomą kontekstą savo pranešimui…\",\n\t\"Flag_Message_Confirm_Yes\": \"Pranešti\",\n\t\"Flag_Message_Confirm_No\": \"Atšaukti\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Šlamštas / nepageidaujamas turinys\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Aiškus arba netinkamas turinys\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Įžeidžiantis arba grasinantis elgesys\",\n\t\"Message_List_Option_Flag_Message\": \"Pranešti\",\n\t\"Flag_Error_Text\":\"Įvyko klaida. Patikrinkite ir bandykite dar kartą.\",\n\t\"Flag_Empty_Submit_Text\":\"Nepavyko pateikti. Prieš pranešdami apie šią žinutę, pasirinkite priežastį.\",\n\t\"START_REPORTING\": \"Pradėti ataskaitą\",\n\t\"LOGOUT\": \"Atsijungti\",\n\t\"AI_ASSISTANTS\": \"DI asistentai\",\n\t\"CREATE_CONVERSATION\": \"Sukurti pokalbį\",\n\t\"EDIT_MODERATION\": \"Redagavimas nepavyko. Jūsų pranešimas buvo užblokuotas dėl moderavimo politikos.\",\n\t\"BLOCKED_MODERATION\": \"Jūsų pranešimas buvo užblokuotas dėl moderavimo politikos.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Pokalbių istorija nerasta\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Kažkas nepavyko iš mūsų pusės. Pabandykite dar kartą.\",\n\t\"START_A_CHAT\": \"Pradėkite pokalbį paspaudę „Naujas pokalbis“ mygtuką\",\n\t\"CHAT_HISTORY\": \"Pokalbių istorija\",\n\t\"ASK_ANYTHING\": \"Klauskite bet ko...\",\n\t\"AI_ASSISTANT\": \"AI asistentas\",\n\t\"SEARCH_PLACEHOLDER\": \"Ieškoti...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Rezultatų nėra\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Pradėkite rašyti, kad ieškotumėte žinučių ir pokalbių\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Rezultatų nerasta\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Nieko neradome. Pabandykite kitą raktažodį.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Klaida įkeliant pokalbius\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Klaida įkeliant žinutes\",\n\t\"SEARCH_TRY_AGAIN\": \"Bandykite dar kartą vėliau\",\n\t\"SEARCH_SEE_MORE\": \"Rodyti daugiau\",\n\t\"SEARCH_LOADING\": \"Įkeliama...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Neskaityta\",\n\t\"SEARCH_FILTER_GROUPS\": \"Grupės\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Nuotraukos\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Vaizdo įrašai\",\n\t\"SEARCH_FILTER_LINKS\": \"Nuorodos\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Dokumentai\",\n\t\"SEARCH_FILTER_AUDIO\": \"Garsas\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Pokalbiai\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Žinutės\",\n\t\"SEARCH_NO_MESSAGES\": \"Nėra žinučių\",\n\t\"MESSAGE_REPORTED\": \"Pranešimas praneštas\",\n\t\"MARK_AS_UNREAD\": \"Pažymėti neskaitytą\",\n\t\"NEW\": \"Naujas\",\n\t\"INSERT_LINK\": \"Įterpti nuorodą\",\n\t\"EDIT_LINK\": \"Redaguoti nuorodą\",\n\t\"LINK_TEXT\": \"Nuorodos tekstas\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Įterpti\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Negalite siųsti žinutės šiai grupei, nes nebesate narys.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/ms/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Terima\",\n\t\"ACTIONS\": \"Tindakan\",\n\t\"ACTIVITY\": \"Aktiviti\",\n\t\"ADD\": \"Tambah\",\n\t\"ADDED\": \"campurkan\",\n\t\"ADDING\": \"Menambah...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Tambah Jawapan Lain\",\n\t\"ADD_CONTACTS\": \"Tambah kenalan untuk memulakan perbualan dan lihat senarai di sini.\",\n\t\"ADD_MEMBERS\": \"Tambah Ahli\",\n\t\"ADD_NEW_OPTION\": \"Tambah pilihan baru\",\n\t\"ADD_OPTION\": \"Tambah Pilihan\",\n\t\"ADD_OPTIONS\": \"Tambah pilihan\",\n\t\"ADD_REACTION\": \"Tambah tindak balas\",\n\t\"ADD_TO_CHAT\": \"Tambah ke Sembang\",\n\t\"ADMIN\": \"Pentadbir\",\n\t\"ADMINISTRATOR\": \"Pentadbir\",\n\t\"AI\": \"KECERDASAN BUATAN\",\n\t\"ALL\": \"Semua\",\n\t\"AND\": \"suatu\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Semua\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Maklumkan semua orang dalam kumpulan ini\",\n\t\"ANIMALES_NATURE\": \"Haiwan & Alam\",\n\t\"ANSWER\": \"Jawapan\",\n\t\"APP_CREDENTIALS\": \"Kredensial Aplikasi\",\n\t\"ASK_QUESTION\": \"Tanya soalan\",\n\t\"AT\": \"di\",\n\t\"ATTACH\": \"Lampirkan\",\n\t\"ATTACH_AUDIO\": \"Lampirkan audio\",\n\t\"ATTACH_DOCUMENT\": \"Lampirkan dokumen\",\n\t\"ATTACH_FILE\": \"Lampirkan fail\",\n\t\"ATTACH_IMAGE\": \"Lampirkan imej\",\n\t\"ATTACH_VIDEO\": \"Lampirkan video\",\n\t\"AT_A_TIME\": \"pada satu masa\",\n\t\"AUDIO_CALL\": \"Panggilan audio\",\n\t\"AVATAR\": \"Avatar\",\n\t\"BADGE_COUNT\": \"Kiraan lencana\",\n\t\"BAN\": \"Larangan\",\n\t\"BANNED\": \"diharamkan\",\n\t\"BANNED_MEMBERS\": \"Ahli yang diharamkan\",\n\t\"BAN_MEMBER_CONFIRM\": \"Adakah anda pasti mahu melarang \",\n\t\"BLOCK\": \"Sekat\",\n\t\"BLOCKED_USERS\": \"Pengguna yang disekat\",\n\t\"BLOCKED_USER_DESC\": \"Tidak dapat menghantar mesej kerana pengguna telah disekat\",\n\t\"BLOCK_CONTACT\": \"Sekat kenalan ini?\",\n\t\"BLOCK_SURE\": \"Adakah anda pasti mahu nyahblokir kenalan ini? Anda akan mula menerima mesej daripada mereka semula.\",\n\t\"BLOCK_USER\": \"Sekat Pengguna\",\n\t\"CALLING\": \"Memanggil...\",\n\t\"CALLS\": \"Panggilan\",\n\t\"CALL_ACCEPTED\": \"Panggilan diterima\",\n\t\"CALL_ANSWERED\": \"Panggilan Dijawab\",\n\t\"CALL_BUSY\": \"Panggil Sibuk\",\n\t\"CALL_CANCELLED\": \"Panggilan Dibatalkan\",\n\t\"CALL_DETAIL\": \"Butiran Panggilan\",\n\t\"CALL_DETAILS\": \"Butiran Panggilan\",\n\t\"CALL_ENDED\": \"Panggilan Berakhir\",\n\t\"CALL_HISTORY\": \"Sejarah Panggilan\",\n\t\"CALL_INITIATED\": \"Panggilan dimulakan\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Buat atau terima panggilan untuk melihat sejarah panggilan anda disenaraikan di sini\",\n\t\"CALL_REJECTED\": \"Panggilan Ditolak\",\n\t\"CALL_UNANSWERED\": \"Panggilan Tidak Dijawab\",\n\t\"CAMERA\": \"kamera\",\n\t\"CAMERA_PERMISSION\": \"Kami tidak mempunyai akses kepada kamera anda. Untuk mendayakan akses, ketik Tetapan dan hidupkan Kamera.\",\n\t\"CANCEL\": \"Batalkan\",\n\t\"CANCELLED_AUDIO_CALL\": \"Panggilan audio yang dibatalkan\",\n\t\"CANCELLED_CALL\": \"Panggilan dibatalkan\",\n\t\"CANCELLED_VIDEO_CALL\": \"Panggilan video dibatalkan\",\n\t\"CANT__LOAD__CHATS\": \"Tidak boleh memuatkan sembang\",\n\t\"CARD\": \"Kad\",\n\t\"CHANGE_ROLE\": \"Tukar Peranan\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Anda boleh menukar peranan untuk menguruskan keizinan dan tanggungjawab kumpulan.\",\n\t\"CHANGE_SCOPE\": \"Tukar Skop\",\n\t\"CHANGE_TO\": \"Tukar kepada\",\n\t\"CHATS\": \"Sembang\",\n\t\"CHAT_PRIVATELY\": \"Berbual Persendirian\",\n\t\"CLICK_TO_REMOVE\": \"Klik untuk membuang\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Klik untuk memulakan perbualan\",\n\t\"CLOSE\": \"Tutup\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Bekerjasama menggunakan dokumen\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Bekerjasama menggunakan papan putih\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Dokumen Kolaboratif\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Papan Putih Kolaboratif\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Tanya AI Bots\",\n\t\"COMETCHAT_ASK_BOT\": \"Tanya\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI Bot\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Bagaimana saya boleh membantu anda dalam perbualan ini? Sila tanya saya soalan dan saya akan memberi nasihat kepada anda 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Masukkan mesej anda di sini\",\n\t\"CONTINUE\": \"Teruskan\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Teruskan dengan Google\",\n\t\"CONVERSATIONS\": \"Perbualan\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Mulakan sembang baru atau jemput orang lain untuk menyertai perbualan.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Perbualan Dengan Mesej\",\n\t\"CONVERSATION_DELETED\": \"Perbualan Dihapuskan\",\n\t\"CONVERSATION_LIST\": \"Senarai Perbualan\",\n\t\"CONVERSATION_LIST_ITEM\": \"Item Senarai Perbualan\",\n\t\"CONVERSATION_STARTER\": \"Permulaan Perbualan\",\n\t\"CONVERSATION_SUMMARY\": \"Ringkasan perbualan\",\n\t\"COPY\": \"Salin\",\n\t\"CREATE\": \"Buat\",\n\t\"CREATED_DOCUMENT\": \"Anda telah mencipta dokumen kolaboratif baru\",\n\t\"CREATED_WHITEBOARD\": \"Anda telah mencipta papan putih kolaboratif baru\",\n\t\"CREATE_GROUP\": \"Buat Kumpulan\",\n\t\"CREATE_POLL\": \"Tinjauan\",\n\t\"CREATING\": \"Mewujudkan\",\n\t\"CUSTOM_MESSAGE\": \"Anda mempunyai mesej\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Dokumen\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Lokasi\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Undian\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Pelekat\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Papan putih\",\n\t\"DATA_ITEM\": \"Item Data\",\n\t\"DECLINE\": \"Menolak\",\n\t\"DELETE\": \"Padam\",\n\t\"DELETE_AND_EXIT\": \"Padam dan Keluar\",\n\t\"DELETE_AND_EXIT_SURE\": \"Adakah anda pasti mahu memadam sembang ini dan keluar daripada kumpulan? Tindakan ini tidak boleh dipulihkan.\",\n\t\"DELETE_CHAT\": \"Padamkan sembang ini?\",\n\t\"DELETE_CHAT_TEXT\": \"Padam Sembang\",\n\t\"DELETE_CONFIRM\": \"Adakah anda pasti mahu memadam?\",\n\t\"DELETE_CONVERSATION\": \"Padam Perbualan?\",\n\t\"DELETE_MESSAGE\": \"Padamkan mesej\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Adakah anda pasti mahu memadam mesej ini? Tindakan ini tidak boleh dibuat asal.\",\n\t\"DELETE_MSG_TEXT\": \"Mesej ini telah dipadamkan\",\n\t\"DELETE_THIS_CONVERSATION\": \"Padam perbualan ini?\",\n\t\"DELETE_THIS_MESSAGE\": \"Padam mesej ini?\",\n\t\"DELIVERED\": \"Dihantar\",\n\t\"DETAILS\": \"Butiran\",\n\t\"DOCS\": \"Dokumen\",\n\t\"DOCUMENT\": \"Dokumen\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Buka dokumen untuk mengedit kandungan bersama-sama\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Buka papan putih untuk menggambar bersama\",\n\t\"EDIT\": \"Sunting\",\n\t\"EDITED\": \"Disunting\",\n\t\"EDIT_MESSAGE\": \"Edit mesej\",\n\t\"EMOJI\": \"Emotikon\",\n\t\"ENTER_GROUP_NAME\": \"Masukkan nama kumpulan\",\n\t\"ENTER_GROUP_PASSWORD\": \"Masukkan kata laluan kumpulan\",\n\t\"ENTER_PASSWORD\": \"Masukkan Kata Laluan\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Masukkan mesej anda di sini\",\n\t\"ENTER_YOUR_OPTION\": \"Masukkan pilihan anda\",\n\t\"ENTER_YOUR_PASSWORD\": \"Masukkan kata laluan anda\",\n\t\"ENTER_YOUR_QUESTION\": \"Masukkan soalan anda\",\n\t\"ERROR\": \"Ralat\",\n\t\"ERROR_TEXT\": \"Nampaknya ada yang salah. Sila cuba lagi.\",\n\t\"FLAGS\": \"Bendera\",\n\t\"FOOD_DRINK\": \"Makanan & Minuman\",\n\t\"FORM\": \"Borang\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Terima kasih kerana mengisi borang.\",\n\t\"FRIDAY\": \"Jumaat\",\n\t\"FULL_SCREEN_VIEWER\": \"Pemapar Skrin Penuh\",\n\t\"GENERATE_SUMMARY\": \"Menjana ringkasan\",\n\t\"GENERATING_ICEBREAKERS\": \"Menjana pemecah ais\",\n\t\"GENERATING_REPLIES\": \"Menjana balasan\",\n\t\"GENERATING_SUMMARY\": \"Menjana ringkasan\",\n\t\"GROUPS\": \"Kumpulan\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Buat atau sertai kumpulan untuk melihatnya disenaraikan di sini dan mula bekerjasama\",\n\t\"GROUPS_WITH_MESSAGES\": \"Kumpulan Dengan Mesej\",\n\t\"GROUP_INFO\": \"Maklumat Kumpulan\",\n\t\"GROUP_LIST\": \"Senarai Kumpulan\",\n\t\"GROUP_MEMBERS\": \"Ahli Kumpulan\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Tambah kenalan untuk melihatnya disenaraikan di sini.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Menunggu penerima menerima dan melihat mesej\",\n\t\"GROUP_NAME_BLANK\": \"Nama kumpulan tidak boleh kosong\",\n\t\"GROUP_PASSWORD_BLANK\": \"Kata laluan kumpulan tidak boleh kosong\",\n\t\"GROUP_TYPE_BLANK\": \"Jenis kumpulan tidak boleh kosong\",\n\t\"HELP\": \"Bantuan\",\n\t\"HISTORY\": \"Sejarah\",\n\t\"IGNORE\": \"Abaikan\",\n\t\"INCOMING_AUDIO_CALL\": \"Panggilan audio masuk\",\n\t\"INCOMING_CALL\": \"Panggilan Masuk\",\n\t\"INCOMING_VIDEO_CALL\": \"Panggilan video masuk\",\n\t\"INFO\": \"Maklumat\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"telah memulakan panggilan audio\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"telah memulakan panggilan video\",\n\t\"INVALID_GROUP_NAME\": \"Sila masukkan nama yang sah untuk kumpulan dan cuba lagi\",\n\t\"INVALID_GROUP_TYPE\": \"Sila masukkan jenis yang sah untuk kumpulan dan cuba lagi\",\n\t\"INVALID_PASSWORD\": \"Sila masukkan kata laluan yang sah untuk kumpulan dan cuba lagi\",\n\t\"INVALID_POLL_OPTION\": \"Sila masukkan jawapan yang diperlukan sebelum membuat pengundian\",\n\t\"INVALID_POLL_QUESTION\": \"Sila masukkan soalan yang diperlukan sebelum membuat pengundian\",\n\t\"IN_A_THREAD\": \"Dalam benang\",\n\t\"IS_TYPING\": \"sedang menaip...\",\n\t\"JOIN\": \"Sertai\",\n\t\"JOINED\": \"menyertai\",\n\t\"JOIN_GROUP\": \"Sertai Kumpulan\",\n\t\"JUMP\": \"Lompat\",\n\t\"KICK\": \"Tendang\",\n\t\"KICKED\": \"ditendang\",\n\t\"LAST_ACTIVE_AT\": \"Terakhir Aktif Di\",\n\t\"LAST_SEEN\": \"Terakhir dilihat\",\n\t\"LAUNCH\": \"Pelancaran\",\n\t\"LEAVE\": \"Tinggalkan\",\n\t\"LEAVE_CONFIRM\": \"Adakah anda pasti mahu meninggalkan kumpulan itu?\",\n\t\"LEAVE_GROUP\": \"Tinggalkan Kumpulan\",\n\t\"LEAVE_GROUP_TEXT\": \"Keluar dari kumpulan ini\",\n\t\"LEAVE_SURE\": \"Adakah anda pasti mahu meninggalkan kumpulan? Anda tidak akan menerima sebarang mesej lagi daripada sembang ini.\",\n\t\"LEFT\": \"ditinggalkan\",\n\t\"LEFT_THE_CALL\": \"meninggalkan panggilan\",\n\t\"LINK\": \"Pautan\",\n\t\"LIVE_REACTION\": \"Reaksi Langsung\",\n\t\"LOADING\": \"Memuatkan...\",\n\t\"LOCALIZE\": \"Melokalisasikan\",\n\t\"LOGIN\": \"Log Masuk\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Nampaknya ada yang tidak kena\",\n\t\"MADE\": \"diperbuat\",\n\t\"MEDIA_MESSAGE\": \"Mesej media\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Tempah Slot Baru\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Tiada slot masa tersedia untuk tarikh ini. Sila cuba tarikh lain.\",\n\t\"MEETING_SCHEDULED\": \"Mesyuarat anda telah dijadualkan.\",\n\t\"MEETING_SCHEDULER\": \"Penjadual Mesyuarat\",\n\t\"MEETING_SLOT_BOOK\": \"Slot masa tidak lagi tersedia. Sila pilih yang lain.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Sila cuba tarikh yang berbeza.\",\n\t\"MEET_WITH\": \"Bertemu Dengan\",\n\t\"MEMBER\": \"Angota\",\n\t\"MEMBERS\": \"Ahli\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Anda boleh menyebut sehingga 10 pengguna sekaligus.\",\n\t\"MENTION_UPTO\": \"Anda boleh menyebut sehingga\",\n\t\"MESSAGE\": \"Pesanan\",\n\t\"MESSAGES\": \"Mesej\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"MESSAGE_COMPOSER\": \"Komposer Mesej\",\n\t\"MESSAGE_COPIED\": \"Mesej disalin ke papan klip.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Mesej dipadamkan dengan jayanya.\",\n\t\"MESSAGE_EDITED\": \"Mesej dikemas kini dengan jayanya.\",\n\t\"MESSAGE_FILE\": \"Fail\",\n\t\"MESSAGE_HEADER\": \"Tajuk Mesej\",\n\t\"MESSAGE_IMAGE\": \"Imej\",\n\t\"MESSAGE_INFORMATION\": \"Maklumat Mesej\",\n\t\"MESSAGE_IS_DELETED\": \"Mesej telah dipadam\",\n\t\"MESSAGE_LIST\": \"Senarai Mesej\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} telah menambah ${onName} ke kumpulan\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} telah menyekat ${onName} dari kumpulan\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} telah menyertai kumpulan\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} telah mengeluarkan ${onName} dari kumpulan\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} telah meninggalkan kumpulan\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} telah menjadikan ${onName} sebagai ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} telah menyahsekat ${onName}\",\n\t\"MESSAGE_PRIVATELY\": \"Mesej Persendirian\",\n\t\"MESSAGE_RECEIPT\": \"Penerimaan Mesej\",\n\t\"MESSAGE_TRANSLATED\": \"Mesej diterjemahkan dengan jayanya.\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MICROPHONE_PERMISSION\": \"Untuk merakam Mesej Suara, kami memerlukan akses ke mikrofon anda. Ketik Tetapan dan hidupkan Mikrofon.\",\n\t\"MISSED_AUDIO_CALL\": \"Panggilan audio terlepas\",\n\t\"MISSED_CALL\": \"Panggilan Terlepas\",\n\t\"MISSED_VIDEO_CALL\": \"Panggilan video terlepas\",\n\t\"MISSED_VOICE_CALL\": \"Panggilan suara terlepas\",\n\t\"MODERATOR\": \"penyampai\",\n\t\"MONDAY\": \"Isnin\",\n\t\"MORE\": \"Lagi\",\n\t\"MORE_TIMES\": \"Lebih banyak kali\",\n\t\"NAME\": \"Nama\",\n\t\"NEW_CHAT\": \"Sembang Baru\",\n\t\"NEW_MESSAGE\": \"mesej baru\",\n\t\"NEW_MESSAGES\": \"mesej baru\",\n\t\"NEW__GROUP\": \"Kumpulan Baru\",\n\t\"NO\": \"Tidak\",\n\t\"NOTIFICATIONS\": \"Pemberitahuan\",\n\t\"NOT_SUPPORTED\": \"Jenis mesej ini tidak disokong\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Tiada ahli yang dilarang ditemui\",\n\t\"NO_CALLS_FOUND\": \"Tiada Panggilan Ditemui\",\n\t\"NO_CALLS_SELECTED\": \"Tiada Panggilan Dipilih\",\n\t\"NO_CALL_LOGS\": \"Tiada Log Panggilan Log Lagi\",\n\t\"NO_CHATS_FOUND\": \"Tiada sembang ditemui\",\n\t\"NO_CHATS_SELECTED\": \"Tiada Sembang Dipilih\",\n\t\"NO_CONVERSATIONS\": \"Tiada Perbualan Lagi”;  \",\n\t\"NO_DOCUMENTS\": \"Tiada Dokumen\",\n\t\"NO_GROUPS_AVAILABLE\": \"Tiada Kumpulan Tersedia\",\n\t\"NO_GROUPS_FOUND\": \"Tiada kumpulan ditemui\",\n\t\"NO_GROUPS_SELECTED\": \"Tiada Kumpulan Terpilih\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Tiada Ahli Kumpulan Tersedia\",\n\t\"NO_MESSAGES_FOUND\": \"Tiada mesej ditemui\",\n\t\"NO_PHOTOS\": \"Tiada Foto\",\n\t\"NO_RECIPIENT\": \"Tiada Penerima\",\n\t\"NO_RECIPIENTS\": \"Tiada Penerima\",\n\t\"NO_RECORDS_FOUND\": \"Tiada rekod ditemui\",\n\t\"NO_REPLIES_FOUND\": \"Tiada Balasan Ditemui\",\n\t\"NO_STICKERS_AVAILABLE\": \"Tiada Pelekat Tersedia\",\n\t\"NO_STICKERS_FOUND\": \"Tiada pelekat ditemui\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Tiada Cadangan Tersedia\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Tiada Ringkasan Tersedia\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Tiada slot masa tersedia\",\n\t\"NO_USERS_AVAILABLE\": \"Tiada Pengguna Tersedia\",\n\t\"NO_USERS_FOUND\": \"Tiada Pengguna Ditemui\",\n\t\"NO_USERS_SELECTED\": \"Tiada Pengguna Yang Dipilih\",\n\t\"NO_VIDEOS\": \"Tiada Video\",\n\t\"NO_VOTE\": \"Tiada undi\",\n\t\"OBJECTS\": \"Objek\",\n\t\"OFFLINE\": \"Luar talian\",\n\t\"ONGOING_AUDIO_CALL\": \"Panggilan audio berterusan\",\n\t\"ONGOING_CALL\": \"Panggilan berterusan\",\n\t\"ONGOING_VIDEO_CALL\": \"Panggilan video berterusan\",\n\t\"ONLINE\": \"Dalam Talian\",\n\t\"ON_ANOTHER_CALL\": \"sedang dalam panggilan lain\",\n\t\"OOPS\": \"OOPS\",\n\t\"OOPS!\": \"OOPS!\",\n\t\"OPEN_DOCUMENT\": \"Buka Dokumen\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Buka dokumen untuk menggambar bersama\",\n\t\"OPEN_WHITEBOARD\": \"Buka Papan Putih\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Buka papan putih untuk menggambar bersama\",\n\t\"OPTIONS\": \"Pilihan\",\n\t\"OTHER\": \"Lain-lain\",\n\t\"OTHERS\": \"lain\",\n\t\"OUTGOING_AUDIO_CALL\": \"Panggilan audio keluar\",\n\t\"OUTGOING_CALL\": \"Panggilan Keluar\",\n\t\"OUTGOING_VIDEO_CALL\": \"Panggilan video keluar\",\n\t\"OWNER\": \"Pemilik\",\n\t\"PARTICIPANT\": \"Peserta\",\n\t\"PARTICIPANTS\": \"Peserta\",\n\t\"PASSWORD\": \"Kata Laluan\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Kata laluan untuk kumpulan salah!\",\n\t\"PASSWORD_PROTECTED\": \"Kata Laluan Dilindungi\",\n\t\"PHOTOS\": \"Gambar\",\n\t\"PICK_YOUR_EMOJI\": \"Pilih emoji anda\",\n\t\"POLLS\": \"Undian\",\n\t\"PREFERENCES\": \"Keutamaan\",\n\t\"PRIVACY\": \"Privasi\",\n\t\"PRIVACY_AND_SECURITY\": \"Privasi dan Keselamatan\",\n\t\"PRIVATE\": \"Persendirian\",\n\t\"PRIVATE_GROUP\": \"Kumpulan Swasta\",\n\t\"PROTECTED_GROUP\": \"Kumpulan yang dilindungi\",\n\t\"PUBLIC\": \"Awam\",\n\t\"QUESTION\": \"Soalan\",\n\t\"QUESTIONS\": \"Soalan\",\n\t\"REACHED_MAX_LIMIT\": \"Anda telah mencapai had. Anda boleh menambah sehingga 12 pilihan.\",\n\t\"REACT\": \"Bertindak balas\",\n\t\"REACTED\": \"bertindak balas\",\n\t\"REACT_TO_MESSAGE\": \"Bertindak balas terhadap mesej\",\n\t\"READ\": \"Baca\",\n\t\"READ_MORE\": \"Baca lebih lanjut\",\n\t\"RECEIPT_INFORMATION\": \"Maklumat Resit\",\n\t\"RECORDING\": \"Rakaman\",\n\t\"REGION\": \"Wilayah\",\n\t\"REJECTED_AUDIO_CALL\": \"Panggilan audio ditolak\",\n\t\"REJECTED_CALL\": \"panggilan ditolak\",\n\t\"REJECTED_VIDEO_CALL\": \"Panggilan video ditolak\",\n\t\"REMOVE\": \"Keluarkan\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Adakah anda pasti mahu mengalih keluar\",\n\t\"REPLIES\": \"balasannya\",\n\t\"REPLY\": \"jawapan\",\n\t\"REPLY_IN_THREAD\": \"Balas dalam thread\",\n\t\"REPLY_TO_THREAD\": \"Balas kepada thread\",\n\t\"REPORT_PROBLEM\": \"Laporkan Masalah\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Sila isikan semua bidang yang diperlukan sebelum membuat pengundian\",\n\t\"RESIZE\": \"Ubah saiz\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Ciri ini memerlukan kebenaran sumber tertentu. Sila aktifkan keizinan yang diperlukan dalam tetapan peranti anda\",\n\t\"RETRY\": \"Cuba lagi\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Bahasa terpilih untuk terjemahan adalah serupa dengan bahasa mesej asal\",\n\t\"SATURDAY\": \"Sabtu\",\n\t\"SAVE\": \"Simpan\",\n\t\"SCHEDULE\": \"Jadual\",\n\t\"SCOPE\": \"Skop\",\n\t\"SCOPE_CHANGE_INFO\": \"Anda boleh menukar peranan untuk menguruskan kebenaran dan tanggungjawab kumpulan\",\n\t\"SEARCH\": \"Cari\",\n\t\"SEARCH_EMOJI\": \"Cari emoji\",\n\t\"SEEN\": \"Dilihat\",\n\t\"SELECT_A_DATE\": \"Pilih tarikh\",\n\t\"SELECT_DAY\": \"Pilih Hari\",\n\t\"SELECT_GROUP_TYPE\": \"Pilih jenis kumpulan\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Pilih sumber audio input\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Pilih sumber audio output\",\n\t\"SELECT_TIME\": \"Pilih Masa\",\n\t\"SELECT_VIDEO_SOURCE\": \"Pilih sumber video\",\n\t\"SELECT__GROUP\": \"Pilih kumpulan untuk memulakan pemesejan\",\n\t\"SELECT__USER\": \"Pilih pengguna untuk memulakan pemesejan\",\n\t\"SEND\": \"Hantar\",\n\t\"SEND_MESSAGE\": \"Hantar Mesej\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Hantar mesej secara peribadi\",\n\t\"SENT\": \"Menghantar\",\n\t\"SETTINGS\": \"Tetapan\",\n\t\"SET_THE_ANSWERS\": \"TETAPKAN JAWAPAN\",\n\t\"SHARE\": \"Kongsi\",\n\t\"SHARED\": \"Dikongsi\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"telah berkongsi dokumen kolaboratif\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"telah berkongsi papan putih kolaboratif\",\n\t\"SHARED_FILE\": \"Fail Dikongsi\",\n\t\"SHARED_LOCATION\": \"Lokasi Dikongsi\",\n\t\"SHARED_MEDIA\": \"Media Dikongsi\",\n\t\"SHOW_LESS\": \"Tunjukkan kurang\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Adakah anda pasti mahu melihat kandungan yang tidak selamat?\",\n\t\"SMART_REPLIES\": \"Balasan Pintar\",\n\t\"SMILEY_PEOPLE\": \"Senyuman & Orang\",\n\t\"SOMETHING_WENT_WRONG\": \"Nampaknya sesuatu telah berlaku kesilapan.\",\n\t\"SOMETHING_WRONG\": \"Ada yang salah. Sila cuba lagi.\",\n\t\"SOUND_MANAGER\": \"Pengurus bunyi\",\n\t\"STATUS_INDICATOR\": \"Penunjuk Status\",\n\t\"STICKER\": \"Pelekat\",\n\t\"SUGGEST_A_REPLY\": \"Cadangkan balasan\",\n\t\"SUNDAY\": \"Ahad\",\n\t\"SURE_TO_DELETE_CHAT\": \"Adakah anda pasti mahu memadam sembang ini? Tindakan ini tidak boleh dibatalkan.\",\n\t\"SYMBOLS\": \"Simbol\",\n\t\"TAP_TO_REMOVE\": \"Ketik untuk membuang\",\n\t\"TAP_TO_START_CONVERSATION\": \"Ketik untuk memulakan perbualan\",\n\t\"TEXT\": \"Teks\",\n\t\"TEXT_TRANSLATE\": \"Terjemah Teks\",\n\t\"TEXT_TRANSLATED\": \"Teks diterjemahkan\",\n\t\"THEME\": \"Tema\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Mesej ini telah dipadamkan\",\n\t\"THREAD\": \"Benang\",\n\t\"THURSDAY\": \"Khamis\",\n\t\"TIME\": \"masa\",\n\t\"TIMES\": \"kali\",\n\t\"TIME_ZONE\": \"Zon Waktu\",\n\t\"TODAY\": \"Hari ini\",\n\t\"TRANSFER\": \"Pemindahan\",\n\t\"TRANSFERRING\": \"Memindahkan\",\n\t\"TRANSFER_CONFIRM\": \"Anda adalah pemilik kumpulan; sila pindahkan pemilikan kepada ahli sebelum meninggalkan kumpulan\",\n\t\"TRANSFER_OWNERSHIP\": \"Pemindahan Pemilikan\",\n\t\"TRANSFER_SURE\": \"Adakah anda pasti mahu memindahkan pemilikan? Tindakan ini tidak boleh diubah, dan pemilik baru akan mengambil alih kawalan sepenuhnya.\",\n\t\"TRANSLATE\": \"Terjemah\",\n\t\"TRANSLATED_MESSAGE\": \"Mesej diterjemahkan\",\n\t\"TRANSLATE_MESSAGE\": \"Terjemah mesej\",\n\t\"TRAVEL_PLACES\": \"Perjalanan & Tempat\",\n\t\"TRY_AGAIN\": \"Cuba lagi\",\n\t\"TUESDAY\": \"Selasa\",\n\t\"TYPE\": \"Taip\",\n\t\"TYPING\": \"Menaip...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Panggilan audio yang tidak dijawab\",\n\t\"UNANSWERED_CALL\": \"Panggilan tidak dijawab\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Panggilan video yang tidak dijawab\",\n\t\"UNBAN\": \"Unban\",\n\t\"UNBANNED\": \"tidak diharamkan\",\n\t\"UNBAN_SURE\": \"Adakah anda pasti mahu membatalkan sekatan pengguna ini?\",\n\t\"UNBLOCK\": \"Nyahblokir\",\n\t\"UNBLOCK_CONTACT\": \"Nyahblokir kenalan ini\",\n\t\"UNBLOCK_SURE\": \"Are you sure you want to unblock this contact? You will start receiving messages from them again.\",\n\t\"UNBLOCK_USER\": \"Nyahsekat Pengguna\",\n\t\"USERS\": \"Pengguna\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Kami tidak menemui pengguna yang sepadan dengan carian anda. Cuba laraskan carian anda.\",\n\t\"USERS_WITH_MESSAGES\": \"Pengguna Dengan Mesej\",\n\t\"USER_INFO\": \"Maklumat Pengguna\",\n\t\"USER_LIST\": \"Senarai Pengguna\",\n\t\"VIDEOS\": \"Video\",\n\t\"VIDEO_CALL\": \"Panggilan video\",\n\t\"VIEW\": \"Lihat\",\n\t\"VIEW_DETAIL\": \"Lihat Butiran\",\n\t\"VIEW_MEMBERS\": \"Lihat Ahli\",\n\t\"VIEW_ON_YOUTUBE\": \"Lihat di Youtube\",\n\t\"VIEW_PROFILE\": \"Lihat Profil\",\n\t\"VISIT\": \"Lawatan\",\n\t\"VOICE\": \"Suara\",\n\t\"VOICE_CALL\": \"Panggilan suara\",\n\t\"VOICE_RECORDING\": \"Rakaman Suara\",\n\t\"VOTE\": \"undi\",\n\t\"VOTES\": \"undi\",\n\t\"WEDNESDAY\": \"Rabu\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Adakah anda ingin memadamkan perbualan ini? Perbualan ini akan dipadamkan dari semua peranti anda.\",\n\t\"WRONG_FILE_TYPE\": \"Anda memilih jenis fail yang berbeza. Sila pilih fail yang sesuai.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Jenis fail ini tidak dibenarkan.\",\n\t\"WRONG_PASSWORD\": \"Sila masukkan kata laluan yang betul dan cuba lagi\",\n\t\"WRONG_TEXT\": \"Nampaknya ada sesuatu yang tidak kena.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Sila cuba lagi.\",\n\t\"YES\": \"Ya\",\n\t\"YESTERDAY\": \"Semalam\",\n\t\"YOU\": \"Anda\",\n\t\"YOU'VE_BLOCKED\": \"Anda telah menyekat\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Anda sudah dalam panggilan berterusan\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Anda memadamkan mesej ini\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Anda masih belum mempunyai sebarang pelekat.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Anda telah memulakan panggilan audio\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Anda telah memulakan panggilan video\",\n\t\"meeting\": \"Mesyuarat\",\n\t\"Flag_Message_Title\": \"Laporkan Mesej\",\n\t\"Flag_Message_Subtitle\": \"Laporkan perbualan ini jika ia melanggar Standard Komuniti kami. Kami tidak akan memberitahu akaun yang anda laporkan.\",\n\t\"Flag_Message_Remark_Label\": \"Sebab\",\n\t\"Flag_Message_Remark_Optional\": \"Pilihan\",\n\t\"Flag_Message_Remark_Placeholder\": \"Berikan konteks tambahan untuk laporan anda…\",\n\t\"Flag_Message_Confirm_Yes\": \"Laporkan\",\n\t\"Flag_Message_Confirm_No\": \"Batal\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / Kandungan Tidak Diingini\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Kandungan eksplisit atau tidak sesuai\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Tingkah laku menghina atau mengancam\",\n\t\"Message_List_Option_Flag_Message\": \"Laporkan\",\n\t\"Flag_Error_Text\":\"Ada sesuatu yang tidak kena. Sila periksa dan cuba lagi.\",\n\t\"Flag_Empty_Submit_Text\":\"Tidak dapat menghantar. Sila pilih sebab sebelum melaporkan mesej ini.\",\n\t\"START_REPORTING\": \"Mulakan Laporan\",\n\t\"LOGOUT\": \"Log keluar\",\n\t\"AI_ASSISTANTS\": \"Pembantu AI\",\n\t\"CREATE_CONVERSATION\": \"Buat Perbualan\",\n\t\"EDIT_MODERATION\": \"Penyuntingan gagal. Mesej anda telah disekat kerana dasar moderasi.\",\n\t\"BLOCKED_MODERATION\": \"Mesej anda telah disekat kerana dasar moderasi.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Tiada sejarah perbualan ditemui\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Sesuatu yang salah berlaku di pihak kami. Sila cuba lagi.\",\n\t\"START_A_CHAT\": \"Mulakan perbualan dengan mengetik butang 'Sembang Baru'\",\n\t\"CHAT_HISTORY\": \"Sejarah Sembang\",\n\t\"ASK_ANYTHING\": \"Tanyakan apa-apa...\",\n\t\"AI_ASSISTANT\": \"Pembantu AI\",\n\t\"SEARCH_PLACEHOLDER\": \"Cari...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Tiada Hasil\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Mula menaip untuk mencari mesej dan perbualan\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Tiada hasil ditemui\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Kami tidak menemui apa-apa padanan. Sila cuba kata kunci lain.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Ralat Memuatkan Perbualan\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Ralat Memuatkan Mesej\",\n\t\"SEARCH_TRY_AGAIN\": \"Sila cuba lagi kemudian\",\n\t\"SEARCH_SEE_MORE\": \"Lihat Lagi\",\n\t\"SEARCH_LOADING\": \"Memuatkan...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Belum dibaca\",\n\t\"SEARCH_FILTER_GROUPS\": \"Kumpulan\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Foto\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Video\",\n\t\"SEARCH_FILTER_LINKS\": \"Pautan\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Dokumen\",\n\t\"SEARCH_FILTER_AUDIO\": \"Audio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Perbualan\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Mesej\",\n\t\"SEARCH_NO_MESSAGES\": \"Tiada mesej\",\n\t\"MESSAGE_REPORTED\": \"Mesej dilaporkan\",\n\t\"MARK_AS_UNREAD\": \"Tandai belum dibaca\",\n\t\"NEW\": \"Baru\",\n\t\"INSERT_LINK\": \"Masukkan pautan\",\n\t\"EDIT_LINK\": \"Edit pautan\",\n\t\"LINK_TEXT\": \"Teks pautan\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Masukkan\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Anda tidak boleh menghantar mesej ke kumpulan ini kerana anda bukan lagi ahli.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/nl/translation.json",
    "content": "{\n\t\"INFO\": \"Info\",\n\t\"REACT\": \"Reageren\",\n\t\"EDIT\": \"Bewerken\",\n\t\"MESSAGE_PRIVATELY\": \"Privébericht sturen\",\n\t\"TRANSLATE\": \"Vertalen\",\n\t\"USERS\": \"Gebruikers\",\n\t\"CHATS\": \"Chats\",\n\t\"SEARCH_EMOJI\": \"Emoji zoeken\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Wil je dit gesprek verwijderen? Dit gesprek wordt verwijderd van al je apparaten.\",\n\t\"GROUPS\": \"Groepen\",\n\t\"SHARED\": \"Gedeeld\",\n\t\"SOUND_MANAGER\": \"Geluidsbeheer\",\n\t\"THEME\": \"Thema\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Allemaal\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Iedereen in deze groep informeren\",\n\t\"DELETE_MSG_TEXT\": \"Dit bericht is verwijderd\",\n\t\"LOCALIZE\": \"Lokaliseren\",\n\t\"CONVERSATION_LIST_ITEM\": \"Gesprek lijst item\",\n\t\"DATA_ITEM\": \"Data item\",\n\t\"STATUS_INDICATOR\": \"Statusindicator\",\n\t\"BADGE_COUNT\": \"Badge aantal\",\n\t\"MESSAGE_RECEIPT\": \"Bericht ontvangstbewijs\",\n\t\"MESSAGE\": \"Bericht\",\n\t\"RECEIPT_INFORMATION\": \"Ontvangstinformatie\",\n\t\"NO_RECIPIENT\": \"Geen ontvanger\",\n\t\"NO_RECIPIENTS\": \"Geen ontvangers\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Gesprekken met berichten\",\n\t\"CONVERSATIONS\": \"Gesprekken\",\n\t\"CONVERSATION_LIST\": \"Gesprekkenlijst\",\n\t\"MESSAGES\": \"Berichten\",\n\t\"WRONG_FILE_TYPE\": \"Je hebt een ander bestandstype geselecteerd. Kies het juiste bestand.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Dit bestandstype is niet toegestaan.\",\n\t\"MESSAGE_HEADER\": \"Berichtkop\",\n\t\"MESSAGE_LIST\": \"Berichtenlijst\",\n\t\"MESSAGE_COMPOSER\": \"Berichtschrijver\",\n\t\"USERS_WITH_MESSAGES\": \"Gebruikers met berichten\",\n\t\"USER_LIST\": \"Gebruikerslijst\",\n\t\"GROUP_LIST\": \"Groepenlijst\",\n\t\"GROUPS_WITH_MESSAGES\": \"Groepen met berichten\",\n\t\"NEW__GROUP\": \"Nieuwe groep\",\n\t\"PASSWORD\": \"Wachtwoord\",\n\t\"CONTINUE\": \"Doorgaan\",\n\t\"NO_CHATS_SELECTED\": \"Geen chats geselecteerd\",\n\t\"NO_USERS_SELECTED\": \"Geen gebruikers geselecteerd\",\n\t\"NO_GROUPS_SELECTED\": \"Geen groepen geselecteerd\",\n\t\"SELECT_DAY\": \"Selecteer een dag\",\n\t\"SELECT_TIME\": \"Selecteer een tijd\",\n\t\"NO_CALLS_SELECTED\": \"Geen gesprekken geselecteerd\",\n\t\"SELECT__GROUP\": \"Selecteer een groep om te beginnen met berichten\",\n\t\"SELECT__USER\": \"Selecteer een gebruiker om te beginnen met berichten\",\n\t\"GROUP_PASSWORD_BLANK\": \"Groepswachtwoord mag niet leeg zijn\",\n\t\"GROUP_NAME_BLANK\": \"Groepsnaam mag niet leeg zijn\",\n\t\"GROUP_TYPE_BLANK\": \"Groepstype mag niet leeg zijn\",\n\t\"DELETE_CONVERSATION\": \"Gesprek verwijderen?\",\n\t\"ADD_TO_CHAT\": \"Toevoegen aan chat\",\n\t\"MORE\": \"Meer\",\n\t\"COPY\": \"Kopiëren\",\n\t\"VOICE_RECORDING\": \"Spraakopname\",\n\t\"MESSAGE_IMAGE\": \"Afbeelding\",\n\t\"MESSAGE_FILE\": \"Bestand\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MESSAGE_AUDIO\": \"Audio\",\n\t\"CUSTOM_MESSAGE\": \"Je hebt een bericht\",\n\t\"SELECT_A_DATE\": \"Selecteer een datum\",\n\t\"TIME_ZONE\": \"Tijdzone\",\n\t\"MEETING_SCHEDULED\": \"Je vergadering is gepland.\",\n\t\"SOMETHING_WRONG\": \"Er is iets misgegaan. Probeer het opnieuw.\",\n\t\"MEETING_SLOT_BOOK\": \"Tijdslot niet meer beschikbaar. Kies een ander.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Nieuw tijdslot boeken\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Geen tijdslot beschikbaar voor deze datum. Probeer een andere datum.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Probeer een andere datum.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Geen tijdsloten beschikbaar\",\n\t\"SCHEDULE\": \"Planning\",\n\t\"MORE_TIMES\": \"Meer tijden\",\n\t\"MISSED_VOICE_CALL\": \"Gemiste spraakoproep\",\n\t\"MEET_WITH\": \"Ontmoeten met\",\n\t\"CONVERSATION_DELETED\": \"Gesprek verwijderd\",\n\t\"MEETING_SCHEDULER\": \"Vergaderingsplanner\",\n\t\"MISSED_VIDEO_CALL\": \"Gemiste video-oproep\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Peiling\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Sticker\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Document\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Whiteboard\",\n\t\"ONLINE\": \"Online\",\n\t\"ADMINISTRATOR\": \"Beheerder\",\n\t\"ADMIN\": \"Admin\",\n\t\"MODERATOR\": \"Moderator\",\n\t\"PARTICIPANT\": \"Deelnemer\",\n\t\"PUBLIC\": \"Openbaar\",\n\t\"PRIVATE\": \"Privé\",\n\t\"PASSWORD_PROTECTED\": \"Wachtwoord beveiligd\",\n\t\"PRIVACY_AND_SECURITY\": \"Privacy en beveiliging\",\n\t\"PREFERENCES\": \"Voorkeuren\",\n\t\"MEMBERS\": \"Leden\",\n\t\"MEMBER\": \"Lid\",\n\t\"EDITED\": \"Bewerkt\",\n\t\"TODAY\": \"Vandaag\",\n\t\"YESTERDAY\": \"Gisteren\",\n\t\"SUNDAY\": \"Zondag\",\n\t\"MONDAY\": \"Maandag\",\n\t\"TUESDAY\": \"Dinsdag\",\n\t\"WEDNESDAY\": \"Woensdag\",\n\t\"THURSDAY\": \"Donderdag\",\n\t\"FRIDAY\": \"Vrijdag\",\n\t\"SATURDAY\": \"Zaterdag\",\n\t\"TYPING\": \"Typen...\",\n\t\"IS_TYPING\": \"is aan het typen...\",\n\t\"CLOSE\": \"Sluiten\",\n\t\"ENTER_GROUP_NAME\": \"Voer groepsnaam in\",\n\t\"ADD_MEMBERS\": \"Leden toevoegen\",\n\t\"SEND_MESSAGE\": \"Bericht versturen\",\n\t\"UNBLOCK_USER\": \"Gebruiker deblokkeren\",\n\t\"BLOCK_USER\": \"Gebruiker blokkeren\",\n\t\"DELETE_AND_EXIT\": \"Verwijderen en verlaten\",\n\t\"LEAVE_GROUP\": \"Groep verlaten\",\n\t\"CREATE_GROUP\": \"Groep aanmaken\",\n\t\"SHARED_MEDIA\": \"Gedeelde media\",\n\t\"SHARED_FILE\": \"Gedeeld bestand\",\n\t\"VIDEO_CALL\": \"Video-oproep\",\n\t\"AUDIO_CALL\": \"Audio-oproep\",\n\t\"LOADING\": \"Laden...\",\n\t\"REPLY\": \"Beantwoorden\",\n\t\"REPLIES\": \"Antwoorden\",\n\t\"AI\": \"AI\",\n\t\"SMART_REPLIES\": \"Slimme antwoorden\",\n\t\"CONVERSATION_STARTER\": \"Gespreksopener\",\n\t\"LAUNCH\": \"Starten\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"heeft een samenwerkingsdocument gedeeld\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"heeft een samenwerkingswhiteboard gedeeld\",\n\t\"CREATED_WHITEBOARD\": \"Je hebt een nieuw samenwerkingswhiteboard gemaakt\",\n\t\"CREATED_DOCUMENT\": \"Je hebt een nieuw samenwerkingsdocument gemaakt\",\n\t\"PHOTOS\": \"Foto's\",\n\t\"VIDEOS\": \"Video's\",\n\t\"DOCUMENT\": \"Document\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Je hebt dit bericht verwijderd\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Dit bericht is verwijderd\",\n\t\"MESSAGE_IS_DELETED\": \"Bericht is verwijderd\",\n\t\"MESSAGE_COPIED\": \"Bericht gekopieerd naar klembord.\",\n\t\"MESSAGE_EDITED\": \"Bericht succesvol bijgewerkt.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Bericht succesvol verwijderd.\",\n\t\"MESSAGE_TRANSLATED\": \"Bericht succesvol vertaald.\",\n\t\"VIEW_ON_YOUTUBE\": \"Bekijken op YouTube\",\n\t\"SEARCH\": \"Zoeken\",\n\t\"ERROR\": \"Fout\",\n\t\"ERROR_TEXT\": \"Er lijkt iets mis te zijn gegaan. Probeer het opnieuw.\",\n\t\"NO_GROUPS_FOUND\": \"Geen groepen gevonden\",\n\t\"NO_CHATS_FOUND\": \"Geen chats gevonden\",\n\t\"CANT__LOAD__CHATS\": \"Kan chats niet laden\",\n\t\"MEDIA_MESSAGE\": \"Media bericht\",\n\t\"INCOMING_AUDIO_CALL\": \"Inkomende audio-oproep\",\n\t\"INCOMING_VIDEO_CALL\": \"Inkomende video-oproep\",\n\t\"DECLINE\": \"Weigeren\",\n\t\"ACCEPT\": \"Accepteren\",\n\t\"INCOMING_CALL\": \"Inkomende oproep\",\n\t\"OUTGOING_CALL\": \"Uitgaande oproep\",\n\t\"CALL_REJECTED\": \"Oproep geweigerd\",\n\t\"CALL_ANSWERED\": \"Oproep beantwoord\",\n\t\"CALL_CANCELLED\": \"Oproep geannuleerd\",\n\t\"MISSED_CALL\": \"Gemiste oproep\",\n\t\"CALL_UNANSWERED\": \"Onbeantwoorde oproep\",\n\t\"CALL_INITIATED\": \"Oproep gestart\",\n\t\"OUTGOING_AUDIO_CALL\": \"Uitgaande audio-oproep\",\n\t\"OUTGOING_VIDEO_CALL\": \"Uitgaande video-oproep\",\n\t\"REJECTED_CALL\": \"geweigerde oproep\",\n\t\"CALL_ACCEPTED\": \"Oproep geaccepteerd\",\n\t\"JOINED\": \"toegetreden\",\n\t\"LEFT_THE_CALL\": \"heeft de oproep verlaten\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Onbeantwoorde audio-oproep\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Onbeantwoorde video-oproep\",\n\t\"CALL_ENDED\": \"Oproep beëindigd\",\n\t\"CALL_BUSY\": \"Oproep bezet\",\n\t\"CALLING\": \"Bellen...\",\n\t\"ADD\": \"Toevoegen\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Geen verbannen leden gevonden\",\n\t\"BANNED_MEMBERS\": \"Verbannen leden\",\n\t\"NAME\": \"Naam\",\n\t\"SCOPE\": \"Bereik\",\n\t\"UNBAN\": \"Verbanning opheffen\",\n\t\"SELECT_GROUP_TYPE\": \"Selecteer groepstype\",\n\t\"ENTER_GROUP_PASSWORD\": \"Voer groepswachtwoord in\",\n\t\"CREATE\": \"Aanmaken\",\n\t\"CREATE_POLL\": \"Peiling\",\n\t\"QUESTION\": \"Vraag\",\n\t\"ENTER_YOUR_QUESTION\": \"Voer je vraag in\",\n\t\"OPTIONS\": \"Opties\",\n\t\"ENTER_YOUR_OPTION\": \"Voer je optie in\",\n\t\"ADD_NEW_OPTION\": \"Nieuwe optie toevoegen\",\n\t\"VIEW_MEMBERS\": \"Leden bekijken\",\n\t\"DETAILS\": \"Details\",\n\t\"NOTIFICATIONS\": \"Meldingen\",\n\t\"OTHER\": \"Overig\",\n\t\"HELP\": \"Help\",\n\t\"REPORT_PROBLEM\": \"Een probleem melden\",\n\t\"GROUP_MEMBERS\": \"Groepsleden\",\n\t\"BAN\": \"Verbannen\",\n\t\"KICK\": \"Verwijderen\",\n\t\"PICK_YOUR_EMOJI\": \"Kies je emoji\",\n\t\"PRIVATE_GROUP\": \"Privégroep\",\n\t\"PROTECTED_GROUP\": \"Beveiligde groep\",\n\t\"VISIT\": \"Bezoeken\",\n\t\"ATTACH\": \"Bijvoegen\",\n\t\"OPEN_WHITEBOARD\": \"Whiteboard openen\",\n\t\"ATTACH_FILE\": \"Bestand bijvoegen\",\n\t\"GENERATING_REPLIES\": \"Antwoorden genereren\",\n\t\"ATTACH_VIDEO\": \"Video bijvoegen\",\n\t\"ATTACH_AUDIO\": \"Audio bijvoegen\",\n\t\"ATTACH_IMAGE\": \"Afbeelding bijvoegen\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Samenwerken met een document\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Samenwerken met een whiteboard\",\n\t\"SUGGEST_A_REPLY\": \"Een antwoord suggereren\",\n\t\"GENERATING_ICEBREAKERS\": \"IJsbrekers genereren\",\n\t\"EMOJI\": \"Emoji\",\n\t\"NO_REPLIES_FOUND\": \"Geen antwoorden gevonden\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Voer hier je bericht in\",\n\t\"NO_MESSAGES_FOUND\": \"Geen berichten gevonden\",\n\t\"THREAD\": \"Discussie\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Samenwerkingsdocument\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Samenwerkingswhiteboard\",\n\t\"ADD_REACTION\": \"Reactie toevoegen\",\n\t\"NO_STICKERS_FOUND\": \"Geen stickers gevonden\",\n\t\"REPLY_TO_THREAD\": \"Reageren op discussie\",\n\t\"REPLY_IN_THREAD\": \"Reageren in discussie\",\n\t\"VIEW\": \"Bekijken\",\n\t\"DELETE_MESSAGE\": \"Bericht verwijderen\",\n\t\"EDIT_MESSAGE\": \"Bericht bewerken\",\n\t\"OWNER\": \"Eigenaar\",\n\t\"CHANGE_SCOPE\": \"Bereik wijzigen\",\n\t\"STICKER\": \"Sticker\",\n\t\"LAST_ACTIVE_AT\": \"Laatst actief op\",\n\t\"LAST_SEEN\": \"Laatst gezien\",\n\t\"AT\": \"om\",\n\t\"VOICE_CALL\": \"Spraakoproep\",\n\t\"VIEW_DETAIL\": \"Details bekijken\",\n\t\"VOTES\": \"stemmen\",\n\t\"VOTE\": \"stem\",\n\t\"NO_VOTE\": \"Geen stem\",\n\t\"REACTED\": \"heeft gereageerd\",\n\t\"ADDED\": \"toegevoegd\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Weet je zeker dat je onveilige inhoud wilt zien?\",\n\t\"REACT_TO_MESSAGE\": \"Reageren op een bericht\",\n\t\"UNBANNED\": \"verbanning opgeheven\",\n\t\"MADE\": \"gemaakt\",\n\t\"MISSED_AUDIO_CALL\": \"Gemiste audio-oproep\",\n\t\"ENTER_YOUR_PASSWORD\": \"Voer je wachtwoord in\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Open document om samen inhoud te bewerken\",\n\t\"DOCS\": \"Documenten\",\n\t\"NO_RECORDS_FOUND\": \"Geen records gevonden\",\n\t\"LIVE_REACTION\": \"Live reactie\",\n\t\"SMILEY_PEOPLE\": \"Smileys & Mensen\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Open whiteboard om samen te tekenen\",\n\t\"ANIMALES_NATURE\": \"Dieren & Natuur\",\n\t\"FOOD_DRINK\": \"Eten & Drinken\",\n\t\"OPEN_DOCUMENT\": \"Document openen\",\n\t\"ACTIVITY\": \"Activiteit\",\n\t\"TRAVEL_PLACES\": \"Reizen & Plaatsen\",\n\t\"OBJECTS\": \"Objecten\",\n\t\"SYMBOLS\": \"Symbolen\",\n\t\"FLAGS\": \"Vlaggen\",\n\t\"SENT\": \"Verzonden\",\n\t\"SEEN\": \"Gezien\",\n\t\"DELIVERED\": \"Afgeleverd\",\n\t\"READ\": \"Gelezen\",\n\t\"MESSAGE_INFORMATION\": \"Berichtinformatie\",\n\t\"TRANSLATE_MESSAGE\": \"Bericht vertalen\",\n\t\"TRANSLATED_MESSAGE\": \"Vertaald bericht\",\n\t\"LEFT\": \"verlaten\",\n\t\"KICKED\": \"verwijderd\",\n\t\"BANNED\": \"verbannen\",\n\t\"NEW_MESSAGES\": \"nieuwe berichten\",\n\t\"NEW_MESSAGE\": \"nieuw bericht\",\n\t\"JUMP\": \"Spring\",\n\t\"SELECT_VIDEO_SOURCE\": \"Selecteer videobron\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Selecteer audio-invoerbron\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Selecteer audio-uitvoerbron\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"heeft een audio-oproep gestart\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"heeft een video-oproep gestart\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Je hebt een audio-oproep gestart\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Je hebt een video-oproep gestart\",\n\t\"IGNORE\": \"Negeren\",\n\t\"ON_ANOTHER_CALL\": \"is in een ander gesprek\",\n\t\"CREATING\": \"Aanmaken\",\n\t\"AVATAR\": \"Avatar\",\n\t\"ONGOING_CALL\": \"Lopende oproep\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Je bent al in een lopende oproep\",\n\t\"RESIZE\": \"Formaat wijzigen\",\n\t\"READ_MORE\": \"Meer lezen\",\n\t\"SHOW_LESS\": \"Minder tonen\",\n\t\"SETTINGS\": \"Instellingen\",\n\t\"ACTIONS\": \"Acties\",\n\t\"VIEW_PROFILE\": \"Profiel bekijken\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Privébericht sturen\",\n\t\"DELETE\": \"Verwijderen\",\n\t\"DELETE_CONFIRM\": \"Weet je zeker dat je wilt verwijderen?\",\n\t\"DELETE_CHAT\": \"Deze chat verwijderen?\",\n\t\"SURE_TO_DELETE_CHAT\": \"Weet je zeker dat je deze chat wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.\",\n\t\"CANCEL\": \"Annuleren\",\n\t\"LEAVE_CONFIRM\": \"Weet je zeker dat je de groep wilt verlaten?\",\n\t\"TRANSFER_CONFIRM\": \"Je bent de groepseigenaar, draag het eigenaarschap over aan een lid voordat je de groep verlaat\",\n\t\"ADDING\": \"Toevoegen...\",\n\t\"TRANSFER\": \"Overdragen\",\n\t\"TRANSFER_OWNERSHIP\": \"Eigenaarschap overdragen\",\n\t\"TRANSFERRING\": \"Overdragen\",\n\t\"YES\": \"Ja\",\n\t\"NO\": \"Nee\",\n\t\"ANSWER\": \"Antwoord\",\n\t\"ADD_ANOTHER_ANSWER\": \"Nog een antwoord toevoegen\",\n\t\"SET_THE_ANSWERS\": \"DE ANTWOORDEN INSTELLEN\",\n\t\"TRY_AGAIN\": \"Probeer opnieuw\",\n\t\"INVALID_GROUP_NAME\": \"Voer een geldige naam in voor de groep en probeer het opnieuw\",\n\t\"INVALID_PASSWORD\": \"Voer een geldig wachtwoord in voor de groep en probeer het opnieuw\",\n\t\"INVALID_GROUP_TYPE\": \"Voer een geldig type in voor de groep en probeer het opnieuw\",\n\t\"WRONG_PASSWORD\": \"Voer het juiste wachtwoord in en probeer het opnieuw\",\n\t\"INVALID_POLL_QUESTION\": \"Voer de vereiste vraag in voordat je een peiling maakt\",\n\t\"INVALID_POLL_OPTION\": \"Voer het vereiste antwoord in voordat je een peiling maakt\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Geselecteerde taal voor vertaling is gelijk aan de taal van het originele bericht\",\n\t\"LEAVE\": \"Verlaten\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Klik om een gesprek te starten\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍Locatie\",\n\t\"SHARED_LOCATION\": \"Gedeelde locatie\",\n\t\"IN_A_THREAD\": \"In een discussie\",\n\t\"CALLS\": \"Oproepen\",\n\t\"CALL_DETAILS\": \"Oproepdetails\",\n\t\"OFFLINE\": \"Offline\",\n\t\"POLLS\": \"Peilingen\",\n\t\"YOU\": \"Jij\",\n\t\"PRIVACY\": \"Privacy\",\n\t\"BLOCKED_USERS\": \"Geblokkeerde gebruikers\",\n\t\"YOU'VE_BLOCKED\": \"Je hebt geblokkeerd\",\n\t\"NO_PHOTOS\": \"Geen foto's\",\n\t\"NO_VIDEOS\": \"Geen video's\",\n\t\"NO_DOCUMENTS\": \"Geen documenten\",\n\t\"JOIN\": \"Deelnemen\",\n\t\"REMOVE\": \"Verwijderen\",\n\t\"BLOCK\": \"Blokkeren\",\n\t\"CHANGE_ROLE\": \"Rol wijzigen\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Je kunt rollen wijzigen om groepsrechten en -verantwoordelijkheden te beheren.\",\n\t\"CHANGE_TO\": \"Wijzigen naar\",\n\t\"NEW_CHAT\": \"Nieuwe chat\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Bedankt voor het invullen van het formulier.\",\n\t\"HISTORY\": \"Geschiedenis\",\n\t\"CANCELLED_AUDIO_CALL\": \"Geannuleerde audio-oproep\",\n\t\"CANCELLED_VIDEO_CALL\": \"Geannuleerde video-oproep\",\n\t\"REJECTED_AUDIO_CALL\": \"Geweigerde audio-oproep\",\n\t\"REJECTED_VIDEO_CALL\": \"Geweigerde video-oproep\",\n\t\"CANCELLED_CALL\": \"Geannuleerde oproep\",\n\t\"UNANSWERED_CALL\": \"Onbeantwoorde oproep\",\n\t\"RECORDING\": \"Opnemen\",\n\t\"PARTICIPANTS\": \"Deelnemers\",\n\t\"CALL_HISTORY\": \"Oproepgeschiedenis\",\n\t\"NO_CALLS_FOUND\": \"Geen oproepen gevonden\",\n\t\"ONGOING_AUDIO_CALL\": \"Lopende audio-oproep\",\n\t\"ONGOING_VIDEO_CALL\": \"Lopende video-oproep\",\n\t\"CALL_DETAIL\": \"Oproepdetail\",\n\t\"FORM\": \"Formulier\",\n\t\"CARD\": \"Kaart\",\n\t\"GENERATING_SUMMARY\": \"Samenvatting genereren\",\n\t\"CONVERSATION_SUMMARY\": \"Gesprekssamenvatting\",\n\t\"GENERATE_SUMMARY\": \"Een samenvatting genereren\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Hoe kan ik je helpen met dit gesprek? Stel me een vraag en ik zal je adviseren 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"Vraag\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"AI-bots vragen\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI-bot\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Je kunt maximaal 10 gebruikers tegelijk vermelden.\",\n\t\"ALL\": \"Alle\",\n\t\"CLICK_TO_REMOVE\": \"Klik om te verwijderen\",\n\t\"OTHERS\": \"anderen\",\n\t\"AND\": \"en\",\n\t\"ASK_QUESTION\": \"Vraag stellen\",\n\t\"ADD_OPTION\": \"Optie toevoegen\",\n\t\"SEND\": \"Versturen\",\n\t\"FULL_SCREEN_VIEWER\": \"Volledig scherm weergave\",\n\t\"TEXT_TRANSLATE\": \"Tekst vertalen\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Het lijkt erop dat er iets mis is gegaan\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Geen samenvatting beschikbaar\",\n\t\"QUESTIONS\": \"Vragen\",\n\t\"NO_STICKERS_AVAILABLE\": \"Geen stickers beschikbaar\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Geen suggesties beschikbaar\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Maak groepen aan of word lid om ze hier weergegeven te zien en samen te werken\",\n\t\"NO_USERS_AVAILABLE\": \"Geen gebruikers beschikbaar\",\n\t\"NO_GROUPS_AVAILABLE\": \"Geen groepen beschikbaar\",\n\t\"NO_CALL_LOGS\": \"Nog geen oproepenlogboek\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Maak of ontvang oproepen om je oproepgeschiedenis hier te zien\",\n\t\"NO_CONVERSATIONS\": \"Nog geen gesprekken\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Start een nieuwe chat of nodig anderen uit om deel te nemen aan het gesprek.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Geen groepsleden beschikbaar\",\n\t\"OOPS!\": \"OEPS!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Wachten tot ontvangers het bericht ontvangen en bekijken\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Voeg contacten toe om ze hier weergegeven te zien.\",\n\t\"REACHED_MAX_LIMIT\": \"Je hebt de limiet bereikt. Je kunt maximaal 12 opties toevoegen.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Vul alle verplichte velden in voordat je een peiling maakt.\",\n\t\"NOT_SUPPORTED\": \"Dit berichttype wordt niet ondersteund\",\n\t\"NO_USERS_FOUND\": \"Geen gebruikers gevonden\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"We konden geen gebruikers vinden die aan je zoekopdracht voldoen. Probeer je zoekopdracht aan te passen.\",\n\t\"SAVE\": \"Opslaan\",\n\t\"RETRY\": \"Opnieuw proberen\",\n\t\"TAP_TO_START_CONVERSATION\": \"Tik om een gesprek te starten\",\n\t\"TEXT_TRANSLATED\": \"Tekst vertaald\",\n\t\"CAMERA\": \"Camera\",\n\t\"SHARE\": \"Delen\",\n\t\"ATTACH_DOCUMENT\": \"Document bijvoegen\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Weet je zeker dat je wilt verwijderen \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Typ je bericht...\",\n\t\"TAP_TO_REMOVE\": \"Tik om te verwijderen\",\n\t\"ADD_CONTACTS\": \"Voeg contacten toe om gesprekken te starten en ze hier weergegeven te zien.\",\n\t\"SCOPE_CHANGE_INFO\": \"Je kunt rollen wijzigen om groepsrechten en -verantwoordelijkheden te beheren\",\n\t\"SOMETHING_WENT_WRONG\": \"Het lijkt erop dat er iets mis is gegaan.\",\n\t\"MENTION_UPTO\": \"Je kunt tot\",\n\t\"TIME\": \"keer\",\n\t\"TIMES\": \"keer\",\n\t\"AT_A_TIME\": \"tegelijk\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Deze functie vereist specifieke rechten. Schakel de nodige rechten in je apparaatinstellingen in.\",\n\t\"MICROPHONE_PERMISSION\": \"Om een spraakbericht op te nemen, hebben we toegang tot je microfoon nodig. Tik op Instellingen en schakel Microfoon in.\",\n\t\"CAMERA_PERMISSION\": \"We hebben geen toegang tot je camera. Om toegang te verlenen, tik op Instellingen en schakel Camera in.\",\n\t\"OOPS\": \"Oeps!\",\n\t\"ADD_OPTIONS\": \"Opties toevoegen\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Open document om samen te tekenen\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Open whiteboard om samen te tekenen\",\n\t\"TEXT\": \"Tekst\",\n\t\"BAN_MEMBER_CONFIRM\": \"Weet je zeker dat je wilt verbannen \",\n\t\"LOGIN\": \"Inloggen\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Doorgaan met Google\",\n\t\"JOIN_GROUP\": \"Deelnemen aan groep\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Wachtwoord voor de groep is onjuist!\",\n\t\"ENTER_PASSWORD\": \"Voer wachtwoord in\",\n\t\"TYPE\": \"Type\",\n\t\"USER_INFO\": \"Gebruikersinformatie\",\n\t\"DELETE_CHAT_TEXT\": \"Chat verwijderen\",\n\t\"UNBLOCK\": \"Deblokkeren\",\n\t\"UNBLOCK_CONTACT\": \"Deze contactpersoon deblokkeren\",\n\t\"BLOCK_CONTACT\": \"Deze contactpersoon blokkeren?\",\n\t\"UNBLOCK_SURE\": \"Weet je zeker dat je deze contactpersoon wilt deblokkeren? Je zult weer berichten van hen ontvangen.\",\n\t\"BLOCK_SURE\": \"Weet je zeker dat je deze contactpersoon wilt blokkeren? Je ontvangt geen berichten meer van hen.\",\n\t\"VOICE\": \"Spraak\",\n\t\"DELETE_AND_EXIT_SURE\": \"Weet je zeker dat je deze chat wilt verwijderen en de groep wilt verlaten? Deze actie kan niet ongedaan worden gemaakt.\",\n\t\"TRANSFER_SURE\": \"Weet je zeker dat je het eigenaarschap wilt overdragen? Dit kan niet ongedaan worden gemaakt, en de nieuwe eigenaar krijgt volledige controle.\",\n\t\"GROUP_INFO\": \"Groepsinformatie\",\n\t\"LEAVE_GROUP_TEXT\": \"Deze groep verlaten\",\n\t\"LEAVE_SURE\": \"Weet je zeker dat je de groep wilt verlaten? Je ontvangt geen berichten meer van deze chat.\",\n\t\"UNBAN_SURE\": \"Weet je zeker dat je deze gebruiker wilt ontbannen?\",\n\t\"DELETE_THIS_CONVERSATION\": \"Dit gesprek verwijderen?\",\n\t\"LINK\": \"Link\",\n\t\"DELETE_THIS_MESSAGE\": \"Dit bericht verwijderen?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Weet je zeker dat je dit bericht wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.\",\n\t\"APP_CREDENTIALS\": \"App-gegevens\",\n\t\"REGION\": \"Regio\",\n\t\"CHAT_PRIVATELY\": \"Privé chatten\",\n\t\"WRONG_TEXT\": \"Het lijkt erop dat er iets mis is gegaan.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Probeer het opnieuw.\",\n\t\"BLOCKED_USER_DESC\": \"Kan geen bericht sturen omdat de gebruiker geblokkeerd is\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} heeft ${onName} aan de groep toegevoegd\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} is toegetreden tot de groep\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} heeft de groep verlaten\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} heeft ${onName} uit de groep verwijderd\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} heeft ${onName} verbannen uit de groep\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} heeft de verbanning van ${onName} opgeheven\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} heeft ${onName} ${role} gemaakt\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Je hebt nog geen stickers.\",\n\t\"meeting\": \"Vergadering\",\n\t\"Flag_Message_Title\": \"Een bericht rapporteren\",\n\t\"Flag_Message_Subtitle\": \"Rapporteer deze chat als deze in strijd is met onze Gemeenschapsnormen. We laten het account dat je hebt gerapporteerd dit niet weten.\",\n\t\"Flag_Message_Remark_Label\": \"Reden\",\n\t\"Flag_Message_Remark_Optional\": \"Optioneel\",\n\t\"Flag_Message_Remark_Placeholder\": \"Geef extra context voor je rapport…\",\n\t\"Flag_Message_Confirm_Yes\": \"Rapporteren\",\n\t\"Flag_Message_Confirm_No\": \"Annuleren\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / ongewenste inhoud\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Expliciete of ongepaste inhoud\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Beledigend of bedreigend gedrag\",\n\t\"Message_List_Option_Flag_Message\": \"Rapporteren\",\n    \"Flag_Error_Text\":\"Er is iets misgegaan. Controleer het en probeer het opnieuw.\",\n\t\"Flag_Empty_Submit_Text\":\"Kan niet indienen. Selecteer een reden voordat je dit bericht rapporteert.\",\n\t\"START_REPORTING\": \"Rapportage starten\",\n\t\"LOGOUT\": \"Uitloggen\",\n\t\"AI_ASSISTANTS\": \"AI-assistenten\",\n\t\"CREATE_CONVERSATION\": \"Gesprek maken\",\n\t\"EDIT_MODERATION\": \"Bewerken mislukt. Je bericht werd geblokkeerd vanwege het moderatiebeleid.\",\n\t\"BLOCKED_MODERATION\": \"Je bericht werd geblokkeerd vanwege het moderatiebeleid.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Geen gesprekshistorie gevonden\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Er ging iets mis aan onze kant. Probeer het opnieuw.\",\n\t\"START_A_CHAT\": \"Start een chat door op de knop 'Nieuwe chat' te tikken\",\n\t\"CHAT_HISTORY\": \"Chatgeschiedenis\",\n\t\"ASK_ANYTHING\": \"Vraag iets...\",\n\t\"AI_ASSISTANT\": \"AI-assistent\",\n\t\"SEARCH_PLACEHOLDER\": \"Zoeken...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Geen Resultaten\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Begin met typen om berichten en gesprekken te zoeken\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Geen resultaten gevonden\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"We konden geen overeenkomsten vinden. Probeer een andere zoekterm.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Fout bij laden van gesprekken\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Fout bij laden van berichten\",\n\t\"SEARCH_TRY_AGAIN\": \"Probeer het later opnieuw\",\n\t\"SEARCH_SEE_MORE\": \"Meer bekijken\",\n\t\"SEARCH_LOADING\": \"Laden...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Ongelezen\",\n\t\"SEARCH_FILTER_GROUPS\": \"Groepen\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Foto’s\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Video’s\",\n\t\"SEARCH_FILTER_LINKS\": \"Links\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Documenten\",\n\t\"SEARCH_FILTER_AUDIO\": \"Audio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Gesprekken\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Berichten\",\n\t\"SEARCH_NO_MESSAGES\": \"Geen berichten\",\n\t\"MESSAGE_REPORTED\": \"Bericht gemeld\",\n\t\"MARK_AS_UNREAD\": \"Ongelezen markeren\",\n\t\"NEW\": \"Nieuw\",\n\t\"INSERT_LINK\": \"Link invoegen\",\n\t\"EDIT_LINK\": \"Link bewerken\",\n\t\"LINK_TEXT\": \"Linktekst\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Invoegen\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"U kunt geen bericht naar deze groep sturen omdat u geen lid meer bent.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/pt/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Aceitar\",\n\t\"ACTIONS\": \"Ações\",\n\t\"ACTIVITY\": \"Atividade\",\n\t\"ADD\": \"Adicionar\",\n\t\"ADDED\": \"adicionada\",\n\t\"ADDING\": \"Adicionando...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Adicionar outra resposta\",\n\t\"ADD_CONTACTS\": \"Adicione contatos para iniciar conversas e vê-los listados aqui.\",\n\t\"ADD_MEMBERS\": \"Adicionar membros\",\n\t\"ADD_NEW_OPTION\": \"Adicionar nova opção\",\n\t\"ADD_OPTION\": \"Adicionar opção\",\n\t\"ADD_OPTIONS\": \"Adicionar opções\",\n\t\"ADD_REACTION\": \"Adicionar reação\",\n\t\"ADD_TO_CHAT\": \"Adicionar ao chat\",\n\t\"ADMIN\": \"Administrador\",\n\t\"ADMINISTRATOR\": \"Administrador\",\n\t\"AI\": \"AI\",\n\t\"ALL\": \"Todos\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Todos\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notificar todos neste grupo\",\n\t\"AND\": \"uma\",\n\t\"ANIMALES_NATURE\": \"Animais e natureza\",\n\t\"ANSWER\": \"Resposta\",\n\t\"APP_CREDENTIALS\": \"Credenciais do aplicativo\",\n\t\"ASK_QUESTION\": \"Faça uma pergunta\",\n\t\"AT\": \"em\",\n\t\"ATTACH\": \"Anexar\",\n\t\"ATTACH_AUDIO\": \"Anexar áudio\",\n\t\"ATTACH_DOCUMENT\": \"Anexar documento\",\n\t\"ATTACH_FILE\": \"Anexar arquivo\",\n\t\"ATTACH_IMAGE\": \"Anexar imagem\",\n\t\"ATTACH_VIDEO\": \"Anexar vídeo\",\n\t\"AT_A_TIME\": \"por vez\",\n\t\"AUDIO_CALL\": \"Chamada de áudio\",\n\t\"AVATAR\": \"Avatar\",\n\t\"BADGE_COUNT\": \"Contagem de emblemas\",\n\t\"BAN\": \"Banir\",\n\t\"BANNED\": \"banido\",\n\t\"BANNED_MEMBERS\": \"Membros banidos\",\n\t\"BAN_MEMBER_CONFIRM\": \"Tem certeza de que deseja banir \",\n\t\"BLOCK\": \"Bloquear\",\n\t\"BLOCKED_USERS\": \"Usuários bloqueados\",\n\t\"BLOCKED_USER_DESC\": \"Não é possível enviar uma mensagem, pois o usuário está bloqueado\",\n\t\"BLOCK_CONTACT\": \"Bloquear este contato?\",\n\t\"BLOCK_SURE\": \"Tem certeza de que deseja bloquear este contato? Você não receberá mais mensagens dele.\",\n\t\"BLOCK_USER\": \"Bloquear usuário\",\n\t\"CALLING\": \"Chamando...\",\n\t\"CALLS\": \"Chamadas\",\n\t\"CALL_ACCEPTED\": \"Chamada aceita\",\n\t\"CALL_ANSWERED\": \"Chamada atendida\",\n\t\"CALL_BUSY\": \"Chamada ocupada\",\n\t\"CALL_CANCELLED\": \"Chamada cancelada\",\n\t\"CALL_DETAIL\": \"Detalhe da chamada\",\n\t\"CALL_DETAILS\": \"Detalhes da chamada\",\n\t\"CALL_ENDED\": \"Chamada encerrada\",\n\t\"CALL_HISTORY\": \"Histórico de chamadas\",\n\t\"CALL_INITIATED\": \"Chamada iniciada\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Faça ou receba chamadas para ver seu histórico de chamadas listado aqui\",\n\t\"CALL_REJECTED\": \"Chamada rejeitada\",\n\t\"CALL_UNANSWERED\": \"Chamada não atendida\",\n\t\"CAMERA\": \"câmera\",\n\t\"CAMERA_PERMISSION\": \"Não temos acesso à sua câmera. Para ativar o acesso, toque em Configurações e ative a Câmera.\",\n\t\"CANCEL\": \"Cancelar\",\n\t\"CANCELLED_AUDIO_CALL\": \"Chamada de áudio cancelada\",\n\t\"CANCELLED_CALL\": \"Chamada cancelada\",\n\t\"CANCELLED_VIDEO_CALL\": \"Chamada de vídeo cancelada\",\n\t\"CANT__LOAD__CHATS\": \"Não consigo carregar bate-papos\",\n\t\"CARD\": \"Cartão\",\n\t\"CHANGE_ROLE\": \"Alterar função\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Você pode alterar as funções para gerenciar as permissões e responsabilidades do grupo.\",\n\t\"CHANGE_SCOPE\": \"Escopo da alteração\",\n\t\"CHANGE_TO\": \"Mudar para\",\n\t\"CHATS\": \"Conversa\",\n\t\"CHAT_PRIVATELY\": \"Converse de forma privada\",\n\t\"CLICK_TO_REMOVE\": \"Clique para remover\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Clique para iniciar a conversa\",\n\t\"CLOSE\": \"Fechar\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Colabore usando um documento\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Colabore usando um quadro branco\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Documento colaborativo\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Quadro branco colaborativo\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Pergunte aos robôs de IA\",\n\t\"COMETCHAT_ASK_BOT\": \"Pergunte\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"Robô de IA\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Como posso te ajudar com essa conversa? Por favor, me faça uma pergunta e eu vou te aconselhar 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Digite sua mensagem aqui\",\n\t\"CONTINUE\": \"Continuar\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Continuar com o Google\",\n\t\"CONVERSATIONS\": \"Conversas\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Inicie um novo bate-papo ou convide outras pessoas para participarem da conversa.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Conversas com mensagens\",\n\t\"CONVERSATION_DELETED\": \"Conversa excluída\",\n\t\"CONVERSATION_LIST\": \"Lista de conversas\",\n\t\"CONVERSATION_LIST_ITEM\": \"Item da lista de conversas\",\n\t\"CONVERSATION_STARTER\": \"Iniciador de conversa\",\n\t\"CONVERSATION_SUMMARY\": \"Resumo da conversa\",\n\t\"COPY\": \"Copiar\",\n\t\"CREATE\": \"Criar\",\n\t\"CREATED_DOCUMENT\": \"Você criou um novo documento colaborativo\",\n\t\"CREATED_WHITEBOARD\": \"Você criou um novo quadro branco colaborativo\",\n\t\"CREATE_GROUP\": \"Criar grupo\",\n\t\"CREATE_POLL\": \"Pesquisa\",\n\t\"CREATING\": \"Criando\",\n\t\"CUSTOM_MESSAGE\": \"Você tem uma mensagem\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Documento\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Localização\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Enquete\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Adesivo\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Quadro branco\",\n\t\"DATA_ITEM\": \"Item de dados\",\n\t\"DECLINE\": \"Declínio\",\n\t\"DELETE\": \"Excluir\",\n\t\"DELETE_AND_EXIT\": \"Excluir e sair\",\n\t\"DELETE_AND_EXIT_SURE\": \"Tem certeza de que deseja excluir este bate-papo e sair do grupo? Esta ação não pode ser desfeita.\",\n\t\"DELETE_CHAT\": \"Excluir esse bate-papo?\",\n\t\"DELETE_CHAT_TEXT\": \"Excluir Bate-papo\",\n\t\"DELETE_CONFIRM\": \"Tem certeza de que deseja excluir?\",\n\t\"DELETE_CONVERSATION\": \"Excluir conversa?\",\n\t\"DELETE_MESSAGE\": \"Excluir mensagem\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Tem certeza de que deseja excluir esta mensagem? Esta ação não pode ser desfeita.\",\n\t\"DELETE_MSG_TEXT\": \"Esta mensagem foi excluída\",\n\t\"DELETE_THIS_CONVERSATION\": \"Excluir esta conversa?\",\n\t\"DELETE_THIS_MESSAGE\": \"Excluir esta mensagem?\",\n\t\"DELIVERED\": \"Entregue\",\n\t\"DETAILS\": \"Detalhes\",\n\t\"DOCS\": \"Documentos\",\n\t\"DOCUMENT\": \"Documento\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Abra o documento para editar o conteúdo em conjunto\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Abra o quadro branco para desenhar juntos\",\n\t\"EDIT\": \"Editar\",\n\t\"EDITED\": \"Editado\",\n\t\"EDIT_MESSAGE\": \"Editar mensagem\",\n\t\"EMOJI\": \"Emoji\",\n\t\"ENTER_GROUP_NAME\": \"Insira o nome do grupo\",\n\t\"ENTER_GROUP_PASSWORD\": \"Digite a senha do grupo\",\n\t\"ENTER_PASSWORD\": \"Digite a Senha\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Digite sua mensagem aqui\",\n\t\"ENTER_YOUR_OPTION\": \"Insira sua opção\",\n\t\"ENTER_YOUR_PASSWORD\": \"Digite sua senha\",\n\t\"ENTER_YOUR_QUESTION\": \"Insira sua pergunta\",\n\t\"ERROR\": \"Erro\",\n\t\"ERROR_TEXT\": \"Parece que algo deu errado. Por favor, tente novamente.\",\n\t\"FLAGS\": \"Bandeiras\",\n\t\"FOOD_DRINK\": \"Comida e bebida\",\n\t\"FORM\": \"Formulário\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Obrigado por preencher o formulário.\",\n\t\"FRIDAY\": \"Sexta-feira\",\n\t\"FULL_SCREEN_VIEWER\": \"Visualizador de tela cheia\",\n\t\"GENERATE_SUMMARY\": \"Gere um resumo\",\n\t\"GENERATING_ICEBREAKERS\": \"Gerando quebra-gelos\",\n\t\"GENERATING_REPLIES\": \"Gerando respostas\",\n\t\"GENERATING_SUMMARY\": \"Gerando resumo\",\n\t\"GROUPS\": \"Grupos\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Crie ou participe de grupos para vê-los listados aqui e comece a colaborar\",\n\t\"GROUPS_WITH_MESSAGES\": \"Grupos com mensagens\",\n\t\"GROUP_INFO\": \"Informações do Grupo\",\n\t\"GROUP_LIST\": \"Lista de grupos\",\n\t\"GROUP_MEMBERS\": \"Membros do grupo\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Adicione contatos para vê-los listados aqui.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Esperando que os destinatários recebam e visualizem a mensagem\",\n\t\"GROUP_NAME_BLANK\": \"O nome do grupo não pode ficar em branco\",\n\t\"GROUP_PASSWORD_BLANK\": \"A senha do grupo não pode ficar em branco\",\n\t\"GROUP_TYPE_BLANK\": \"O tipo de grupo não pode ficar em branco\",\n\t\"HELP\": \"Socorro\",\n\t\"HISTORY\": \"História\",\n\t\"IGNORE\": \"Ignora\",\n\t\"INCOMING_AUDIO_CALL\": \"Chamada de áudio recebida\",\n\t\"INCOMING_CALL\": \"Chamada recebida\",\n\t\"INCOMING_VIDEO_CALL\": \"Chamada de vídeo recebida\",\n\t\"INFO\": \"Informações\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"iniciou uma chamada de áudio\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"iniciou uma videochamada\",\n\t\"INVALID_GROUP_NAME\": \"Insira um nome válido para o grupo e tente novamente\",\n\t\"INVALID_GROUP_TYPE\": \"Insira um tipo válido para o grupo e tente novamente\",\n\t\"INVALID_PASSWORD\": \"Insira uma senha válida para o grupo e tente novamente\",\n\t\"INVALID_POLL_OPTION\": \"Insira a resposta necessária antes de criar uma enquete\",\n\t\"INVALID_POLL_QUESTION\": \"Insira a pergunta necessária antes de criar uma enquete\",\n\t\"IN_A_THREAD\": \"Em um tópico\",\n\t\"IS_TYPING\": \"está digitando...\",\n\t\"JOIN\": \"Unir\",\n\t\"JOINED\": \"ingressou\",\n\t\"JOIN_GROUP\": \"Entrar no Grupo\",\n\t\"JUMP\": \"Saltar\",\n\t\"KICK\": \"Chute\",\n\t\"KICKED\": \"chutado\",\n\t\"LAST_ACTIVE_AT\": \"Ativo pela última vez em\",\n\t\"LAST_SEEN\": \"Visto pela última vez\",\n\t\"LAUNCH\": \"Lançamento\",\n\t\"LEAVE\": \"Sair\",\n\t\"LEAVE_CONFIRM\": \"Tem certeza de que quer sair do grupo?\",\n\t\"LEAVE_GROUP\": \"Sair do grupo\",\n\t\"LEAVE_GROUP_TEXT\": \"Sair deste grupo\",\n\t\"LEAVE_SURE\": \"Tem certeza de que deseja sair do grupo? Você não receberá mais mensagens deste bate-papo.\",\n\t\"LEFT\": \"esquerdo\",\n\t\"LEFT_THE_CALL\": \"deixou a ligação\",\n\t\"LINK\": \"Ligação\",\n\t\"LIVE_REACTION\": \"Reação ao vivo\",\n\t\"LOADING\": \"Carregando...\",\n\t\"LOCALIZE\": \"Localizar\",\n\t\"LOGIN\": \"Entrar\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Parece que algo deu errado\",\n\t\"MADE\": \"fez\",\n\t\"MEDIA_MESSAGE\": \"Mensagem de mídia\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Reserve um novo slot\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Não há horário disponível para esta data. Por favor, tente outra data.\",\n\t\"MEETING_SCHEDULED\": \"Sua reunião foi agendada.\",\n\t\"MEETING_SCHEDULER\": \"Agendador de reuniões\",\n\t\"MEETING_SLOT_BOOK\": \"O intervalo de tempo não está mais disponível. Por favor, escolha outro.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Por favor, tente uma data diferente.\",\n\t\"MEET_WITH\": \"Encontre-se com\",\n\t\"MEMBER\": \"Membro\",\n\t\"MEMBERS\": \"Membros\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Você pode mencionar até 10 usuários ao mesmo tempo.\",\n\t\"MENTION_UPTO\": \"Você pode mencionar até\",\n\t\"MESSAGE\": \"Mensagem\",\n\t\"MESSAGES\": \"Mensagens\",\n\t\"MESSAGE_AUDIO\": \"Áudio\",\n\t\"MESSAGE_COMPOSER\": \"Compositor de mensagens\",\n\t\"MESSAGE_COPIED\": \"Mensagem copiada para a área de transferência.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Mensagem excluída com sucesso.\",\n\t\"MESSAGE_EDITED\": \"Mensagem atualizada com sucesso.\",\n\t\"MESSAGE_FILE\": \"Arquivo\",\n\t\"MESSAGE_HEADER\": \"cabeçalho da mensagem\",\n\t\"MESSAGE_IMAGE\": \"Imagem\",\n\t\"MESSAGE_INFORMATION\": \"Informações da mensagem\",\n\t\"MESSAGE_IS_DELETED\": \"A mensagem está excluída\",\n\t\"MESSAGE_LIST\": \"Lista de mensagens\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} adicionou ${onName} ao grupo\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} baniu ${onName} do grupo\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} entrou no grupo\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} removeu ${onName} do grupo\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} saiu do grupo\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} tornou ${onName} ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} removeu o banimento de ${onName}\",\n\t\"MESSAGE_PRIVATELY\": \"Mensagem privada\",\n\t\"MESSAGE_RECEIPT\": \"Recibo de mensagem\",\n\t\"MESSAGE_TRANSLATED\": \"Mensagem traduzida com sucesso.\",\n\t\"MESSAGE_VIDEO\": \"Vídeo\",\n\t\"MICROPHONE_PERMISSION\": \"Para gravar uma mensagem de voz, precisamos acessar seu microfone. Toque em Configurações e ative o microfone.\",\n\t\"MISSED_AUDIO_CALL\": \"Chamada de áudio perdida\",\n\t\"MISSED_CALL\": \"Chamada perdida\",\n\t\"MISSED_VIDEO_CALL\": \"Chamada de vídeo perdida\",\n\t\"MISSED_VOICE_CALL\": \"Chamada de voz perdida\",\n\t\"MODERATOR\": \"apresentadora\",\n\t\"MONDAY\": \"Segunda-feira\",\n\t\"MORE\": \"Mais\",\n\t\"MORE_TIMES\": \"Mais vezes\",\n\t\"NAME\": \"Nome\",\n\t\"NEW_CHAT\": \"Novo bate-papo\",\n\t\"NEW_MESSAGE\": \"nova mensagem\",\n\t\"NEW_MESSAGES\": \"novas mensagens\",\n\t\"NEW__GROUP\": \"Novo grupo\",\n\t\"NO\": \"Não\",\n\t\"NOTIFICATIONS\": \"Notificações\",\n\t\"NOT_SUPPORTED\": \"Esse tipo de mensagem não é suportado\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Nenhum membro banido foi encontrado\",\n\t\"NO_CALLS_FOUND\": \"Nenhuma chamada encontrada\",\n\t\"NO_CALLS_SELECTED\": \"Nenhuma chamada selecionada\",\n\t\"NO_CALL_LOGS\": \"Ainda não há registros de chamadas\",\n\t\"NO_CHATS_FOUND\": \"Nenhum bate-papo encontrado\",\n\t\"NO_CHATS_SELECTED\": \"Nenhum bate-papo selecionado\",\n\t\"NO_CONVERSATIONS\": \"Ainda não há conversas”;  \",\n\t\"NO_DOCUMENTS\": \"Sem documentos\",\n\t\"NO_GROUPS_AVAILABLE\": \"Não há grupos disponíveis\",\n\t\"NO_GROUPS_FOUND\": \"Nenhum grupo encontrado\",\n\t\"NO_GROUPS_SELECTED\": \"Nenhum grupo selecionado\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Nenhum membro do grupo disponível\",\n\t\"NO_MESSAGES_FOUND\": \"Nenhuma mensagem encontrada\",\n\t\"NO_PHOTOS\": \"Sem fotos\",\n\t\"NO_RECIPIENT\": \"Nenhum destinatário\",\n\t\"NO_RECIPIENTS\": \"Sem destinatários\",\n\t\"NO_RECORDS_FOUND\": \"Nenhum registro encontrado\",\n\t\"NO_REPLIES_FOUND\": \"Nenhuma resposta encontrada\",\n\t\"NO_STICKERS_AVAILABLE\": \"Não há adesivos disponíveis\",\n\t\"NO_STICKERS_FOUND\": \"Nenhum adesivo encontrado\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Nenhuma sugestão disponível\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Nenhum resumo disponível\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Não há horários disponíveis\",\n\t\"NO_USERS_AVAILABLE\": \"Nenhum usuário disponível\",\n\t\"NO_USERS_FOUND\": \"Nenhum usuário encontrado\",\n\t\"NO_USERS_SELECTED\": \"Nenhum usuário selecionado\",\n\t\"NO_VIDEOS\": \"Sem vídeos\",\n\t\"NO_VOTE\": \"Sem voto\",\n\t\"OBJECTS\": \"Objetos\",\n\t\"OFFLINE\": \"Off-line\",\n\t\"ONGOING_AUDIO_CALL\": \"Chamada de áudio em andamento\",\n\t\"ONGOING_CALL\": \"Chamada em andamento\",\n\t\"ONGOING_VIDEO_CALL\": \"Chamada de vídeo em andamento\",\n\t\"ONLINE\": \"On-line\",\n\t\"ON_ANOTHER_CALL\": \"está em outra ligação\",\n\t\"OOPS\": \"OPA\",\n\t\"OOPS!\": \"OPA!\",\n\t\"OPEN_DOCUMENT\": \"Abrir documento\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Abra o documento para desenhar em conjunto\",\n\t\"OPEN_WHITEBOARD\": \"Quadro branco aberto\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Abra o quadro branco para desenhar juntos\",\n\t\"OPTIONS\": \"Opções\",\n\t\"OTHER\": \"Outros\",\n\t\"OTHERS\": \"outras\",\n\t\"OUTGOING_AUDIO_CALL\": \"Chamada de áudio de saída\",\n\t\"OUTGOING_CALL\": \"Chamada de saída\",\n\t\"OUTGOING_VIDEO_CALL\": \"Chamada de vídeo de saída\",\n\t\"OWNER\": \"Proprietário\",\n\t\"PARTICIPANT\": \"Participante\",\n\t\"PARTICIPANTS\": \"Participantes\",\n\t\"PASSWORD\": \"Senha\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"A senha para o grupo está incorreta!\",\n\t\"PASSWORD_PROTECTED\": \"Protegido por senha\",\n\t\"PHOTOS\": \"Fotos\",\n\t\"PICK_YOUR_EMOJI\": \"Escolha seu emoji\",\n\t\"POLLS\": \"Enquetes\",\n\t\"PREFERENCES\": \"Preferências\",\n\t\"PRIVACY\": \"Privacidade\",\n\t\"PRIVACY_AND_SECURITY\": \"Privacidade e segurança\",\n\t\"PRIVATE\": \"Privado\",\n\t\"PRIVATE_GROUP\": \"Grupo privado\",\n\t\"PROTECTED_GROUP\": \"Grupo protegido\",\n\t\"PUBLIC\": \"Público\",\n\t\"QUESTION\": \"Pergunta\",\n\t\"QUESTIONS\": \"Perguntas\",\n\t\"REACHED_MAX_LIMIT\": \"Você atingiu o limite. Você pode adicionar até 12 opções.\",\n\t\"REACT\": \"Reaja\",\n\t\"REACTED\": \"reagiu\",\n\t\"REACT_TO_MESSAGE\": \"Reagir a uma mensagem\",\n\t\"READ\": \"Leia\",\n\t\"READ_MORE\": \"Leia mais\",\n\t\"RECEIPT_INFORMATION\": \"Informações sobre o recibo\",\n\t\"RECORDING\": \"Gravação\",\n\t\"REGION\": \"Região\",\n\t\"REJECTED_AUDIO_CALL\": \"Chamada de áudio rejeitada\",\n\t\"REJECTED_CALL\": \"chamada rejeitada\",\n\t\"REJECTED_VIDEO_CALL\": \"Chamada de vídeo rejeitada\",\n\t\"REMOVE\": \"Remover\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Tem certeza de que deseja remover\",\n\t\"REPLIES\": \"respostas\",\n\t\"REPLY\": \"resposta\",\n\t\"REPLY_IN_THREAD\": \"Responder no tópico\",\n\t\"REPLY_TO_THREAD\": \"Responder ao tópico\",\n\t\"REPORT_PROBLEM\": \"Relatar um problema\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Preencha todos os campos obrigatórios antes de criar uma enquete\",\n\t\"RESIZE\": \"Redimensionar\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Esse recurso requer permissões específicas de recursos. Ative as permissões necessárias nas configurações do seu dispositivo\",\n\t\"RETRY\": \"Tentar novamente\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"O idioma selecionado para tradução é semelhante ao idioma da mensagem original\",\n\t\"SATURDAY\": \"sábado\",\n\t\"SAVE\": \"Salvar\",\n\t\"SCHEDULE\": \"Cronograma\",\n\t\"SCOPE\": \"Escopo\",\n\t\"SCOPE_CHANGE_INFO\": \"Você pode alterar os papéis para gerenciar permissões e responsabilidades do grupo\",\n\t\"SEARCH\": \"Pesquisar\",\n\t\"SEARCH_EMOJI\": \"Pesquisar emoji\",\n\t\"SEEN\": \"Visto\",\n\t\"SELECT_A_DATE\": \"Selecione uma data\",\n\t\"SELECT_DAY\": \"Selecione um dia\",\n\t\"SELECT_GROUP_TYPE\": \"Selecione o tipo de grupo\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Selecione a fonte de áudio de entrada\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Selecione a fonte de áudio de saída\",\n\t\"SELECT_TIME\": \"Selecione um horário\",\n\t\"SELECT_VIDEO_SOURCE\": \"Selecione a fonte de vídeo\",\n\t\"SELECT__GROUP\": \"Selecione um grupo para começar a enviar mensagens\",\n\t\"SELECT__USER\": \"Selecione um usuário para começar a enviar mensagens\",\n\t\"SEND\": \"Enviar\",\n\t\"SEND_MESSAGE\": \"Enviar mensagem\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Enviar mensagem de forma privada\",\n\t\"SENT\": \"Enviado\",\n\t\"SETTINGS\": \"Configurações\",\n\t\"SET_THE_ANSWERS\": \"DEFINA AS RESPOSTAS\",\n\t\"SHARE\": \"Compartilhar\",\n\t\"SHARED\": \"Compartilhado\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"compartilhou um documento colaborativo\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"compartilhou um quadro branco colaborativo\",\n\t\"SHARED_FILE\": \"Arquivo compartilhado\",\n\t\"SHARED_LOCATION\": \"Localização compartilhada\",\n\t\"SHARED_MEDIA\": \"Mídia compartilhada\",\n\t\"SHOW_LESS\": \"Mostrar menos\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Tem certeza de que deseja ver conteúdo não seguro?\",\n\t\"SMART_REPLIES\": \"Respostas inteligentes\",\n\t\"SMILEY_PEOPLE\": \"Smileys e pessoas\",\n\t\"SOMETHING_WENT_WRONG\": \"Parece que algo deu errado.\",\n\t\"SOMETHING_WRONG\": \"Algo deu errado. Por favor, tente novamente.\",\n\t\"SOUND_MANAGER\": \"Gerenciador de som\",\n\t\"STATUS_INDICATOR\": \"Indicador de status\",\n\t\"STICKER\": \"Adesivo\",\n\t\"SUGGEST_A_REPLY\": \"Sugira uma resposta\",\n\t\"SUNDAY\": \"domingo\",\n\t\"SURE_TO_DELETE_CHAT\": \"Tem certeza de que deseja excluir esse bate-papo? Essa ação não pode ser desfeita.\",\n\t\"SYMBOLS\": \"Símbolos\",\n\t\"TAP_TO_REMOVE\": \"Toque para remover\",\n\t\"TAP_TO_START_CONVERSATION\": \"Toque para iniciar a conversa\",\n\t\"TEXT\": \"Texto\",\n\t\"TEXT_TRANSLATE\": \"Tradução de texto\",\n\t\"TEXT_TRANSLATED\": \"Texto traduzido\",\n\t\"THEME\": \"Tema\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Esta mensagem foi excluída\",\n\t\"THREAD\": \"Tópico\",\n\t\"THURSDAY\": \"Quinta-feira\",\n\t\"TIME\": \"horas\",\n\t\"TIMES\": \"vezes\",\n\t\"TIME_ZONE\": \"Fuso horário\",\n\t\"TODAY\": \"Hoje\",\n\t\"TRANSFER\": \"Transferência\",\n\t\"TRANSFERRING\": \"Transferindo\",\n\t\"TRANSFER_CONFIRM\": \"Você é o proprietário do grupo; transfira a propriedade para um membro antes de sair do grupo\",\n\t\"TRANSFER_OWNERSHIP\": \"Transferir propriedade\",\n\t\"TRANSFER_SURE\": \"Tem certeza de que deseja transferir a propriedade? Isso não pode ser desfeito, e o novo proprietário assumirá o controle total.\",\n\t\"TRANSLATE\": \"Traduza\",\n\t\"TRANSLATED_MESSAGE\": \"Mensagem traduzida\",\n\t\"TRANSLATE_MESSAGE\": \"Traduzir mensagem\",\n\t\"TRAVEL_PLACES\": \"Viagens e lugares\",\n\t\"TRY_AGAIN\": \"Tente novamente\",\n\t\"TUESDAY\": \"terça\",\n\t\"TYPE\": \"Tipo\",\n\t\"TYPING\": \"Digitando...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Chamada de áudio não atendida\",\n\t\"UNANSWERED_CALL\": \"Chamada não atendida\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Chamada de vídeo não atendida\",\n\t\"UNBAN\": \"Unban\",\n\t\"UNBANNED\": \"desbanido\",\n\t\"UNBAN_SURE\": \"Tem certeza de que deseja desbanir este usuário?\",\n\t\"UNBLOCK\": \"Desbloquear\",\n\t\"UNBLOCK_CONTACT\": \"Desbloquear este contato\",\n\t\"UNBLOCK_SURE\": \"Tem certeza de que deseja desbloquear este contato? Você começará a receber mensagens dele novamente.\",\n\t\"UNBLOCK_USER\": \"Desbloquear usuário\",\n\t\"USERS\": \"Usuários\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Não encontramos nenhum usuário que corresponda à sua pesquisa. Tente ajustar sua pesquisa.\",\n\t\"USERS_WITH_MESSAGES\": \"Usuários com mensagens\",\n\t\"USER_INFO\": \"Informações do Usuário\",\n\t\"USER_LIST\": \"Lista de usuários\",\n\t\"VIDEOS\": \"Vídeos\",\n\t\"VIDEO_CALL\": \"Chamada de vídeo\",\n\t\"VIEW\": \"Visualizar\",\n\t\"VIEW_DETAIL\": \"Exibir detalhes\",\n\t\"VIEW_MEMBERS\": \"Exibir membros\",\n\t\"VIEW_ON_YOUTUBE\": \"Ver no Youtube\",\n\t\"VIEW_PROFILE\": \"Exibir perfil\",\n\t\"VISIT\": \"Visita\",\n\t\"VOICE\": \"Voz\",\n\t\"VOICE_CALL\": \"Chamada de voz\",\n\t\"VOICE_RECORDING\": \"Gravação de voz\",\n\t\"VOTE\": \"votar\",\n\t\"VOTES\": \"vota\",\n\t\"WEDNESDAY\": \"Quarta-feira\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Você gostaria de excluir esta conversa? Essa conversa será excluída de todos os seus dispositivos.\",\n\t\"WRONG_FILE_TYPE\": \"Você selecionou um tipo diferente de arquivo. Escolha o arquivo apropriado.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Este tipo de arquivo não é permitido.\",\n\t\"WRONG_PASSWORD\": \"Digite a senha correta e tente novamente\",\n\t\"WRONG_TEXT\": \"Parece que algo deu errado.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Por favor, tente novamente.\",\n\t\"YES\": \"sim\",\n\t\"YESTERDAY\": \"Ontem\",\n\t\"YOU\": \"Você\",\n\t\"YOU'VE_BLOCKED\": \"Você bloqueou\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Você já está em uma chamada em andamento\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Você excluiu esta mensagem\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Você ainda não tem nenhum adesivo.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Você iniciou uma chamada de áudio\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Você iniciou uma videochamada\",\n\t\"meeting\": \"Reunião\",\n\t\"Flag_Message_Title\": \"Denunciar uma mensagem\",\n\t\"Flag_Message_Subtitle\": \"Denuncie este chat se ele violar os nossos Padrões da Comunidade. Não informaremos a conta que você denunciou.\",\n\t\"Flag_Message_Remark_Label\": \"Motivo\",\n\t\"Flag_Message_Remark_Optional\": \"Opcional\",\n\t\"Flag_Message_Remark_Placeholder\": \"Forneça contexto adicional para sua denúncia…\",\n\t\"Flag_Message_Confirm_Yes\": \"Denunciar\",\n\t\"Flag_Message_Confirm_No\": \"Cancelar\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / Conteúdo indesejado\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Conteúdo explícito ou inapropriado\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Comportamento insultante ou ameaçador\",\n\t\"Message_List_Option_Flag_Message\": \"Denunciar\",\n\t\"Flag_Error_Text\":\"Algo deu errado. Por favor, verifique e tente novamente.\",\n\t\"Flag_Empty_Submit_Text\":\"Não foi possível enviar. Selecione um motivo antes de relatar esta mensagem.\",\n\t\"START_REPORTING\": \"Iniciar relatório\",\n\t\"LOGOUT\": \"Sair\",\n\t\"AI_ASSISTANTS\": \"Assistentes de IA\",\n\t\"CREATE_CONVERSATION\": \"Criar conversa\",\n\t\"EDIT_MODERATION\": \"Edição falhou. Sua mensagem foi bloqueada devido às políticas de moderação.\",\n\t\"BLOCKED_MODERATION\": \"Sua mensagem foi bloqueada devido às políticas de moderação.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Nenhum histórico de conversa encontrado\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Algo deu errado da nossa parte. Por favor, tente novamente.\",\n\t\"START_A_CHAT\": \"Inicie um chat tocando no botão 'Nova conversa'\",\n\t\"CHAT_HISTORY\": \"Histórico de chat\",\n\t\"ASK_ANYTHING\": \"Pergunte qualquer coisa...\",\n\t\"AI_ASSISTANT\": \"Assistente de IA\",\n\t\"SEARCH_PLACEHOLDER\": \"Pesquisar...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Sem Resultados\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Comece a digitar para buscar mensagens e conversas\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Nenhum resultado encontrado\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Não encontramos nenhuma correspondência. Tente outra palavra-chave.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Erro ao carregar conversas\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Erro ao carregar mensagens\",\n\t\"SEARCH_TRY_AGAIN\": \"Por favor, tente novamente mais tarde\",\n\t\"SEARCH_SEE_MORE\": \"Ver Mais\",\n\t\"SEARCH_LOADING\": \"Carregando...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Não lidos\",\n\t\"SEARCH_FILTER_GROUPS\": \"Grupos\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Fotos\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Vídeos\",\n\t\"SEARCH_FILTER_LINKS\": \"Links\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Documentos\",\n\t\"SEARCH_FILTER_AUDIO\": \"Áudio\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Conversas\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Mensagens\",\n\t\"SEARCH_NO_MESSAGES\": \"Nenhuma mensagem\",\n\t\"MESSAGE_REPORTED\": \"Mensagem denunciada\",\n\t\"MARK_AS_UNREAD\": \"Marcar não lido\",\n\t\"NEW\": \"Novo\",\n\t\"INSERT_LINK\": \"Inserir link\",\n\t\"EDIT_LINK\": \"Editar link\",\n\t\"LINK_TEXT\": \"Texto do link\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Inserir\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Você não pode enviar mensagem para este grupo porque não é mais membro.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/ru/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Принять\",\n\t\"ACTIONS\": \"Действия\",\n\t\"ACTIVITY\": \"Активность\",\n\t\"ADD\": \"Добавить\",\n\t\"ADDED\": \"добавил\",\n\t\"ADDING\": \"Добавление...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Добавить еще один ответ\",\n\t\"ADD_CONTACTS\": \"Добавьте контакты, чтобы начать разговоры, и они отобразятся здесь.\",\n\t\"ADD_MEMBERS\": \"Добавить участников\",\n\t\"ADD_NEW_OPTION\": \"Добавить новую опцию\",\n\t\"ADD_OPTION\": \"Добавить опцию\",\n\t\"ADD_OPTIONS\": \"Добавить опции\",\n\t\"ADD_REACTION\": \"Добавить реакцию\",\n\t\"ADD_TO_CHAT\": \"Добавить в чат\",\n\t\"ADMIN\": \"Администратор\",\n\t\"ADMINISTRATOR\": \"Администратор\",\n\t\"AI\": \"ИСКУССТВЕННЫЙ ИНТЕЛЛЕКТ\",\n\t\"ALL\": \"Все\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Все\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Уведомить всех в этой группе\",\n\t\"AND\": \"_\",\n\t\"ANIMALES_NATURE\": \"Животные и природа\",\n\t\"ANSWER\": \"Ответ\",\n\t\"APP_CREDENTIALS\": \"Учетные данные приложения\",\n\t\"ASK_QUESTION\": \"Задать вопрос\",\n\t\"AT\": \"в\",\n\t\"ATTACH\": \"Прикрепить\",\n\t\"ATTACH_AUDIO\": \"Прикрепите аудио\",\n\t\"ATTACH_DOCUMENT\": \"Прикрепить документ\",\n\t\"ATTACH_FILE\": \"Прикрепить файл\",\n\t\"ATTACH_IMAGE\": \"Прикрепите изображение\",\n\t\"ATTACH_VIDEO\": \"Прикрепите видео\",\n\t\"AT_A_TIME\": \"одновременно\",\n\t\"AUDIO_CALL\": \"Звуковой звонок\",\n\t\"AVATAR\": \"Аватар\",\n\t\"BADGE_COUNT\": \"Количество бейджей\",\n\t\"BAN\": \"Запрет\",\n\t\"BANNED\": \"запретили\",\n\t\"BANNED_MEMBERS\": \"Забаненные участники\",\n\t\"BAN_MEMBER_CONFIRM\": \"Вы уверены, что хотите заблокировать \",\n\t\"BLOCK\": \"Блокировать\",\n\t\"BLOCKED_USERS\": \"Заблокированные пользователи\",\n\t\"BLOCKED_USER_DESC\": \"Невозможно отправить сообщение, так как пользователь заблокирован\",\n\t\"BLOCK_CONTACT\": \"Заблокировать этот контакт?\",\n\t\"BLOCK_SURE\": \"Вы уверены, что хотите заблокировать этот контакт? Вы больше не будете получать от него сообщения.\",\n\t\"BLOCK_USER\": \"Заблокировать пользователя\",\n\t\"CALLING\": \"Звоню...\",\n\t\"CALLS\": \"Звонки\",\n\t\"CALL_ACCEPTED\": \"Вызов принят\",\n\t\"CALL_ANSWERED\": \"Вызов отвечен\",\n\t\"CALL_BUSY\": \"Звонок занят\",\n\t\"CALL_CANCELLED\": \"Вызов отменен\",\n\t\"CALL_DETAIL\": \"Сведения о звонке\",\n\t\"CALL_DETAILS\": \"Сведения о звонке\",\n\t\"CALL_ENDED\": \"Вызов завершен\",\n\t\"CALL_HISTORY\": \"История звонков\",\n\t\"CALL_INITIATED\": \"Вызов инициирован\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Совершайте или принимайте звонки, чтобы увидеть историю звонков, указанную здесь\",\n\t\"CALL_REJECTED\": \"Вызов отклонен\",\n\t\"CALL_UNANSWERED\": \"Звонок без ответа\",\n\t\"CAMERA\": \"камера\",\n\t\"CAMERA_PERMISSION\": \"У нас нет доступа к вашей камере. Чтобы разрешить доступ, нажмите Настройки и включите камеру.\",\n\t\"CANCEL\": \"Отменить\",\n\t\"CANCELLED_AUDIO_CALL\": \"Отмененный аудиозвонок\",\n\t\"CANCELLED_CALL\": \"Отмененный звонок\",\n\t\"CANCELLED_VIDEO_CALL\": \"Отмененный видеозвонок\",\n\t\"CANT__LOAD__CHATS\": \"Не удается загрузить чаты\",\n\t\"CARD\": \"Карточка\",\n\t\"CHANGE_ROLE\": \"Изменить роль\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Вы можете менять роли для управления групповыми разрешениями и обязанностями.\",\n\t\"CHANGE_SCOPE\": \"Изменить область\",\n\t\"CHANGE_TO\": \"Изменить на\",\n\t\"CHATS\": \"Чаты\",\n\t\"CHAT_PRIVATELY\": \"Чат в частном режиме\",\n\t\"CLICK_TO_REMOVE\": \"Нажмите, чтобы удалить\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Нажмите, чтобы начать разговор\",\n\t\"CLOSE\": \"Закрыть\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Совместная работа с помощью документа\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Сотрудничайте с помощью белой доски\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Совместный документ\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Белая доска для совместной работы\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Спросите роботов AI\",\n\t\"COMETCHAT_ASK_BOT\": \"Спросите\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"ИИ-бот\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Чем я могу помочь вам в этом разговоре? Пожалуйста, задайте мне вопрос, и я вам посоветую 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Введите здесь свое сообщение\",\n\t\"CONTINUE\": \"Продолжить\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Продолжить через Google\",\n\t\"CONVERSATIONS\": \"Беседы\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Начните новый чат или пригласите других присоединиться к беседе.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Беседы с сообщениями\",\n\t\"CONVERSATION_DELETED\": \"Беседа удалена\",\n\t\"CONVERSATION_LIST\": \"Список разговоров\",\n\t\"CONVERSATION_LIST_ITEM\": \"Элемент списка бесед\",\n\t\"CONVERSATION_STARTER\": \"Начало беседы\",\n\t\"CONVERSATION_SUMMARY\": \"Краткое содержание беседы\",\n\t\"COPY\": \"Копировать\",\n\t\"CREATE\": \"Создайте\",\n\t\"CREATED_DOCUMENT\": \"Вы создали новый совместный документ\",\n\t\"CREATED_WHITEBOARD\": \"Вы создали новую доску для совместной работы\",\n\t\"CREATE_GROUP\": \"Создать группу\",\n\t\"CREATE_POLL\": \"Опрос\",\n\t\"CREATING\": \"Создание\",\n\t\"CUSTOM_MESSAGE\": \"У вас есть сообщение\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Документ\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Местонахождение\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Опрос\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Наклейка\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Белая доска\",\n\t\"DATA_ITEM\": \"Элемент данных\",\n\t\"DECLINE\": \"Снижение\",\n\t\"DELETE\": \"Удалить\",\n\t\"DELETE_AND_EXIT\": \"Удалить и выйти\",\n\t\"DELETE_AND_EXIT_SURE\": \"Вы уверены, что хотите удалить этот чат и выйти из группы? Это действие нельзя отменить.\",\n\t\"DELETE_CHAT\": \"Удалить этот чат?\",\n\t\"DELETE_CHAT_TEXT\": \"Удалить чат\",\n\t\"DELETE_CONFIRM\": \"Вы действительно хотите удалить?\",\n\t\"DELETE_CONVERSATION\": \"Удалить беседу?\",\n\t\"DELETE_MESSAGE\": \"Удалить сообщение\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Вы уверены, что хотите удалить это сообщение? Это действие нельзя отменить.\",\n\t\"DELETE_MSG_TEXT\": \"Это сообщение было удалено\",\n\t\"DELETE_THIS_CONVERSATION\": \"Удалить этот разговор?\",\n\t\"DELETE_THIS_MESSAGE\": \"Удалить это сообщение?\",\n\t\"DELIVERED\": \"Доставлено\",\n\t\"DETAILS\": \"Подробности\",\n\t\"DOCS\": \"Документы\",\n\t\"DOCUMENT\": \"Документ\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Откройте документ для совместного редактирования содержимого\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Откройте доску, чтобы рисовать вместе\",\n\t\"EDIT\": \"Редактировать\",\n\t\"EDITED\": \"Редактировано\",\n\t\"EDIT_MESSAGE\": \"Изменить сообщение\",\n\t\"EMOJI\": \"смайлик\",\n\t\"ENTER_GROUP_NAME\": \"Введите имя группы\",\n\t\"ENTER_GROUP_PASSWORD\": \"Введите групповой пароль\",\n\t\"ENTER_PASSWORD\": \"Введите пароль\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Введите здесь свое сообщение\",\n\t\"ENTER_YOUR_OPTION\": \"Введите свой вариант\",\n\t\"ENTER_YOUR_PASSWORD\": \"Введите свой пароль\",\n\t\"ENTER_YOUR_QUESTION\": \"Введите свой вопрос\",\n\t\"ERROR\": \"Ошибка\",\n\t\"ERROR_TEXT\": \"Похоже, что-то пошло не так. Пожалуйста, попробуйте еще раз.\",\n\t\"FLAGS\": \"Флаги\",\n\t\"FOOD_DRINK\": \"Еда и напитки\",\n\t\"FORM\": \"Форма\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Спасибо за заполнение формы.\",\n\t\"FRIDAY\": \"пятница\",\n\t\"FULL_SCREEN_VIEWER\": \"Полноэкранный просмотрщик\",\n\t\"GENERATE_SUMMARY\": \"Сгенерируйте резюме\",\n\t\"GENERATING_ICEBREAKERS\": \"Генерация ледоколов\",\n\t\"GENERATING_REPLIES\": \"Генерация ответов\",\n\t\"GENERATING_SUMMARY\": \"Создание сводки\",\n\t\"GROUPS\": \"Группы\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Создайте группы или присоединитесь к ним, чтобы увидеть их в списке и начать сотрудничество\",\n\t\"GROUPS_WITH_MESSAGES\": \"Группы с сообщениями\",\n\t\"GROUP_INFO\": \"Информация о группе\",\n\t\"GROUP_LIST\": \"Список групп\",\n\t\"GROUP_MEMBERS\": \"Члены группы\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Добавьте контакты, чтобы увидеть их в списке.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Ожидание получения и просмотра сообщения получателями\",\n\t\"GROUP_NAME_BLANK\": \"Имя группы не может быть пустым\",\n\t\"GROUP_PASSWORD_BLANK\": \"Пароль группы не может быть пустым\",\n\t\"GROUP_TYPE_BLANK\": \"Тип группы не может быть пустым\",\n\t\"HELP\": \"Помощь\",\n\t\"HISTORY\": \"История\",\n\t\"IGNORE\": \"Проигнорировать\",\n\t\"INCOMING_AUDIO_CALL\": \"Входящий аудиозвонок\",\n\t\"INCOMING_CALL\": \"Входящий звонок\",\n\t\"INCOMING_VIDEO_CALL\": \"Входящий видеозвонок\",\n\t\"INFO\": \"Информация\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"инициировал звуковой звонок\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"инициировал видеозвонок\",\n\t\"INVALID_GROUP_NAME\": \"Введите действительное имя группы и повторите попытку\",\n\t\"INVALID_GROUP_TYPE\": \"Введите правильный тип группы и повторите попытку\",\n\t\"INVALID_PASSWORD\": \"Введите действительный пароль для группы и повторите попытку\",\n\t\"INVALID_POLL_OPTION\": \"Пожалуйста, введите требуемый ответ перед созданием опроса\",\n\t\"INVALID_POLL_QUESTION\": \"Пожалуйста, введите необходимый вопрос перед созданием опроса\",\n\t\"IN_A_THREAD\": \"В теме\",\n\t\"IS_TYPING\": \"печатает...\",\n\t\"JOIN\": \"Присоединяйтесь\",\n\t\"JOINED\": \"совместный\",\n\t\"JOIN_GROUP\": \"Присоединиться к группе\",\n\t\"JUMP\": \"Прыжок\",\n\t\"KICK\": \"Пинать\",\n\t\"KICKED\": \"били ногами\",\n\t\"LAST_ACTIVE_AT\": \"Последнее активное сообщение\",\n\t\"LAST_SEEN\": \"Последний раз видели\",\n\t\"LAUNCH\": \"Запуск\",\n\t\"LEAVE\": \"Уехать\",\n\t\"LEAVE_CONFIRM\": \"Вы уверены, что хотите покинуть группу?\",\n\t\"LEAVE_GROUP\": \"Покинуть группу\",\n\t\"LEAVE_GROUP_TEXT\": \"Покинуть группу\",\n\t\"LEAVE_SURE\": \"Вы уверены, что хотите покинуть группу? Вы больше не будете получать сообщения из этого чата.\",\n\t\"LEFT\": \"левый\",\n\t\"LEFT_THE_CALL\": \"оставил звонок\",\n\t\"LINK\": \"Ссылка\",\n\t\"LIVE_REACTION\": \"Живая реакция\",\n\t\"LOADING\": \"Загрузка...\",\n\t\"LOCALIZE\": \"Локализация\",\n\t\"LOGIN\": \"Войти\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Похоже, что-то пошло не так\",\n\t\"MADE\": \"сделал\",\n\t\"MEDIA_MESSAGE\": \"Сообщение для СМИ\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Забронируйте новый слот\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Временной интервал на эту дату недоступен. Пожалуйста, попробуйте другую дату.\",\n\t\"MEETING_SCHEDULED\": \"Ваша встреча запланирована.\",\n\t\"MEETING_SCHEDULER\": \"Планировщик собраний\",\n\t\"MEETING_SLOT_BOOK\": \"Временной интервал больше не доступен. Пожалуйста, выберите другое.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Пожалуйста, попробуйте другую дату.\",\n\t\"MEET_WITH\": \"Встретьтесь с\",\n\t\"MEMBER\": \"Член\",\n\t\"MEMBERS\": \"Члены\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Вы можете указать до 10 пользователей одновременно.\",\n\t\"MENTION_UPTO\": \"Вы можете упомянуть до\",\n\t\"MESSAGE\": \"Послание\",\n\t\"MESSAGES\": \"Сообщения\",\n\t\"MESSAGE_AUDIO\": \"Аудио\",\n\t\"MESSAGE_COMPOSER\": \"Композитор сообщений\",\n\t\"MESSAGE_COPIED\": \"Сообщение скопировано в буфер обмена.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Сообщение успешно удалено.\",\n\t\"MESSAGE_EDITED\": \"Сообщение успешно обновлено.\",\n\t\"MESSAGE_FILE\": \"Файл\",\n\t\"MESSAGE_HEADER\": \"Заголовок сообщения\",\n\t\"MESSAGE_IMAGE\": \"Изображение\",\n\t\"MESSAGE_INFORMATION\": \"Информация о сообщении\",\n\t\"MESSAGE_IS_DELETED\": \"Сообщение удалено\",\n\t\"MESSAGE_LIST\": \"Список сообщений\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} добавил(а) ${onName} в группу\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} заблокировал(а) ${onName} в группе\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} присоединился(ась) к группе\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} исключил(а) ${onName} из группы\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} покинул(а) группу\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} назначил(а) ${onName} как ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} разблокировал(а) ${onName} в группе\",\n\t\"MESSAGE_PRIVATELY\": \"Сообщение конфиденциально\",\n\t\"MESSAGE_RECEIPT\": \"Получение сообщения\",\n\t\"MESSAGE_TRANSLATED\": \"Сообщение успешно переведено.\",\n\t\"MESSAGE_VIDEO\": \"Видео\",\n\t\"MICROPHONE_PERMISSION\": \"Чтобы записать голосовое сообщение, нам нужен доступ к вашему микрофону. Нажмите «Настройки» и включите «Микрофон».\",\n\t\"MISSED_AUDIO_CALL\": \"Пропущенный аудиозвонок\",\n\t\"MISSED_CALL\": \"Пропущенный звонок\",\n\t\"MISSED_VIDEO_CALL\": \"Пропущенный видеозвонок\",\n\t\"MISSED_VOICE_CALL\": \"Пропущенный голосовой вызов\",\n\t\"MODERATOR\": \"ведущий\",\n\t\"MONDAY\": \"понедельник\",\n\t\"MORE\": \"Больше\",\n\t\"MORE_TIMES\": \"Больше раз\",\n\t\"NAME\": \"Имя\",\n\t\"NEW_CHAT\": \"Новый чат\",\n\t\"NEW_MESSAGE\": \"новое сообщение\",\n\t\"NEW_MESSAGES\": \"новые сообщения\",\n\t\"NEW__GROUP\": \"Новая группа\",\n\t\"NO\": \"Нет\",\n\t\"NOTIFICATIONS\": \"Уведомления\",\n\t\"NOT_SUPPORTED\": \"Этот тип сообщения не поддерживается\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Забаненных участников не найдено\",\n\t\"NO_CALLS_FOUND\": \"Звонки не найдены\",\n\t\"NO_CALLS_SELECTED\": \"Вызовы не выбраны\",\n\t\"NO_CALL_LOGS\": \"Журналов вызовов пока нет\",\n\t\"NO_CHATS_FOUND\": \"Чаты не найдены\",\n\t\"NO_CHATS_SELECTED\": \"Чаты не выбраны\",\n\t\"NO_CONVERSATIONS\": \"Пока нет разговоров»;  \",\n\t\"NO_DOCUMENTS\": \"Нет документов\",\n\t\"NO_GROUPS_AVAILABLE\": \"Нет доступных групп\",\n\t\"NO_GROUPS_FOUND\": \"Группы не найдены\",\n\t\"NO_GROUPS_SELECTED\": \"Не выбрано ни одной группы\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Нет доступных членов группы\",\n\t\"NO_MESSAGES_FOUND\": \"Сообщения не найдены\",\n\t\"NO_PHOTOS\": \"Нет фотографий\",\n\t\"NO_RECIPIENT\": \"Получателя нет\",\n\t\"NO_RECIPIENTS\": \"Получателей нет\",\n\t\"NO_RECORDS_FOUND\": \"Записей не найдено\",\n\t\"NO_REPLIES_FOUND\": \"Ответов не найдено\",\n\t\"NO_STICKERS_AVAILABLE\": \"Стикеров нет\",\n\t\"NO_STICKERS_FOUND\": \"Стикеры не найдены\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Предложений нет\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Сводка недоступна\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Временных слотов нет\",\n\t\"NO_USERS_AVAILABLE\": \"Нет доступных пользователей\",\n\t\"NO_USERS_FOUND\": \"Пользователи не найдены\",\n\t\"NO_USERS_SELECTED\": \"Не выбрано ни одного пользователя\",\n\t\"NO_VIDEOS\": \"Нет видео\",\n\t\"NO_VOTE\": \"Нет голосования\",\n\t\"OBJECTS\": \"Объекты\",\n\t\"OFFLINE\": \"Не в сети\",\n\t\"ONGOING_AUDIO_CALL\": \"Продолжающийся аудиозвонок\",\n\t\"ONGOING_CALL\": \"Постоянный звонок\",\n\t\"ONGOING_VIDEO_CALL\": \"Продолжающийся видеозвонок\",\n\t\"ONLINE\": \"Онлайн\",\n\t\"ON_ANOTHER_CALL\": \"звонит по другому звонку\",\n\t\"OOPS\": \"УПС\",\n\t\"OOPS!\": \"УПС!\",\n\t\"OPEN_DOCUMENT\": \"Открыть документ\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Откройте документ для совместного рисования\",\n\t\"OPEN_WHITEBOARD\": \"Открытая доска\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Откройте доску, чтобы рисовать вместе\",\n\t\"OPTIONS\": \"Опции\",\n\t\"OTHER\": \"Другой\",\n\t\"OTHERS\": \"другие\",\n\t\"OUTGOING_AUDIO_CALL\": \"Исходящий аудиозвонок\",\n\t\"OUTGOING_CALL\": \"Исходящий звонок\",\n\t\"OUTGOING_VIDEO_CALL\": \"Исходящий видеозвонок\",\n\t\"OWNER\": \"Владелец\",\n\t\"PARTICIPANT\": \"Участник\",\n\t\"PARTICIPANTS\": \"Участники\",\n\t\"PASSWORD\": \"Пароль\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Неверный пароль для группы!\",\n\t\"PASSWORD_PROTECTED\": \"Защищено паролем\",\n\t\"PHOTOS\": \"Фотографии\",\n\t\"PICK_YOUR_EMOJI\": \"Выберите свой смайлик\",\n\t\"POLLS\": \"Опросы\",\n\t\"PREFERENCES\": \"Предпочтения\",\n\t\"PRIVACY\": \"Конфиденциальность\",\n\t\"PRIVACY_AND_SECURITY\": \"Конфиденциальность и безопасность\",\n\t\"PRIVATE\": \"Частное\",\n\t\"PRIVATE_GROUP\": \"Частная группа\",\n\t\"PROTECTED_GROUP\": \"Защищенная группа\",\n\t\"PUBLIC\": \"Общественность\",\n\t\"QUESTION\": \"Вопрос\",\n\t\"QUESTIONS\": \"Вопросы\",\n\t\"REACHED_MAX_LIMIT\": \"Вы достигли предела. Можно добавить до 12 вариантов.\",\n\t\"REACT\": \"React\",\n\t\"REACTED\": \"отреагировал\",\n\t\"REACT_TO_MESSAGE\": \"Реагировать на сообщение\",\n\t\"READ\": \"Прочтите\",\n\t\"READ_MORE\": \"Прочитайте больше\",\n\t\"RECEIPT_INFORMATION\": \"Информация о чеке\",\n\t\"RECORDING\": \"Запись\",\n\t\"REGION\": \"Регион\",\n\t\"REJECTED_AUDIO_CALL\": \"Отклоненный аудиозвонок\",\n\t\"REJECTED_CALL\": \"отклоненный звонок\",\n\t\"REJECTED_VIDEO_CALL\": \"Отклоненный видеозвонок\",\n\t\"REMOVE\": \"Удалить\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Вы уверены, что хотите удалить\",\n\t\"REPLIES\": \"ответы\",\n\t\"REPLY\": \"ответить\",\n\t\"REPLY_IN_THREAD\": \"Ответ в теме\",\n\t\"REPLY_TO_THREAD\": \"Ответить на тему\",\n\t\"REPORT_PROBLEM\": \"Сообщить о проблеме\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Пожалуйста, заполните все обязательные поля перед созданием опроса\",\n\t\"RESIZE\": \"Изменить размер\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Для этой функции требуются определенные разрешения на использование ресурсов. Пожалуйста, включите необходимые разрешения в настройках устройства\",\n\t\"RETRY\": \" Повторить попытку\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Выбранный язык для перевода аналогичен языку оригинального сообщения\",\n\t\"SATURDAY\": \"суббота\",\n\t\"SAVE\": \"Сохранить\",\n\t\"SCHEDULE\": \"Расписание\",\n\t\"SCOPE\": \"Область применения\",\n\t\"SCOPE_CHANGE_INFO\": \"Вы можете изменить роли, чтобы управлять разрешениями и обязанностями группы\",\n\t\"SEARCH\": \"Поиск\",\n\t\"SEARCH_EMOJI\": \"Поиск смайликов\",\n\t\"SEEN\": \"Увиденное\",\n\t\"SELECT_A_DATE\": \"Выберите дату\",\n\t\"SELECT_DAY\": \"Выберите день\",\n\t\"SELECT_GROUP_TYPE\": \"Выберите тип группы\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Выберите источник входного звука\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Выберите выходной источник звука\",\n\t\"SELECT_TIME\": \"Выберите время\",\n\t\"SELECT_VIDEO_SOURCE\": \"Выберите источник видео\",\n\t\"SELECT__GROUP\": \"Выберите группу, чтобы начать обмен сообщениями\",\n\t\"SELECT__USER\": \"Выберите пользователя, чтобы начать обмен сообщениями\",\n\t\"SEND\": \"Отправить\",\n\t\"SEND_MESSAGE\": \"Отправить сообщение\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Отправить сообщение конфиденциально\",\n\t\"SENT\": \"Отправлено\",\n\t\"SETTINGS\": \"Настройки\",\n\t\"SET_THE_ANSWERS\": \"ЗАДАЙТЕ ОТВЕТЫ\",\n\t\"SHARE\": \"Поделись\",\n\t\"SHARED\": \"Общий\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"поделился совместным документом\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"поделился совместной доской\",\n\t\"SHARED_FILE\": \"Общий файл\",\n\t\"SHARED_LOCATION\": \"Общее местоположение\",\n\t\"SHARED_MEDIA\": \"Общие медиафайлы\",\n\t\"SHOW_LESS\": \"Показать меньше\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Вы действительно хотите увидеть небезопасный контент?\",\n\t\"SMART_REPLIES\": \"Умные ответы\",\n\t\"SMILEY_PEOPLE\": \"Смайлы и люди\",\n\t\"SOMETHING_WENT_WRONG\": \"Похоже, что-то пошло не так.\",\n\t\"SOMETHING_WRONG\": \"Что-то пошло не так. Пожалуйста, попробуйте еще раз.\",\n\t\"SOUND_MANAGER\": \"Звуковой менеджер\",\n\t\"STATUS_INDICATOR\": \"Индикатор состояния\",\n\t\"STICKER\": \"Наклейка\",\n\t\"SUGGEST_A_REPLY\": \"Предложите ответ\",\n\t\"SUNDAY\": \"Воскресенье\",\n\t\"SURE_TO_DELETE_CHAT\": \"Вы действительно хотите удалить этот чат? Это действие нельзя отменить.\",\n\t\"SYMBOLS\": \"Символы\",\n\t\"TAP_TO_REMOVE\": \"Нажмите, чтобы удалить\",\n\t\"TAP_TO_START_CONVERSATION\": \"Нажмите, чтобы начать разговор\",\n\t\"TEXT\": \"Текст\",\n\t\"TEXT_TRANSLATE\": \"Перевод текста\",\n\t\"TEXT_TRANSLATED\": \"Текст переведен\",\n\t\"THEME\": \"Тема\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Это сообщение было удалено\",\n\t\"THREAD\": \"Тема\",\n\t\"THURSDAY\": \"Четверг\",\n\t\"TIME\": \"время\",\n\t\"TIMES\": \"раз\",\n\t\"TIME_ZONE\": \"Часовой пояс\",\n\t\"TODAY\": \"Сегодня\",\n\t\"TRANSFER\": \"Трансфер\",\n\t\"TRANSFERRING\": \"Передача\",\n\t\"TRANSFER_CONFIRM\": \"Вы являетесь владельцем группы; пожалуйста, передайте право собственности члену перед тем, как покинуть группу\",\n\t\"TRANSFER_OWNERSHIP\": \"Передача права собственности\",\n\t\"TRANSFER_SURE\": \"Вы уверены, что хотите передать право собственности? Это нельзя отменить, и новый владелец получит полный контроль.\",\n\t\"TRANSLATE\": \"Перевести\",\n\t\"TRANSLATED_MESSAGE\": \"Переведенное сообщение\",\n\t\"TRANSLATE_MESSAGE\": \"Перевести сообщение\",\n\t\"TRAVEL_PLACES\": \"Путешествия и места\",\n\t\"TRY_AGAIN\": \"Попробуй еще раз\",\n\t\"TUESDAY\": \"вторник\",\n\t\"TYPE\": \"Тип\",\n\t\"TYPING\": \"Набрав...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Звуковой звонок без ответа\",\n\t\"UNANSWERED_CALL\": \"Звонок без ответа\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Видеозвонок без ответа\",\n\t\"UNBAN\": \"Разбанить\",\n\t\"UNBANNED\": \"разблокирован\",\n\t\"UNBAN_SURE\": \"Вы уверены, что хотите снять бан с этого пользователя?\",\n\t\"UNBLOCK\": \"Разблокировать\",\n\t\"UNBLOCK_CONTACT\": \"Разблокировать этот контакт\",\n\t\"UNBLOCK_SURE\": \"Вы уверены, что хотите разблокировать этот контакт? Вы снова начнете получать от него сообщения.\",\n\t\"UNBLOCK_USER\": \"Разблокировать пользователя\",\n\t\"USERS\": \"Пользователи\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Нам не удалось найти пользователей, соответствующих вашему запросу. Попробуйте настроить поиск.\",\n\t\"USERS_WITH_MESSAGES\": \"Пользователи с сообщениями\",\n\t\"USER_INFO\": \"Информация о пользователе\",\n\t\"USER_LIST\": \"Список пользователей\",\n\t\"VIDEOS\": \"Видеоролики\",\n\t\"VIDEO_CALL\": \"Видеозвонок\",\n\t\"VIEW\": \"Вид\",\n\t\"VIEW_DETAIL\": \"Показать подробности\",\n\t\"VIEW_MEMBERS\": \"Показать участников\",\n\t\"VIEW_ON_YOUTUBE\": \"Смотреть на Youtube\",\n\t\"VIEW_PROFILE\": \"Показать профиль\",\n\t\"VISIT\": \"Посетите\",\n\t\"VOICE\": \"Голос\",\n\t\"VOICE_CALL\": \"Голосовой вызов\",\n\t\"VOICE_RECORDING\": \"Запись голоса\",\n\t\"VOTE\": \"голосования\",\n\t\"VOTES\": \"голосов\",\n\t\"WEDNESDAY\": \"Среда\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Хотите удалить этот разговор? Этот разговор будет удален со всех ваших устройств.\",\n\t\"WRONG_FILE_TYPE\": \"Вы выбрали другой тип файла. Выберите подходящий файл.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Этот тип файла не разрешён.\",\n\t\"WRONG_PASSWORD\": \"Пожалуйста, введите правильный пароль и попробуйте снова\",\n\t\"WRONG_TEXT\": \"Похоже, что что-то пошло не так.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Пожалуйста, попробуйте ещё раз.\",\n\t\"YES\": \"Да\",\n\t\"YESTERDAY\": \"Вчера\",\n\t\"YOU\": \"Ты\",\n\t\"YOU'VE_BLOCKED\": \"Вы заблокировали\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Вы уже на постоянном звонке\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Вы удалили это сообщение\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"У вас пока нет стикеров.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Вы инициировали аудиозвонок\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Вы инициировали видеозвонок\",\n\t\"meeting\": \"Встреча\",\n\t\"Flag_Message_Title\": \"Пожаловаться на сообщение\",\n\t\"Flag_Message_Subtitle\": \"Сообщите о чате, если он нарушает наши Правила сообщества. Мы не скажем аккаунту, что вы сообщили.\",\n\t\"Flag_Message_Remark_Label\": \"Причина\",\n\t\"Flag_Message_Remark_Optional\": \"Необязательно\",\n\t\"Flag_Message_Remark_Placeholder\": \"Укажите дополнительный контекст для вашей жалобы…\",\n\t\"Flag_Message_Confirm_Yes\": \"Пожаловаться\",\n\t\"Flag_Message_Confirm_No\": \"Отмена\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Спам / нежелательный контент\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Откровенный или неподобающий контент\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Оскорбительное или угрожающее поведение\",\n\t\"Message_List_Option_Flag_Message\": \"Пожаловаться\",\n\t\"Flag_Error_Text\":\"Произошла ошибка. Пожалуйста, проверьте и попробуйте снова.\",\n\t\"Flag_Empty_Submit_Text\":\"Невозможно отправить. Пожалуйста, выберите причину перед тем, как пожаловаться на это сообщение.\",\n\t\"START_REPORTING\": \"Начать отчет\",\n\t\"LOGOUT\": \"Выйти\",\n\t\"AI_ASSISTANTS\": \"ИИ ассистенты\",\n\t\"CREATE_CONVERSATION\": \"Создать разговор\",\n\t\"EDIT_MODERATION\": \"Ошибка редактирования. Ваше сообщение было заблокировано из-за политик модерации.\",\n\t\"BLOCKED_MODERATION\": \"Ваше сообщение было заблокировано из-за политик модерации.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"История чатов не найдена\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Что-то пошло не так с нашей стороны. Пожалуйста, попробуйте снова.\",\n\t\"START_A_CHAT\": \"Начните чат, нажав кнопку 'Новый чат'\",\n\t\"CHAT_HISTORY\": \"История чатов\",\n\t\"ASK_ANYTHING\": \"Спрашивайте все...\",\n\t\"AI_ASSISTANT\": \"AI ассистент\",\n\t\"SEARCH_PLACEHOLDER\": \"Поиск...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Нет результатов\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Начните вводить текст для поиска сообщений и бесед\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Ничего не найдено\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Мы не нашли совпадений. Попробуйте другой запрос.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Ошибка загрузки бесед\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Ошибка загрузки сообщений\",\n\t\"SEARCH_TRY_AGAIN\": \"Пожалуйста, попробуйте позже\",\n\t\"SEARCH_SEE_MORE\": \"Показать больше\",\n\t\"SEARCH_LOADING\": \"Загрузка...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Непрочитанные\",\n\t\"SEARCH_FILTER_GROUPS\": \"Группы\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Фото\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Видео\",\n\t\"SEARCH_FILTER_LINKS\": \"Ссылки\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Документы\",\n\t\"SEARCH_FILTER_AUDIO\": \"Аудио\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Беседы\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Сообщения\",\n\t\"SEARCH_NO_MESSAGES\": \"Нет сообщений\",\n\t\"MESSAGE_REPORTED\": \"Сообщение отправлено на жалобу\",\n\t\"MARK_AS_UNREAD\": \"Отметить непрочитанным\",\n\t\"NEW\": \"Новый\",\n\t\"INSERT_LINK\": \"Вставить ссылку\",\n\t\"EDIT_LINK\": \"Редактировать ссылку\",\n\t\"LINK_TEXT\": \"Текст ссылки\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Вставить\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Вы не можете отправить сообщение в эту группу, так как вы больше не являетесь участником.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/sv/translation.json",
    "content": "{\n\t\"ACCEPT\": \"Acceptera\",\n\t\"ACTIONS\": \"Åtgärder\",\n\t\"ACTIVITY\": \"Aktivitet\",\n\t\"ADD\": \"Lägg till\",\n\t\"ADDED\": \"Lagt till\",\n\t\"ADDING\": \"Lägger till...\",\n\t\"ADD_ANOTHER_ANSWER\": \"Lägg till ett annat svar\",\n\t\"ADD_CONTACTS\": \"Lägg till kontakter för att starta konversationer och se dem listade här.\",\n\t\"ADD_MEMBERS\": \"Lägg till medlemmar\",\n\t\"ADD_NEW_OPTION\": \"Lägg till nytt alternativ\",\n\t\"ADD_OPTION\": \"Lägg till alternativ\",\n\t\"ADD_OPTIONS\": \"Lägg till alternativ\",\n\t\"ADD_REACTION\": \"Lägg till reaktion\",\n\t\"ADD_TO_CHAT\": \"Lägg till i chatten\",\n\t\"ADMIN\": \"Administratör\",\n\t\"ADMINISTRATOR\": \"Administratör\",\n\t\"AI\": \"AI\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Alla\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Notifiera alla i den här gruppen\",\n\t\"ALL\": \"Allt\",\n\t\"AND\": \"ett\",\n\t\"ANIMALES_NATURE\": \"Djur & Natur\",\n\t\"ANSWER\": \"Svara\",\n\t\"APP_CREDENTIALS\": \"Appreferenser\",\n\t\"ASK_QUESTION\": \"Ställ en fråga\",\n\t\"AT\": \"på\",\n\t\"ATTACH\": \"Bifoga\",\n\t\"ATTACH_AUDIO\": \"Bifoga ljud\",\n\t\"ATTACH_DOCUMENT\": \"Bifoga dokument\",\n\t\"ATTACH_FILE\": \"Bifoga fil\",\n\t\"ATTACH_IMAGE\": \"Bifoga bild\",\n\t\"ATTACH_VIDEO\": \"Bifoga video\",\n\t\"AT_A_TIME\": \"åt gången\",\n\t\"AUDIO_CALL\": \"Ljudsamtal\",\n\t\"AVATAR\": \"Avatar\",\n\t\"BADGE_COUNT\": \"Antal märken\",\n\t\"BAN\": \"Förbud\",\n\t\"BANNED\": \"förbjudna\",\n\t\"BANNED_MEMBERS\": \"Förbjudna medlemmar\",\n\t\"BAN_MEMBER_CONFIRM\": \"Är du säker på att du vill banna \",\n\t\"BLOCK\": \"Blockera\",\n\t\"BLOCKED_USERS\": \"Blockerade användare\",\n\t\"BLOCKED_USER_DESC\": \"Kan inte skicka ett meddelande eftersom användaren är blockerad\",\n\t\"BLOCK_CONTACT\": \"Blockera denna kontakt?\",\n\t\"BLOCK_SURE\": \"Är du säker på att du vill blockera denna kontakt? Du kommer inte att få meddelanden från dem längre.\",\n\t\"BLOCK_USER\": \"Blockera användare\",\n\t\"CALLING\": \"Ringer...\",\n\t\"CALLS\": \"Samtal\",\n\t\"CALL_ACCEPTED\": \"Samtal accepterat\",\n\t\"CALL_ANSWERED\": \"Samtal besvarat\",\n\t\"CALL_BUSY\": \"Ring upptagen\",\n\t\"CALL_CANCELLED\": \"Samtal avbruten\",\n\t\"CALL_DETAIL\": \"Samtalsdetaljer\",\n\t\"CALL_DETAILS\": \"Samtalsinformation\",\n\t\"CALL_ENDED\": \"Samtalet avslutades\",\n\t\"CALL_HISTORY\": \"Samtalshistorik\",\n\t\"CALL_INITIATED\": \"Samtal initierat\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Ring eller ta emot samtal för att se din samtalshistorik listad här\",\n\t\"CALL_REJECTED\": \"Samtal avvisat\",\n\t\"CALL_UNANSWERED\": \"Ring obesvarat\",\n\t\"CAMERA\": \"kamera\",\n\t\"CAMERA_PERMISSION\": \"Vi har inte tillgång till din kamera. Aktivera åtkomst genom att trycka på Inställningar och slå på Kamera.\",\n\t\"CANCEL\": \"Avbryt\",\n\t\"CANCELLED_AUDIO_CALL\": \"Avbrutet ljudsamtal\",\n\t\"CANCELLED_CALL\": \"Avbrutet samtal\",\n\t\"CANCELLED_VIDEO_CALL\": \"Avbrutet videosamtal\",\n\t\"CANT__LOAD__CHATS\": \"Det går inte att ladda chattar\",\n\t\"CARD\": \"Kort\",\n\t\"CHANGE_ROLE\": \"Ändra roll\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Du kan ändra roller för att hantera gruppbehörigheter och ansvarsområden.\",\n\t\"CHANGE_SCOPE\": \"Ändra omfattning\",\n\t\"CHANGE_TO\": \"Ändra till\",\n\t\"CHATS\": \"Chattar\",\n\t\"CHAT_PRIVATELY\": \"Chatta privat\",\n\t\"CLICK_TO_REMOVE\": \"Klicka för att ta bort\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Klicka för att starta konversation\",\n\t\"CLOSE\": \"Stäng\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Samarbeta med hjälp av ett dokument\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Samarbeta med hjälp av en whiteboard\",\n\t\"COLLABORATIVE_DOCUMENT\": \"Samarbetsdokument\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"Kollaborativ whiteboard\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Fråga AI-bots\",\n\t\"COMETCHAT_ASK_BOT\": \"Fråga\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI Bot\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Hur kan jag hjälpa dig med det här samtalet? Ställ mig en fråga så ska jag ge dig råd 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Skriv ditt meddelande här\",\n\t\"CONTINUE\": \"Fortsätta\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Fortsätt med Google\",\n\t\"CONVERSATIONS\": \"Konversationer\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Starta en ny chatt eller bjud in andra att delta i konversationen.\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Konversationer med meddelanden\",\n\t\"CONVERSATION_DELETED\": \"Konversation borttagen\",\n\t\"CONVERSATION_LIST\": \"Konversationslista\",\n\t\"CONVERSATION_LIST_ITEM\": \"Konversationslistobjekt\",\n\t\"CONVERSATION_STARTER\": \"Konversationsstartare\",\n\t\"CONVERSATION_SUMMARY\": \"Konversationssammanfattning\",\n\t\"COPY\": \"Kopiera\",\n\t\"CREATE\": \"Skapa\",\n\t\"CREATED_DOCUMENT\": \"Du har skapat ett nytt samarbetsdokument\",\n\t\"CREATED_WHITEBOARD\": \"Du har skapat en ny kollaborativ whiteboard\",\n\t\"CREATE_GROUP\": \"Skapa grupp\",\n\t\"CREATE_POLL\": \"Undersökning\",\n\t\"CREATING\": \"Skapar\",\n\t\"CUSTOM_MESSAGE\": \"Du har ett meddelande\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Dokument\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 Plats\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Omröstning\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Klistermärke\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Whiteboard\",\n\t\"DATA_ITEM\": \"Dataobjekt\",\n\t\"DECLINE\": \"Nedgång\",\n\t\"DELETE\": \"Ta bort\",\n\t\"DELETE_AND_EXIT\": \"Ta bort och avsluta\",\n\t\"DELETE_AND_EXIT_SURE\": \"Är du säker på att du vill radera denna chatt och lämna gruppen? Denna åtgärd kan inte ångras.\",\n\t\"DELETE_CHAT\": \"Ta bort den här chatten?\",\n\t\"DELETE_CHAT_TEXT\": \"Radera chatt\",\n\t\"DELETE_CONFIRM\": \"Är du säker på att du vill ta bort?\",\n\t\"DELETE_CONVERSATION\": \"Radera konversation?\",\n\t\"DELETE_MESSAGE\": \"Ta bort meddelande\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Är du säker på att du vill radera detta meddelande? Denna åtgärd kan inte ångras.\",\n\t\"DELETE_MSG_TEXT\": \"Detta meddelande har tagits bort\",\n\t\"DELETE_THIS_CONVERSATION\": \"Ta bort denna konversation?\",\n\t\"DELETE_THIS_MESSAGE\": \"Radera meddelandet?\",\n\t\"DELIVERED\": \"Levereras\",\n\t\"DETAILS\": \"Detaljer\",\n\t\"DOCS\": \"Dokument\",\n\t\"DOCUMENT\": \"Dokument\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"Öppna dokument för att redigera innehåll tillsammans\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Öppna whiteboard för att rita tillsammans\",\n\t\"EDIT\": \"Redigera\",\n\t\"EDITED\": \"Redigerad\",\n\t\"EDIT_MESSAGE\": \"Redigera meddelande\",\n\t\"EMOJI\": \"Emoji\",\n\t\"ENTER_GROUP_NAME\": \"Ange gruppnamn\",\n\t\"ENTER_GROUP_PASSWORD\": \"Ange grupplösenord\",\n\t\"ENTER_PASSWORD\": \"Ange lösenord\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Skriv ditt meddelande här\",\n\t\"ENTER_YOUR_OPTION\": \"Ange ditt alternativ\",\n\t\"ENTER_YOUR_PASSWORD\": \"Ange ditt lösenord\",\n\t\"ENTER_YOUR_QUESTION\": \"Fyll i din fråga\",\n\t\"ERROR\": \"Fel\",\n\t\"ERROR_TEXT\": \"Ser ut som att något gick fel. Vänligen försök igen.\",\n\t\"FLAGS\": \"flaggor\",\n\t\"FOOD_DRINK\": \"Mat & Dryck\",\n\t\"FORM\": \"Formulär\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Tack för att du fyllde i formuläret.\",\n\t\"FRIDAY\": \"fredag\",\n\t\"FULL_SCREEN_VIEWER\": \"Helskärmsläsare\",\n\t\"GENERATE_SUMMARY\": \"Skapa en sammanfattning\",\n\t\"GENERATING_ICEBREAKERS\": \"Generera isbrytare\",\n\t\"GENERATING_REPLIES\": \"Generera svar\",\n\t\"GENERATING_SUMMARY\": \"Generera sammanfattning\",\n\t\"GROUPS\": \"Grupper\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Skapa eller gå med i grupper för att se dem listade här och börja samarbeta\",\n\t\"GROUPS_WITH_MESSAGES\": \"Grupper med meddelanden\",\n\t\"GROUP_INFO\": \"Gruppinformation\",\n\t\"GROUP_LIST\": \"Grupplista\",\n\t\"GROUP_MEMBERS\": \"Gruppmedlemmar\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Lägg till kontakter för att se dem listade här.\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Väntar på att mottagarna ska ta emot och visa meddelandet\",\n\t\"GROUP_NAME_BLANK\": \"Gruppnamnet får inte vara tomt\",\n\t\"GROUP_PASSWORD_BLANK\": \"Grupplösenordet kan inte vara tomt\",\n\t\"GROUP_TYPE_BLANK\": \"Grupptyp kan inte vara tom\",\n\t\"HELP\": \"Hjälp\",\n\t\"HISTORY\": \"Historia\",\n\t\"IGNORE\": \"Ignorera\",\n\t\"INCOMING_AUDIO_CALL\": \"Inkommande ljudsamtal\",\n\t\"INCOMING_CALL\": \"Inkommande samtal\",\n\t\"INCOMING_VIDEO_CALL\": \"Inkommande videosamtal\",\n\t\"INFO\": \"Info\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"har initierat ett ljudsamtal\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"har initierat ett videosamtal\",\n\t\"INVALID_GROUP_NAME\": \"Ange ett giltigt namn för gruppen och försök igen\",\n\t\"INVALID_GROUP_TYPE\": \"Ange en giltig typ för gruppen och försök igen\",\n\t\"INVALID_PASSWORD\": \"Ange ett giltigt lösenord för gruppen och försök igen\",\n\t\"INVALID_POLL_OPTION\": \"Ange önskat svar innan du skapar en omröstning\",\n\t\"INVALID_POLL_QUESTION\": \"Ange önskad fråga innan du skapar en omröstning\",\n\t\"IN_A_THREAD\": \"I en tråd\",\n\t\"IS_TYPING\": \"skriver...\",\n\t\"JOIN\": \"Gå med\",\n\t\"JOINED\": \"anslöt sig\",\n\t\"JOIN_GROUP\": \"Gå med i gruppen\",\n\t\"JUMP\": \"Hoppa\",\n\t\"KICK\": \"Sparka\",\n\t\"KICKED\": \"sparkad\",\n\t\"LAST_ACTIVE_AT\": \"Senast aktiv kl\",\n\t\"LAST_SEEN\": \"Senast sett\",\n\t\"LAUNCH\": \"Lansera\",\n\t\"LEAVE\": \"Lämna\",\n\t\"LEAVE_CONFIRM\": \"Är du säker på att du vill lämna gruppen?\",\n\t\"LEAVE_GROUP\": \"Lämna gruppen\",\n\t\"LEAVE_GROUP_TEXT\": \"Lämna denna grupp\",\n\t\"LEAVE_SURE\": \"Är du säker på att du vill lämna gruppen? Du kommer inte längre att få några meddelanden från denna chatt.\",\n\t\"LEFT\": \"vänster\",\n\t\"LEFT_THE_CALL\": \"lämnade samtalet\",\n\t\"LINK\": \"Länk\",\n\t\"LIVE_REACTION\": \"Levande reaktion\",\n\t\"LOADING\": \"Laddar...\",\n\t\"LOCALIZE\": \"Lokalisera\",\n\t\"LOGIN\": \"Logga in\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Ser ut som att något gick fel\",\n\t\"MADE\": \"gjord\",\n\t\"MEDIA_MESSAGE\": \"Mediemeddelande\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Boka ny spelautomat\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Ingen tidslucka tillgänglig för detta datum. Försök med ett annat datum.\",\n\t\"MEETING_SCHEDULED\": \"Ditt möte är planerat.\",\n\t\"MEETING_SCHEDULER\": \"Mötesschemaläggare\",\n\t\"MEETING_SLOT_BOOK\": \"Tidslucka är inte längre tillgänglig. Vänligen välj en annan.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Försök med ett annat datum.\",\n\t\"MEET_WITH\": \"Möt med\",\n\t\"MEMBER\": \"Medlem\",\n\t\"MEMBERS\": \"Medlemmar\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Du kan nämna upp till 10 användare samtidigt.\",\n\t\"MENTION_UPTO\": \"Du kan nämna upp till\",\n\t\"MESSAGE\": \"Meddelande\",\n\t\"MESSAGES\": \"Meddelanden\",\n\t\"MESSAGE_AUDIO\": \"Ljud\",\n\t\"MESSAGE_COMPOSER\": \"Meddelandekompositör\",\n\t\"MESSAGE_COPIED\": \"Meddelande kopierat till Urklipp.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Meddelandet har tagits bort framgångsrikt.\",\n\t\"MESSAGE_EDITED\": \"Meddelandet uppdaterades framgångsrikt.\",\n\t\"MESSAGE_FILE\": \"Fil\",\n\t\"MESSAGE_HEADER\": \"Meddelandehuvud\",\n\t\"MESSAGE_IMAGE\": \"Bild\",\n\t\"MESSAGE_INFORMATION\": \"Meddelandeinformation\",\n\t\"MESSAGE_IS_DELETED\": \"Meddelandet är raderat\",\n\t\"MESSAGE_LIST\": \"Meddelandelista\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} har lagt till ${onName} i gruppen\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} har bannlyst ${onName} från gruppen\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} har gått med i gruppen\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} har sparkat ut ${onName} från gruppen\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} har lämnat gruppen\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} har gjort ${onName} till ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} har upphävt bannlysningen av ${onName}\",\n\t\"MESSAGE_PRIVATELY\": \"Meddelande privat\",\n\t\"MESSAGE_RECEIPT\": \"Meddelandekvitto\",\n\t\"MESSAGE_TRANSLATED\": \"Meddelandet har översatts framgångsrikt.\",\n\t\"MESSAGE_VIDEO\": \"Videoklipp\",\n\t\"MICROPHONE_PERMISSION\": \"För att spela in ett röstmeddelande behöver vi tillgång till din mikrofon. Tryck på Inställningar och slå på Mikrofon.\",\n\t\"MISSED_AUDIO_CALL\": \"Missat ljudsamtal\",\n\t\"MISSED_CALL\": \"Missat samtal\",\n\t\"MISSED_VIDEO_CALL\": \"Missat videosamtal\",\n\t\"MISSED_VOICE_CALL\": \"Missat röstsamtal\",\n\t\"MODERATOR\": \"presentatör\",\n\t\"MONDAY\": \"måndag\",\n\t\"MORE\": \"Mer\",\n\t\"MORE_TIMES\": \"Fler gånger\",\n\t\"NAME\": \"Namn\",\n\t\"NEW_CHAT\": \"Ny chatt\",\n\t\"NEW_MESSAGE\": \"nytt meddelande\",\n\t\"NEW_MESSAGES\": \"nya meddelanden\",\n\t\"NEW__GROUP\": \"Ny grupp\",\n\t\"NO\": \"Nej\",\n\t\"NOTIFICATIONS\": \"Meddelanden\",\n\t\"NOT_SUPPORTED\": \"Den här meddelandetypen stöds inte\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Inga förbjudna medlemmar hittades\",\n\t\"NO_CALLS_FOUND\": \"Inga samtal hittades\",\n\t\"NO_CALLS_SELECTED\": \"Inga samtal valda\",\n\t\"NO_CALL_LOGS\": \"Inga samtalsloggar ännu\",\n\t\"NO_CHATS_FOUND\": \"Inga chattar hittades\",\n\t\"NO_CHATS_SELECTED\": \"Inga chattar valda\",\n\t\"NO_CONVERSATIONS\": \"Inga konversationer ännu ”;  \",\n\t\"NO_DOCUMENTS\": \"Inga dokument\",\n\t\"NO_GROUPS_AVAILABLE\": \"Inga grupper tillgängliga\",\n\t\"NO_GROUPS_FOUND\": \"Inga grupper hittades\",\n\t\"NO_GROUPS_SELECTED\": \"Inga grupper valda\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Ingen gruppmedlem tillgänglig\",\n\t\"NO_MESSAGES_FOUND\": \"Inga meddelanden hittades\",\n\t\"NO_PHOTOS\": \"Inga foton\",\n\t\"NO_RECIPIENT\": \"Ingen mottagare\",\n\t\"NO_RECIPIENTS\": \"Inga mottagare\",\n\t\"NO_RECORDS_FOUND\": \"Inga poster hittades\",\n\t\"NO_REPLIES_FOUND\": \"Inga svar hittades\",\n\t\"NO_STICKERS_AVAILABLE\": \"Inga klistermärken tillgängliga\",\n\t\"NO_STICKERS_FOUND\": \"Inga klistermärken hittades\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Inga förslag tillgängliga\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Ingen sammanfattning tillgänglig\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Inga tidsluckor tillgängliga\",\n\t\"NO_USERS_AVAILABLE\": \"Inga användare tillgängliga\",\n\t\"NO_USERS_FOUND\": \"Inga användare hittades\",\n\t\"NO_USERS_SELECTED\": \"Inga användare valda\",\n\t\"NO_VIDEOS\": \"Inga videor\",\n\t\"NO_VOTE\": \"Ingen röst\",\n\t\"OBJECTS\": \"Objekt\",\n\t\"OFFLINE\": \"Offline\",\n\t\"ONGOING_AUDIO_CALL\": \"Pågående ljudsamtal\",\n\t\"ONGOING_CALL\": \"Pågående utlysning\",\n\t\"ONGOING_VIDEO_CALL\": \"Pågående videosamtal\",\n\t\"ONLINE\": \"Uppkopplad\",\n\t\"ON_ANOTHER_CALL\": \"är på ett annat samtal\",\n\t\"OOPS\": \"HOPPSAN\",\n\t\"OOPS!\": \"HOPPSAN!\",\n\t\"OPEN_DOCUMENT\": \"Öppna dokument\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Öppna dokument för att rita tillsammans\",\n\t\"OPEN_WHITEBOARD\": \"Öppna whiteboard\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Öppna whiteboard för att rita tillsammans\",\n\t\"OPTIONS\": \"Alternativ\",\n\t\"OTHER\": \"Övrigt\",\n\t\"OTHERS\": \"andra\",\n\t\"OUTGOING_AUDIO_CALL\": \"Utgående ljudsamtal\",\n\t\"OUTGOING_CALL\": \"Utgående samtal\",\n\t\"OUTGOING_VIDEO_CALL\": \"Utgående videosamtal\",\n\t\"OWNER\": \"Ägare\",\n\t\"PARTICIPANT\": \"Deltagare\",\n\t\"PARTICIPANTS\": \"Deltagare\",\n\t\"PASSWORD\": \"Lösenord\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Gruppens lösenord är felaktigt!\",\n\t\"PASSWORD_PROTECTED\": \"Lösenordsskyddad\",\n\t\"PHOTOS\": \"Foton\",\n\t\"PICK_YOUR_EMOJI\": \"Välj din emoji\",\n\t\"POLLS\": \"Omröstningar\",\n\t\"PREFERENCES\": \"Inställningar\",\n\t\"PRIVACY\": \"Integritet\",\n\t\"PRIVACY_AND_SECURITY\": \"Sekretess och säkerhet\",\n\t\"PRIVATE\": \"Privat\",\n\t\"PRIVATE_GROUP\": \"Privat grupp\",\n\t\"PROTECTED_GROUP\": \"Skyddad grupp\",\n\t\"PUBLIC\": \"Offentlig\",\n\t\"QUESTION\": \"Fråga\",\n\t\"QUESTIONS\": \"Frågor\",\n\t\"REACHED_MAX_LIMIT\": \"Du har nått gränsen. Du kan lägga till upp till 12 alternativ.\",\n\t\"REACT\": \"Reagera\",\n\t\"REACTED\": \"reagerade\",\n\t\"REACT_TO_MESSAGE\": \"Reagera på ett meddelande\",\n\t\"READ\": \"Läsa\",\n\t\"READ_MORE\": \"Läs mer\",\n\t\"RECEIPT_INFORMATION\": \"Kvittoinformation\",\n\t\"RECORDING\": \"Inspelning\",\n\t\"REGION\": \"Region\",\n\t\"REJECTED_AUDIO_CALL\": \"Avvisat ljudsamtal\",\n\t\"REJECTED_CALL\": \"avvisat samtal\",\n\t\"REJECTED_VIDEO_CALL\": \"Avvisat videosamtal\",\n\t\"REMOVE\": \"Ta bort\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Är du säker på att du vill ta bort\",\n\t\"REPLIES\": \"svar\",\n\t\"REPLY\": \"svar\",\n\t\"REPLY_IN_THREAD\": \"Svara i tråd\",\n\t\"REPLY_TO_THREAD\": \"Svara på tråden\",\n\t\"REPORT_PROBLEM\": \"Rapportera ett problem\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Fyll i alla obligatoriska fält innan du skapar en omröstning\",\n\t\"RESIZE\": \"Ändra storlek\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Den här funktionen kräver specifika resursbehörigheter. Aktivera nödvändiga behörigheter i dina enhetsinställningar\",\n\t\"RETRY\": \"Försök igen\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Valt språk för översättning liknar språket i originalmeddelandet\",\n\t\"SATURDAY\": \"Lördag\",\n\t\"SAVE\": \"Spara\",\n\t\"SCHEDULE\": \"Schema\",\n\t\"SCOPE\": \"Omfattning\",\n\t\"SCOPE_CHANGE_INFO\": \"Du kan ändra roller för att hantera gruppbehörigheter och ansvar\",\n\t\"SEARCH\": \"Sök\",\n\t\"SEARCH_EMOJI\": \"Sök emoji\",\n\t\"SEEN\": \"Sett\",\n\t\"SELECT_A_DATE\": \"Välj ett datum\",\n\t\"SELECT_DAY\": \"Välj en dag\",\n\t\"SELECT_GROUP_TYPE\": \"Välj grupptyp\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Välj ingångsljudkälla\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Välj ljudkälla för utgång\",\n\t\"SELECT_TIME\": \"Välj en tid\",\n\t\"SELECT_VIDEO_SOURCE\": \"Välj videokälla\",\n\t\"SELECT__GROUP\": \"Välj en grupp för att starta meddelanden\",\n\t\"SELECT__USER\": \"Välj en användare för att starta meddelanden\",\n\t\"SEND\": \"Skicka\",\n\t\"SEND_MESSAGE\": \"Skicka meddelande\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Skicka meddelande privat\",\n\t\"SENT\": \"Skickat\",\n\t\"SETTINGS\": \"Inställningar\",\n\t\"SET_THE_ANSWERS\": \"STÄLL IN SVAREN\",\n\t\"SHARE\": \"Dela\",\n\t\"SHARED\": \"Delad\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"har delat ett samarbetsdokument\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"har delat en kollaborativ whiteboard\",\n\t\"SHARED_FILE\": \"Delad fil\",\n\t\"SHARED_LOCATION\": \"Delad plats\",\n\t\"SHARED_MEDIA\": \"Delade medier\",\n\t\"SHOW_LESS\": \"Visa mindre\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Är du säker på att du vill se osäkert innehåll?\",\n\t\"SMART_REPLIES\": \"Smarta svar\",\n\t\"SMILEY_PEOPLE\": \"Smileys och människor\",\n\t\"SOMETHING_WENT_WRONG\": \"Det verkar som att något gick fel.\",\n\t\"SOMETHING_WRONG\": \"Något gick fel. Vänligen försök igen.\",\n\t\"SOUND_MANAGER\": \"Ljudhanterare\",\n\t\"STATUS_INDICATOR\": \"Statusindikator\",\n\t\"STICKER\": \"Klistermärke\",\n\t\"SUGGEST_A_REPLY\": \"Föreslå ett svar\",\n\t\"SUNDAY\": \"söndag\",\n\t\"SURE_TO_DELETE_CHAT\": \"Är du säker på att du vill ta bort den här chatten? Den här åtgärden kan inte ångras.\",\n\t\"SYMBOLS\": \"Symboler\",\n\t\"TAP_TO_REMOVE\": \"Tryck för att ta bort\",\n\t\"TAP_TO_START_CONVERSATION\": \"Tryck för att starta konversationen\",\n\t\"TEXT\": \"Texten\",\n\t\"TEXT_TRANSLATE\": \"Text Översätt\",\n\t\"TEXT_TRANSLATED\": \"Text översatt\",\n\t\"THEME\": \"Tema\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Det här meddelandet raderades\",\n\t\"THREAD\": \"Tråd\",\n\t\"THURSDAY\": \"torsdag\",\n\t\"TIME\": \"tid\",\n\t\"TIMES\": \"gånger\",\n\t\"TIME_ZONE\": \"Tidszon\",\n\t\"TODAY\": \"I dag\",\n\t\"TRANSFER\": \"Överföring\",\n\t\"TRANSFERRING\": \"Överföring\",\n\t\"TRANSFER_CONFIRM\": \"Du är gruppens ägare; överför äganderätten till en medlem innan du lämnar gruppen\",\n\t\"TRANSFER_OWNERSHIP\": \"Överför äganderätten\",\n\t\"TRANSFER_SURE\": \"Är du säker på att du vill överföra äganderätten? Detta kan inte ångras, och den nya ägaren kommer att ta full kontroll.\",\n\t\"TRANSLATE\": \"Översätt\",\n\t\"TRANSLATED_MESSAGE\": \"Översatt meddelande\",\n\t\"TRANSLATE_MESSAGE\": \"Översätt meddelande\",\n\t\"TRAVEL_PLACES\": \"Resor & Platser\",\n\t\"TRY_AGAIN\": \"Försök igen\",\n\t\"TUESDAY\": \"tisdag\",\n\t\"TYPE\": \"Typ\",\n\t\"TYPING\": \"Skriver...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Obesvarat ljudsamtal\",\n\t\"UNANSWERED_CALL\": \"Obesvarat samtal\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Obesvarat videosamtal\",\n\t\"UNBAN\": \"Häv förbud\",\n\t\"UNBANNED\": \"oförbjudet\",\n\t\"UNBAN_SURE\": \"Är du säker på att du vill ta bort blockeringen av denna användare?\",\n\t\"UNBLOCK\": \"Avblockera\",\n\t\"UNBLOCK_CONTACT\": \"Avblockera denna kontakt\",\n\t\"UNBLOCK_SURE\": \"Är du säker på att du vill avblockera denna kontakt? Du kommer att börja få meddelanden från dem igen.\",\n\t\"UNBLOCK_USER\": \"Avblockera användare\",\n\t\"USERS\": \"Användare\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Vi kunde inte hitta några användare som matchade din sökning. Försök att justera din sökning.\",\n\t\"USERS_WITH_MESSAGES\": \"Användare med meddelanden\",\n\t\"USER_INFO\": \"Användarinformation\",\n\t\"USER_LIST\": \"Användarlista\",\n\t\"VIDEOS\": \"Videor\",\n\t\"VIDEO_CALL\": \"Videosamtal\",\n\t\"VIEW\": \"Se\",\n\t\"VIEW_DETAIL\": \"Visa detalj\",\n\t\"VIEW_MEMBERS\": \"Visa medlemmar\",\n\t\"VIEW_ON_YOUTUBE\": \"Visa på Youtube\",\n\t\"VIEW_PROFILE\": \"Visa profil\",\n\t\"VISIT\": \"Besök\",\n\t\"VOICE\": \"Röst\",\n\t\"VOICE_CALL\": \"Röstsamtal\",\n\t\"VOICE_RECORDING\": \"Röstinspelning\",\n\t\"VOTE\": \"rösta\",\n\t\"VOTES\": \"röster\",\n\t\"WEDNESDAY\": \"onsdag\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Vill du ta bort den här konversationen? Den här konversationen kommer att raderas från alla dina enheter.\",\n\t\"WRONG_FILE_TYPE\": \"Du har valt en annan typ av fil. Välj lämplig fil.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Denna filtyp är inte tillåten.\",\n\t\"WRONG_PASSWORD\": \"Ange rätt lösenord och försök igen\",\n\t\"WRONG_TEXT\": \"Det verkar som att något gick fel.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Vänligen försök igen.\",\n\t\"YES\": \"Javisst\",\n\t\"YESTERDAY\": \"Igår\",\n\t\"YOU\": \"Du\",\n\t\"YOU'VE_BLOCKED\": \"Du har blockerat\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Du är redan i ett pågående samtal\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Du raderade detta meddelande\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Du har inga klistermärken ännu.\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Du har initierat ett ljudsamtal\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Du har initierat ett videosamtal\",\n\t\"meeting\": \"Möte\",\n\t\"Flag_Message_Title\": \"Rapportera meddelande\",\n\t\"Flag_Message_Subtitle\": \"Rapportera den här chatten om den bryter mot våra gemenskapsstandarder. Vi meddelar inte kontot som du rapporterade.\",\n\t\"Flag_Message_Remark_Label\": \"Orsak\",\n\t\"Flag_Message_Remark_Optional\": \"Valfritt\",\n\t\"Flag_Message_Remark_Placeholder\": \"Ge ytterligare kontext för din rapport…\",\n\t\"Flag_Message_Confirm_Yes\": \"Rapportera\",\n\t\"Flag_Message_Confirm_No\": \"Avbryt\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / oönskat innehåll\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Explicit eller olämpligt innehåll\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Förolämpande eller hotfullt beteende\",\n\t\"Message_List_Option_Flag_Message\": \"Rapportera\",\n\t\"Flag_Error_Text\":\"Något gick fel. Kontrollera och försök igen.\",\n\t\"Flag_Empty_Submit_Text\":\"Kan inte skicka. Vänligen välj en anledning innan du rapporterar detta meddelande.\",\n\t\"START_REPORTING\": \"Starta rapportering\",\n\t\"LOGOUT\": \"Logga ut\",\n\t\"AI_ASSISTANTS\": \"AI-assistenter\",\n\t\"CREATE_CONVERSATION\": \"Skapa konversation\",\n\t\"EDIT_MODERATION\": \"Redigering misslyckades. Ditt meddelande blockerades på grund av modereringspolicyer.\",\n\t\"BLOCKED_MODERATION\": \"Ditt meddelande blockerades på grund av modereringspolicyer.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Ingen konversationshistorik hittades\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Något gick fel hos oss. Försök igen.\",\n\t\"START_A_CHAT\": \"Starta en chatt genom att trycka på knappen 'Ny chatt'\",\n\t\"CHAT_HISTORY\": \"Chattens historik\",\n\t\"ASK_ANYTHING\": \"Fråga vad som helst...\",\n\t\"AI_ASSISTANT\": \"AI-assistent\",\n\t\"SEARCH_PLACEHOLDER\": \"Sök...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Inga resultat\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Börja skriva för att söka efter meddelanden och konversationer\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Inga resultat hittades\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Vi kunde inte hitta några matchningar. Prova ett annat sökord.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Fel vid inläsning av konversationer\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Fel vid inläsning av meddelanden\",\n\t\"SEARCH_TRY_AGAIN\": \"Försök igen senare\",\n\t\"SEARCH_SEE_MORE\": \"Visa mer\",\n\t\"SEARCH_LOADING\": \"Laddar...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Olästa\",\n\t\"SEARCH_FILTER_GROUPS\": \"Grupper\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Foton\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Videor\",\n\t\"SEARCH_FILTER_LINKS\": \"Länkar\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Dokument\",\n\t\"SEARCH_FILTER_AUDIO\": \"Ljud\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Konversationer\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Meddelanden\",\n\t\"SEARCH_NO_MESSAGES\": \"Inga meddelanden\",\n\t\"MESSAGE_REPORTED\": \"Meddelandet har rapporterats\",\n\t\"MARK_AS_UNREAD\": \"Markera oläst\",\n\t\"NEW\": \"Ny\",\n\t\"INSERT_LINK\": \"Infoga länk\",\n\t\"EDIT_LINK\": \"Redigera länk\",\n\t\"LINK_TEXT\": \"Länktext\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Infoga\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Du kan inte skicka meddelande till denna grupp eftersom du inte längre är medlem.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/tr/translation.json",
    "content": "{\n\t\"INFO\": \"Bilgi\",\n\t\"REACT\": \"Tepki Ver\",\n\t\"EDIT\": \"Düzenle\",\n\t\"MESSAGE_PRIVATELY\": \"Özel Mesaj Gönder\",\n\t\"TRANSLATE\": \"Çevir\",\n\t\"USERS\": \"Kullanıcılar\",\n\t\"CHATS\": \"Sohbetler\",\n\t\"SEARCH_EMOJI\": \"Emoji ara\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"Bu konuşmayı silmek istiyor musunuz? Bu konuşma tüm cihazlarınızdan silinecektir.\",\n\t\"GROUPS\": \"Gruplar\",\n\t\"SHARED\": \"Paylaşılan\",\n\t\"SOUND_MANAGER\": \"Ses Yöneticisi\",\n\t\"THEME\": \"Tema\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"Hepsi\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"Bu gruptaki herkesi bildir\",\n\t\"DELETE_MSG_TEXT\": \"Bu mesaj silindi\",\n\t\"LOCALIZE\": \"Yerelleştir\",\n\t\"CONVERSATION_LIST_ITEM\": \"Konuşma Listesi Öğesi\",\n\t\"DATA_ITEM\": \"Veri Öğesi\",\n\t\"STATUS_INDICATOR\": \"Durum Göstergesi\",\n\t\"BADGE_COUNT\": \"Rozet Sayısı\",\n\t\"MESSAGE_RECEIPT\": \"Mesaj Alındı Bilgisi\",\n\t\"MESSAGE\": \"Mesaj\",\n\t\"RECEIPT_INFORMATION\": \"Alındı Bilgisi\",\n\t\"NO_RECIPIENT\": \"Alıcı Yok\",\n\t\"NO_RECIPIENTS\": \"Alıcılar Yok\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"Mesajlı Konuşmalar\",\n\t\"CONVERSATIONS\": \"Konuşmalar\",\n\t\"CONVERSATION_LIST\": \"Konuşma Listesi\",\n\t\"MESSAGES\": \"Mesajlar\",\n\t\"WRONG_FILE_TYPE\": \"Farklı bir dosya türü seçtiniz. Lütfen uygun dosyayı seçin.\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"Bu dosya türüne izin verilmiyor.\",\n\t\"MESSAGE_HEADER\": \"Mesaj Başlığı\",\n\t\"MESSAGE_LIST\": \"Mesaj Listesi\",\n\t\"MESSAGE_COMPOSER\": \"Mesaj Oluşturucu\",\n\t\"USERS_WITH_MESSAGES\": \"Mesajlı Kullanıcılar\",\n\t\"USER_LIST\": \"Kullanıcı Listesi\",\n\t\"GROUP_LIST\": \"Grup Listesi\",\n\t\"GROUPS_WITH_MESSAGES\": \"Mesajlı Gruplar\",\n\t\"NEW__GROUP\": \"Yeni Grup\",\n\t\"PASSWORD\": \"Şifre\",\n\t\"CONTINUE\": \"Devam Et\",\n\t\"NO_CHATS_SELECTED\": \"Seçili Sohbet Yok\",\n\t\"NO_USERS_SELECTED\": \"Seçili Kullanıcı Yok\",\n\t\"NO_GROUPS_SELECTED\": \"Seçili Grup Yok\",\n\t\"SELECT_DAY\": \"Bir Gün Seçin\",\n\t\"SELECT_TIME\": \"Bir Saat Seçin\",\n\t\"NO_CALLS_SELECTED\": \"Seçili Arama Yok\",\n\t\"SELECT__GROUP\": \"Mesajlaşmaya başlamak için bir grup seçin\",\n\t\"SELECT__USER\": \"Mesajlaşmaya başlamak için bir kullanıcı seçin\",\n\t\"GROUP_PASSWORD_BLANK\": \"Grup şifresi boş olamaz\",\n\t\"GROUP_NAME_BLANK\": \"Grup adı boş olamaz\",\n\t\"GROUP_TYPE_BLANK\": \"Grup türü boş olamaz\",\n\t\"DELETE_CONVERSATION\": \"Konuşmayı Sil?\",\n\t\"ADD_TO_CHAT\": \"Sohbete Ekle\",\n\t\"MORE\": \"Daha Fazla\",\n\t\"COPY\": \"Kopyala\",\n\t\"VOICE_RECORDING\": \"Ses Kaydı\",\n\t\"MESSAGE_IMAGE\": \"Resim\",\n\t\"MESSAGE_FILE\": \"Dosya\",\n\t\"MESSAGE_VIDEO\": \"Video\",\n\t\"MESSAGE_AUDIO\": \"Ses\",\n\t\"CUSTOM_MESSAGE\": \"Bir mesajınız var\",\n\t\"SELECT_A_DATE\": \"Bir tarih seçin\",\n\t\"TIME_ZONE\": \"Saat Dilimi\",\n\t\"MEETING_SCHEDULED\": \"Toplantınız planlandı.\",\n\t\"SOMETHING_WRONG\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\",\n\t\"MEETING_SLOT_BOOK\": \"Zaman dilimi artık kullanılamıyor. Lütfen başka bir tane seçin.\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"Yeni Dilim Ayır\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"Bu tarih için kullanılabilir zaman dilimi yok. Lütfen başka bir tarih deneyin.\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"Lütfen farklı bir tarih deneyin.\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"Kullanılabilir zaman dilimi yok\",\n\t\"SCHEDULE\": \"Planla\",\n\t\"MORE_TIMES\": \"Daha fazla zaman\",\n\t\"MISSED_VOICE_CALL\": \"Cevapsız sesli arama\",\n\t\"MEET_WITH\": \"İle Buluş\",\n\t\"CONVERSATION_DELETED\": \"Konuşma Silindi\",\n\t\"MEETING_SCHEDULER\": \"Toplantı Planlayıcı\",\n\t\"MISSED_VIDEO_CALL\": \"Cevapsız görüntülü arama\",\n\t\"CUSTOM_MESSAGE_POLL\": \"Anket\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"Çıkartma\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"Belge\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"Beyaz Tahta\",\n\t\"ONLINE\": \"Çevrimiçi\",\n\t\"ADMINISTRATOR\": \"Yönetici\",\n\t\"ADMIN\": \"Yönetici\",\n\t\"MODERATOR\": \"Moderatör\",\n\t\"PARTICIPANT\": \"Katılımcı\",\n\t\"PUBLIC\": \"Herkese Açık\",\n\t\"PRIVATE\": \"Özel\",\n\t\"PASSWORD_PROTECTED\": \"Şifre Korumalı\",\n\t\"PRIVACY_AND_SECURITY\": \"Gizlilik ve Güvenlik\",\n\t\"PREFERENCES\": \"Tercihler\",\n\t\"MEMBERS\": \"Üyeler\",\n\t\"MEMBER\": \"Üye\",\n\t\"EDITED\": \"Düzenlendi\",\n\t\"TODAY\": \"Bugün\",\n\t\"YESTERDAY\": \"Dün\",\n\t\"SUNDAY\": \"Pazar\",\n\t\"MONDAY\": \"Pazartesi\",\n\t\"TUESDAY\": \"Salı\",\n\t\"WEDNESDAY\": \"Çarşamba\",\n\t\"THURSDAY\": \"Perşembe\",\n\t\"FRIDAY\": \"Cuma\",\n\t\"SATURDAY\": \"Cumartesi\",\n\t\"TYPING\": \"Yazıyor...\",\n\t\"IS_TYPING\": \"yazıyor...\",\n\t\"CLOSE\": \"Kapat\",\n\t\"ENTER_GROUP_NAME\": \"Grup adı girin\",\n\t\"ADD_MEMBERS\": \"Üye Ekle\",\n\t\"SEND_MESSAGE\": \"Mesaj Gönder\",\n\t\"UNBLOCK_USER\": \"Kullanıcının Engelini Kaldır\",\n\t\"BLOCK_USER\": \"Kullanıcıyı Engelle\",\n\t\"DELETE_AND_EXIT\": \"Sil ve Çık\",\n\t\"LEAVE_GROUP\": \"Gruptan Ayrıl\",\n\t\"CREATE_GROUP\": \"Grup Oluştur\",\n\t\"SHARED_MEDIA\": \"Paylaşılan Medya\",\n\t\"SHARED_FILE\": \"Paylaşılan Dosya\",\n\t\"VIDEO_CALL\": \"Görüntülü arama\",\n\t\"AUDIO_CALL\": \"Sesli arama\",\n\t\"LOADING\": \"Yükleniyor...\",\n\t\"REPLY\": \"Yanıtla\",\n\t\"REPLIES\": \"Yanıtlar\",\n\t\"AI\": \"Yapay Zeka\",\n\t\"SMART_REPLIES\": \"Akıllı Yanıtlar\",\n\t\"CONVERSATION_STARTER\": \"Konuşma Başlatıcı\",\n\t\"LAUNCH\": \"Başlat\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"işbirlikçi bir belge paylaştı\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"işbirlikçi bir beyaz tahta paylaştı\",\n\t\"CREATED_WHITEBOARD\": \"Yeni bir işbirlikçi beyaz tahta oluşturdunuz\",\n\t\"CREATED_DOCUMENT\": \"Yeni bir işbirlikçi belge oluşturdunuz\",\n\t\"PHOTOS\": \"Fotoğraflar\",\n\t\"VIDEOS\": \"Videolar\",\n\t\"DOCUMENT\": \"Belge\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ Bu mesajı sildiniz\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ Bu mesaj silindi\",\n\t\"MESSAGE_IS_DELETED\": \"Mesaj silindi\",\n\t\"MESSAGE_COPIED\": \"Mesaj panoya kopyalandı.\",\n\t\"MESSAGE_EDITED\": \"Mesaj başarıyla güncellendi.\",\n\t\"MESSAGE_DELETED_TEXT\": \"Mesaj başarıyla silindi.\",\n\t\"MESSAGE_TRANSLATED\": \"Mesaj başarıyla çevrildi.\",\n\t\"VIEW_ON_YOUTUBE\": \"YouTube'da Görüntüle\",\n\t\"SEARCH\": \"Ara\",\n\t\"ERROR\": \"Hata\",\n\t\"ERROR_TEXT\": \"Bir şeyler yanlış gitti gibi görünüyor. Lütfen tekrar deneyin.\",\n\t\"NO_GROUPS_FOUND\": \"Grup bulunamadı\",\n\t\"NO_CHATS_FOUND\": \"Sohbet bulunamadı\",\n\t\"CANT__LOAD__CHATS\": \"Sohbetler yüklenemiyor\",\n\t\"MEDIA_MESSAGE\": \"Medya mesajı\",\n\t\"INCOMING_AUDIO_CALL\": \"Gelen sesli arama\",\n\t\"INCOMING_VIDEO_CALL\": \"Gelen görüntülü arama\",\n\t\"DECLINE\": \"Reddet\",\n\t\"ACCEPT\": \"Kabul Et\",\n\t\"INCOMING_CALL\": \"Gelen Arama\",\n\t\"OUTGOING_CALL\": \"Giden Arama\",\n\t\"CALL_REJECTED\": \"Arama Reddedildi\",\n\t\"CALL_ANSWERED\": \"Arama Cevaplandı\",\n\t\"CALL_CANCELLED\": \"Arama İptal Edildi\",\n\t\"MISSED_CALL\": \"Cevapsız Arama\",\n\t\"CALL_UNANSWERED\": \"Cevaplanmamış Arama\",\n\t\"CALL_INITIATED\": \"Arama başlatıldı\",\n\t\"OUTGOING_AUDIO_CALL\": \"Giden sesli arama\",\n\t\"OUTGOING_VIDEO_CALL\": \"Giden görüntülü arama\",\n\t\"REJECTED_CALL\": \"reddedilen arama\",\n\t\"CALL_ACCEPTED\": \"Arama kabul edildi\",\n\t\"JOINED\": \"katıldı\",\n\t\"LEFT_THE_CALL\": \"aramadan ayrıldı\",\n\t\"UNANSWERED_AUDIO_CALL\": \"Cevaplanmamış sesli arama\",\n\t\"UNANSWERED_VIDEO_CALL\": \"Cevaplanmamış görüntülü arama\",\n\t\"CALL_ENDED\": \"Arama Sona Erdi\",\n\t\"CALL_BUSY\": \"Arama Meşgul\",\n\t\"CALLING\": \"Aranıyor...\",\n\t\"ADD\": \"Ekle\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"Yasaklı üye bulunamadı\",\n\t\"BANNED_MEMBERS\": \"Yasaklı Üyeler\",\n\t\"NAME\": \"İsim\",\n\t\"SCOPE\": \"Kapsam\",\n\t\"UNBAN\": \"Yasağı Kaldır\",\n\t\"SELECT_GROUP_TYPE\": \"Grup türü seç\",\n\t\"ENTER_GROUP_PASSWORD\": \"Grup şifresi gir\",\n\t\"CREATE\": \"Oluştur\",\n\t\"CREATE_POLL\": \"Anket\",\n\t\"QUESTION\": \"Soru\",\n\t\"ENTER_YOUR_QUESTION\": \"Sorunuzu girin\",\n\t\"OPTIONS\": \"Seçenekler\",\n\t\"ENTER_YOUR_OPTION\": \"Seçeneğinizi girin\",\n\t\"ADD_NEW_OPTION\": \"Yeni seçenek ekle\",\n\t\"VIEW_MEMBERS\": \"Üyeleri Görüntüle\",\n\t\"DETAILS\": \"Detaylar\",\n\t\"NOTIFICATIONS\": \"Bildirimler\",\n\t\"OTHER\": \"Diğer\",\n\t\"HELP\": \"Yardım\",\n\t\"REPORT_PROBLEM\": \"Sorun Bildir\",\n\t\"GROUP_MEMBERS\": \"Grup Üyeleri\",\n\t\"BAN\": \"Yasakla\",\n\t\"KICK\": \"At\",\n\t\"PICK_YOUR_EMOJI\": \"Emojinizi seçin\",\n\t\"PRIVATE_GROUP\": \"Özel Grup\",\n\t\"PROTECTED_GROUP\": \"Korumalı Grup\",\n\t\"VISIT\": \"Ziyaret Et\",\n\t\"ATTACH\": \"Ekle\",\n\t\"OPEN_WHITEBOARD\": \"Beyaz Tahta Aç\",\n\t\"ATTACH_FILE\": \"Dosya ekle\",\n\t\"GENERATING_REPLIES\": \"Yanıtlar oluşturuluyor\",\n\t\"ATTACH_VIDEO\": \"Video ekle\",\n\t\"ATTACH_AUDIO\": \"Ses ekle\",\n\t\"ATTACH_IMAGE\": \"Resim ekle\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"Belge kullanarak işbirliği yap\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"Beyaz tahta kullanarak işbirliği yap\",\n\t\"SUGGEST_A_REPLY\": \"Bir yanıt öner\",\n\t\"GENERATING_ICEBREAKERS\": \"Buz kırıcılar oluşturuluyor\",\n\t\"EMOJI\": \"Emoji\",\n\t\"NO_REPLIES_FOUND\": \"Yanıt Bulunamadı\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"Mesajınızı buraya girin\",\n\t\"NO_MESSAGES_FOUND\": \"Mesaj bulunamadı\",\n\t\"THREAD\": \"İş Parçacığı\",\n\t\"COLLABORATIVE_DOCUMENT\": \"İşbirlikçi Belge\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"İşbirlikçi Beyaz Tahta\",\n\t\"ADD_REACTION\": \"Tepki ekle\",\n\t\"NO_STICKERS_FOUND\": \"Çıkartma bulunamadı\",\n\t\"REPLY_TO_THREAD\": \"İş parçacığını yanıtla\",\n\t\"REPLY_IN_THREAD\": \"İş parçacığında yanıtla\",\n\t\"VIEW\": \"Görüntüle\",\n\t\"DELETE_MESSAGE\": \"Mesajı sil\",\n\t\"EDIT_MESSAGE\": \"Mesajı düzenle\",\n\t\"OWNER\": \"Sahip\",\n\t\"CHANGE_SCOPE\": \"Kapsamı Değiştir\",\n\t\"STICKER\": \"Çıkartma\",\n\t\"LAST_ACTIVE_AT\": \"Son Aktif Olma Zamanı\",\n\t\"LAST_SEEN\": \"Son görülme\",\n\t\"AT\": \"saat\",\n\t\"VOICE_CALL\": \"Sesli arama\",\n\t\"VIEW_DETAIL\": \"Ayrıntıyı Görüntüle\",\n\t\"VOTES\": \"oy\",\n\t\"VOTE\": \"oy\",\n\t\"NO_VOTE\": \"Oy yok\",\n\t\"REACTED\": \"tepki verdi\",\n\t\"ADDED\": \"eklendi\",\n\t\"SHOW_UNSAFE_CONTENT\": \"Güvenli olmayan içeriği görmek istediğinizden emin misiniz?\",\n\t\"REACT_TO_MESSAGE\": \"Mesaja tepki ver\",\n\t\"UNBANNED\": \"yasağı kaldırıldı\",\n\t\"MADE\": \"yaptı\",\n\t\"MISSED_AUDIO_CALL\": \"Cevapsız sesli arama\",\n\t\"ENTER_YOUR_PASSWORD\": \"Şifrenizi girin\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"İçeriği birlikte düzenlemek için belgeyi açın\",\n\t\"DOCS\": \"Belgeler\",\n\t\"NO_RECORDS_FOUND\": \"Kayıt bulunamadı\",\n\t\"LIVE_REACTION\": \"Canlı Tepki\",\n\t\"SMILEY_PEOPLE\": \"Yüz İfadeleri ve İnsanlar\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"Birlikte çizmek için beyaz tahta açın\",\n\t\"ANIMALES_NATURE\": \"Hayvanlar ve Doğa\",\n\t\"FOOD_DRINK\": \"Yiyecek ve İçecek\",\n\t\"OPEN_DOCUMENT\": \"Belge Aç\",\n\t\"ACTIVITY\": \"Aktivite\",\n\t\"TRAVEL_PLACES\": \"Seyahat ve Yerler\",\n\t\"OBJECTS\": \"Nesneler\",\n\t\"SYMBOLS\": \"Semboller\",\n\t\"FLAGS\": \"Bayraklar\",\n\t\"SENT\": \"Gönderildi\",\n\t\"SEEN\": \"Görüldü\",\n\t\"DELIVERED\": \"İletildi\",\n\t\"READ\": \"Okundu\",\n\t\"MESSAGE_INFORMATION\": \"Mesaj Bilgisi\",\n\t\"TRANSLATE_MESSAGE\": \"Mesajı çevir\",\n\t\"TRANSLATED_MESSAGE\": \"Çevirilen mesaj\",\n\t\"LEFT\": \"ayrıldı\",\n\t\"KICKED\": \"atıldı\",\n\t\"BANNED\": \"yasaklandı\",\n\t\"NEW_MESSAGES\": \"yeni mesaj\",\n\t\"NEW_MESSAGE\": \"yeni mesaj\",\n\t\"JUMP\": \"Atla\",\n\t\"SELECT_VIDEO_SOURCE\": \"Video kaynağı seç\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"Giriş ses kaynağı seç\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"Çıkış ses kaynağı seç\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"sesli arama başlattı\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"görüntülü arama başlattı\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"Bir sesli arama başlattınız\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"Bir görüntülü arama başlattınız\",\n\t\"IGNORE\": \"Yoksay\",\n\t\"ON_ANOTHER_CALL\": \"başka bir aramada\",\n\t\"CREATING\": \"Oluşturuluyor\",\n\t\"AVATAR\": \"Avatar\",\n\t\"ONGOING_CALL\": \"Devam eden arama\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"Zaten devam eden bir aramada bulunuyorsunuz\",\n\t\"RESIZE\": \"Yeniden Boyutlandır\",\n\t\"READ_MORE\": \"Daha fazla oku\",\n\t\"SHOW_LESS\": \"Daha az göster\",\n\t\"SETTINGS\": \"Ayarlar\",\n\t\"ACTIONS\": \"İşlemler\",\n\t\"VIEW_PROFILE\": \"Profili Görüntüle\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"Özel olarak mesaj gönder\",\n\t\"DELETE\": \"Sil\",\n\t\"DELETE_CONFIRM\": \"Silmek istediğinizden emin misiniz?\",\n\t\"DELETE_CHAT\": \"Bu sohbeti silmek istiyor musunuz?\",\n\t\"SURE_TO_DELETE_CHAT\": \"Bu sohbeti silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.\",\n\t\"CANCEL\": \"İptal\",\n\t\"LEAVE_CONFIRM\": \"Gruptan ayrılmak istediğinizden emin misiniz?\",\n\t\"TRANSFER_CONFIRM\": \"Grup sahibisiniz, lütfen gruptan ayrılmadan önce sahipliği bir üyeye devredin\",\n\t\"ADDING\": \"Ekleniyor...\",\n\t\"TRANSFER\": \"Devret\",\n\t\"TRANSFER_OWNERSHIP\": \"Sahipliği Devret\",\n\t\"TRANSFERRING\": \"Devrediliyor\",\n\t\"YES\": \"Evet\",\n\t\"NO\": \"Hayır\",\n\t\"ANSWER\": \"Yanıt\",\n\t\"ADD_ANOTHER_ANSWER\": \"Başka Yanıt Ekle\",\n\t\"SET_THE_ANSWERS\": \"YANITLARI AYARLA\",\n\t\"TRY_AGAIN\": \"Tekrar deneyin\",\n\t\"INVALID_GROUP_NAME\": \"Lütfen grup için geçerli bir ad girin ve tekrar deneyin\",\n\t\"INVALID_PASSWORD\": \"Lütfen grup için geçerli bir şifre girin ve tekrar deneyin\",\n\t\"INVALID_GROUP_TYPE\": \"Lütfen grup için geçerli bir tür girin ve tekrar deneyin\",\n\t\"WRONG_PASSWORD\": \"Lütfen doğru şifreyi girin ve tekrar deneyin\",\n\t\"INVALID_POLL_QUESTION\": \"Lütfen anket oluşturmadan önce gerekli soruyu girin\",\n\t\"INVALID_POLL_OPTION\": \"Lütfen anket oluşturmadan önce gerekli yanıtı girin\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"Çeviri için seçilen dil, orijinal mesajın diliyle benzerdir\",\n\t\"LEAVE\": \"Ayrıl\",\n\t\"CLICK_TO_START_CONVERSATION\": \"Konuşma başlatmak için tıklayın\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍Konum\",\n\t\"SHARED_LOCATION\": \"Paylaşılan Konum\",\n\t\"IN_A_THREAD\": \"Bir iş parçacığında\",\n\t\"CALLS\": \"Aramalar\",\n\t\"CALL_DETAILS\": \"Arama Detayları\",\n\t\"OFFLINE\": \"Çevrimdışı\",\n\t\"POLLS\": \"Anketler\",\n\t\"YOU\": \"Sen\",\n\t\"PRIVACY\": \"Gizlilik\",\n\t\"BLOCKED_USERS\": \"Engellenen Kullanıcılar\",\n\t\"YOU'VE_BLOCKED\": \"Engellediniz\",\n\t\"NO_PHOTOS\": \"Fotoğraf Yok\",\n\t\"NO_VIDEOS\": \"Video Yok\",\n\t\"NO_DOCUMENTS\": \"Belge Yok\",\n\t\"JOIN\": \"Katıl\",\n\t\"REMOVE\": \"Kaldır\",\n\t\"BLOCK\": \"Engelle\",\n\t\"CHANGE_ROLE\": \"Rol Değiştir\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"Grup izinlerini ve sorumluluklarını yönetmek için rolleri değiştirebilirsiniz.\",\n\t\"CHANGE_TO\": \"Şuna değiştir\",\n\t\"NEW_CHAT\": \"Yeni Sohbet\",\n\t\"FORM_COMPLETION_MESSAGE\": \"Formu doldurduğunuz için teşekkürler.\",\n\t\"HISTORY\": \"Geçmiş\",\n\t\"CANCELLED_AUDIO_CALL\": \"İptal edilen sesli arama\",\n\t\"CANCELLED_VIDEO_CALL\": \"İptal edilen görüntülü arama\",\n\t\"REJECTED_AUDIO_CALL\": \"Reddedilen sesli arama\",\n\t\"REJECTED_VIDEO_CALL\": \"Reddedilen görüntülü arama\",\n\t\"CANCELLED_CALL\": \"İptal edilen arama\",\n\t\"UNANSWERED_CALL\": \"Cevaplanmamış arama\",\n\t\"RECORDING\": \"Kayıt\",\n\t\"PARTICIPANTS\": \"Katılımcılar\",\n\t\"CALL_HISTORY\": \"Arama Geçmişi\",\n\t\"NO_CALLS_FOUND\": \"Arama Bulunamadı\",\n\t\"ONGOING_AUDIO_CALL\": \"Devam eden sesli arama\",\n\t\"ONGOING_VIDEO_CALL\": \"Devam eden görüntülü arama\",\n\t\"CALL_DETAIL\": \"Arama Detayı\",\n\t\"FORM\": \"Form\",\n\t\"CARD\": \"Kart\",\n\t\"GENERATING_SUMMARY\": \"Özet oluşturuluyor\",\n\t\"CONVERSATION_SUMMARY\": \"Konuşma özeti\",\n\t\"GENERATE_SUMMARY\": \"Özet oluştur\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"Bu konuşmada size nasıl yardımcı olabilirim? Lütfen bir soru sorun ve size tavsiyede bulunayım 🙂\",\n\t\"COMETCHAT_ASK_BOT\": \"Sor\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"Yapay Zeka Botlarına Sor\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"Yapay Zeka Botu\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"Bir kerede en fazla 10 kullanıcıyı etiketleyebilirsiniz.\",\n\t\"ALL\": \"Tümü\",\n\t\"CLICK_TO_REMOVE\": \"Kaldırmak için tıklayın\",\n\t\"OTHERS\": \"diğerleri\",\n\t\"AND\": \"ve\",\n\t\"ASK_QUESTION\": \"Soru sor\",\n\t\"ADD_OPTION\": \"Seçenek Ekle\",\n\t\"SEND\": \"Gönder\",\n\t\"FULL_SCREEN_VIEWER\": \"Tam Ekran Görüntüleyici\",\n\t\"TEXT_TRANSLATE\": \"Metin Çeviri\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"Bir şeyler yanlış gitti gibi görünüyor\",\n\t\"NO_SUMMARY_AVAILABLE\": \"Özet Mevcut Değil\",\n\t\"QUESTIONS\": \"Sorular\",\n\t\"NO_STICKERS_AVAILABLE\": \"Çıkartma Mevcut Değil\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"Öneri Mevcut Değil\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"Grupları burada listelemek ve işbirliği yapmaya başlamak için gruplar oluşturun veya gruplara katılın\",\n\t\"NO_USERS_AVAILABLE\": \"Kullanıcı Mevcut Değil\",\n\t\"NO_GROUPS_AVAILABLE\": \"Grup Mevcut Değil\",\n\t\"NO_CALL_LOGS\": \"Henüz Arama Kaydı Yok\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"Arama geçmişinizi burada görmek için arama yapın veya alın\",\n\t\"NO_CONVERSATIONS\": \"Henüz Konuşma Yok\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"Yeni bir sohbet başlatın veya başkalarını konuşmaya katılmaya davet edin.\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"Grup Üyesi Mevcut Değil\",\n\t\"OOPS!\": \"UPS!\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"Alıcıların mesajı almasını ve görüntülemesini bekliyor\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"Kişileri burada listelemek için ekleyin.\",\n\t\"REACHED_MAX_LIMIT\": \"Limite ulaştınız. En fazla 12 seçenek ekleyebilirsiniz.\",\n\t\"REQUIRED_FIELDS_WARNING\": \"Lütfen anket oluşturmadan önce tüm gerekli alanları doldurun.\",\n\t\"NOT_SUPPORTED\": \"Bu mesaj türü desteklenmiyor\",\n\t\"NO_USERS_FOUND\": \"Kullanıcı Bulunamadı\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"Aramanızla eşleşen kullanıcı bulamadık. Aramanızı ayarlamayı deneyin.\",\n\t\"SAVE\": \"Kaydet\",\n\t\"RETRY\": \"Tekrar Dene\",\n\t\"TAP_TO_START_CONVERSATION\": \"Konuşma başlatmak için dokunun\",\n\t\"TEXT_TRANSLATED\": \"Metin Çevrildi\",\n\t\"CAMERA\": \"Kamera\",\n\t\"SHARE\": \"Paylaş\",\n\t\"ATTACH_DOCUMENT\": \"Belge Ekle\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"Kaldırmak istediğinizden emin misiniz \",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"Mesajınızı yazın...\",\n\t\"TAP_TO_REMOVE\": \"Kaldırmak için dokunun\",\n\t\"ADD_CONTACTS\": \"Konuşma başlatmak ve burada listelenmelerini görmek için kişiler ekleyin.\",\n\t\"SCOPE_CHANGE_INFO\": \"Grup izinlerini ve sorumluluklarını yönetmek için rolleri değiştirebilirsiniz\",\n\t\"SOMETHING_WENT_WRONG\": \"Bir şeyler yanlış gitti gibi görünüyor.\",\n\t\"MENTION_UPTO\": \"En fazla\",\n\t\"TIME\": \"kez\",\n\t\"TIMES\": \"kez\",\n\t\"AT_A_TIME\": \"bir kerede\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"Bu özellik belirli kaynak izinleri gerektiriyor. Lütfen cihaz ayarlarınızda gerekli izinleri etkinleştirin.\",\n\t\"MICROPHONE_PERMISSION\": \"Sesli Mesaj kaydetmek için mikrofonunuza erişmemiz gerekiyor. Ayarlar'a dokunun ve Mikrofon'u açın.\",\n\t\"CAMERA_PERMISSION\": \"Kameranıza erişimimiz yok. Erişimi etkinleştirmek için Ayarlar'a dokunun ve Kamera'yı açın.\",\n\t\"OOPS\": \"Ups!\",\n\t\"ADD_OPTIONS\": \"Seçenekler Ekle\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"Birlikte çizmek için belgeyi açın\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"Birlikte çizmek için beyaz tahtayı açın\",\n\t\"TEXT\": \"Metin\",\n\t\"BAN_MEMBER_CONFIRM\": \"Yasaklamak istediğinizden emin misiniz \",\n\t\"LOGIN\": \"Giriş Yap\",\n\t\"CONTINUE_WITH_GOOGLE\": \"Google ile Devam Et\",\n\t\"JOIN_GROUP\": \"Gruba Katıl\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"Grup şifresi yanlış!\",\n\t\"ENTER_PASSWORD\": \"Şifre Gir\",\n\t\"TYPE\": \"Tür\",\n\t\"USER_INFO\": \"Kullanıcı Bilgisi\",\n\t\"DELETE_CHAT_TEXT\": \"Sohbeti Sil\",\n\t\"UNBLOCK\": \"Engeli Kaldır\",\n\t\"UNBLOCK_CONTACT\": \"Bu kişinin engelini kaldır\",\n\t\"BLOCK_CONTACT\": \"Bu kişiyi engellemek istiyor musunuz?\",\n\t\"UNBLOCK_SURE\": \"Bu kişinin engelini kaldırmak istediğinizden emin misiniz? Onlardan tekrar mesaj almaya başlayacaksınız.\",\n\t\"BLOCK_SURE\": \"Bu kişiyi engellemek istediğinizden emin misiniz? Artık onlardan mesaj almayacaksınız.\",\n\t\"VOICE\": \"Ses\",\n\t\"DELETE_AND_EXIT_SURE\": \"Bu sohbeti silmek ve gruptan çıkmak istediğinizden emin misiniz? Bu işlem geri alınamaz.\",\n\t\"TRANSFER_SURE\": \"Sahipliği devretmek istediğinizden emin misiniz? Bu işlem geri alınamaz ve yeni sahip tam kontrol sahibi olacaktır.\",\n\t\"GROUP_INFO\": \"Grup Bilgisi\",\n\t\"LEAVE_GROUP_TEXT\": \"Bu gruptan ayrıl\",\n\t\"LEAVE_SURE\": \"Gruptan ayrılmak istediğinizden emin misiniz? Bu sohbetten daha fazla mesaj almayacaksınız.\",\n\t\"UNBAN_SURE\": \"Bu kullanıcının yasağını kaldırmak istediğinizden emin misiniz?\",\n\t\"DELETE_THIS_CONVERSATION\": \"Bu konuşmayı silmek istiyor musunuz?\",\n\t\"LINK\": \"Bağlantı\",\n\t\"DELETE_THIS_MESSAGE\": \"Bu Mesajı Silmek İstiyor musunuz?\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"Bu mesajı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.\",\n\t\"APP_CREDENTIALS\": \"Uygulama Kimlik Bilgileri\",\n\t\"REGION\": \"Bölge\",\n\t\"CHAT_PRIVATELY\": \"Özel Sohbet\",\n\t\"WRONG_TEXT\": \"Bir şeyler yanlış gitti gibi görünüyor.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"Lütfen tekrar deneyin.\",\n\t\"BLOCKED_USER_DESC\": \"Kullanıcı engellendiği için mesaj gönderilemiyor\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName}, ${onName} kullanıcısını gruba ekledi\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} gruba katıldı\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} gruptan ayrıldı\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName}, ${onName} kullanıcısını gruptan attı\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName}, ${onName} kullanıcısını gruptan yasakladı\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName}, ${onName} kullanıcısının yasağını kaldırdı\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName}, ${onName} kullanıcısını ${role} yaptı\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"Henüz hiç çıkartman yok.\",\n\t\"meeting\": \"Toplantı\",\n\t\"Flag_Message_Title\": \"Mesajı bildir\",\n\t\"Flag_Message_Subtitle\": \"Bu sohbet topluluk standartlarımıza aykırıysa bildir. Bildirdiğin hesabı bildirdiğini onlara söylemeyeceğiz.\",\n\t\"Flag_Message_Remark_Label\": \"Sebep\",\n\t\"Flag_Message_Remark_Optional\": \"İsteğe bağlı\",\n\t\"Flag_Message_Remark_Placeholder\": \"Bildiriminiz için ek bağlam sağlayın…\",\n\t\"Flag_Message_Confirm_Yes\": \"Bildir\",\n\t\"Flag_Message_Confirm_No\": \"İptal\",\n\t\"Flag_Message_Reason_Id_Spam\": \"Spam / İstenmeyen içerik\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"Açık veya uygunsuz içerik\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"Hakaret içeren veya tehdit edici davranış\",\n\t\"Message_List_Option_Flag_Message\": \"Bildir\",\n    \"Flag_Error_Text\":\"Bir şeyler yanlış gitti. Lütfen kontrol edip tekrar deneyin.\",\n\t\"Flag_Empty_Submit_Text\":\"Gönderilemiyor. Lütfen bu mesajı bildirmeden önce bir neden seçin.\",\n\t\"START_REPORTING\": \"Raporlamayı başlat\",\n\t\"LOGOUT\": \"Çıkış Yap\",\n\t\"AI_ASSISTANTS\": \"Yapay Zekâ Asistanları\",\n\t\"CREATE_CONVERSATION\": \"Konuşma oluştur\",\n\t\"EDIT_MODERATION\": \"Düzenleme başarısız oldu. Mesajınız moderasyon politikaları nedeniyle engellendi.\",\n\t\"BLOCKED_MODERATION\": \"Mesajınız moderasyon politikaları nedeniyle engellendi.\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"Konuşma geçmişi bulunamadı.\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"Bizim tarafımızda bir sorun oluştu. Lütfen tekrar deneyin.\",\n\t\"START_A_CHAT\": \"'Yeni Sohbet' butonuna tıklayarak sohbet başlatın.\",\n\t\"CHAT_HISTORY\": \"Sohbet Geçmişi\",\n\t\"ASK_ANYTHING\": \"Herhangi bir şey sorun...\",\n\t\"AI_ASSISTANT\": \"AI Asistanı\",\n\t\"SEARCH_PLACEHOLDER\": \"Ara...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"Sonuç Yok\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"Mesajları ve sohbetleri aramak için yazmaya başlayın\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"Sonuç bulunamadı\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"Eşleşme bulunamadı. Farklı bir anahtar kelime deneyin.\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"Sohbetler yüklenirken hata oluştu\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"Mesajlar yüklenirken hata oluştu\",\n\t\"SEARCH_TRY_AGAIN\": \"Lütfen daha sonra tekrar deneyin\",\n\t\"SEARCH_SEE_MORE\": \"Daha Fazla Gör\",\n\t\"SEARCH_LOADING\": \"Yükleniyor...\",\n\t\"SEARCH_FILTER_UNREAD\": \"Okunmamış\",\n\t\"SEARCH_FILTER_GROUPS\": \"Gruplar\",\n\t\"SEARCH_FILTER_PHOTOS\": \"Fotoğraflar\",\n\t\"SEARCH_FILTER_VIDEOS\": \"Videolar\",\n\t\"SEARCH_FILTER_LINKS\": \"Bağlantılar\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"Belgeler\",\n\t\"SEARCH_FILTER_AUDIO\": \"Ses\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"Sohbetler\",\n\t\"SEARCH_FILTER_MESSAGES\": \"Mesajlar\",\n\t\"SEARCH_NO_MESSAGES\": \"Mesaj yok\",\n\t\"MESSAGE_REPORTED\": \"Mesaj bildirildi\",\n\t\"MARK_AS_UNREAD\": \"Okunmamış işaretle\",\n\t\"NEW\": \"Yeni\",\n\t\"INSERT_LINK\": \"Bağlantı ekle\",\n\t\"EDIT_LINK\": \"Bağlantıyı düzenle\",\n\t\"LINK_TEXT\": \"Bağlantı metni\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"Ekle\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"Artık üye olmadığınız için bu gruba mesaj gönderemezsiniz.\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/zh/translation.json",
    "content": "{\n\t\"ACCEPT\": \"接受\",\n\t\"ACTIONS\": \"行动\",\n\t\"ACTIVITY\": \"活动\",\n\t\"ADD\": \"添加\",\n\t\"ADDED\": \"添加\",\n\t\"ADDING\": \"正在添加...\",\n\t\"ADD_ANOTHER_ANSWER\": \"添加另一个答案\",\n\t\"ADD_CONTACTS\": \" 添加联系人以开始对话并在此处查看列表。\",\n\t\"ADD_MEMBERS\": \"添加成员\",\n\t\"ADD_NEW_OPTION\": \"添加新选项\",\n\t\"ADD_OPTION\": \"添加选项\",\n\t\"ADD_OPTIONS\": \" 添加选项\",\n\t\"ADD_REACTION\": \"添加反应\",\n\t\"ADD_TO_CHAT\": \"加入聊天\",\n\t\"ADMIN\": \"管理员\",\n\t\"ADMINISTRATOR\": \"管理员\",\n\t\"AI\": \"AI\",\n\t\"ALL\": \"全部\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"全部\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"通知此群组中的所有人\",\n\t\"AND\": \"一个\",\n\t\"ANIMALES_NATURE\": \"动物与自然\",\n\t\"ANSWER\": \"回答\",\n\t\"APP_CREDENTIALS\": \"应用凭据\",\n\t\"ASK_QUESTION\": \"问问题\",\n\t\"AT\": \"在\",\n\t\"ATTACH\": \"附上\",\n\t\"ATTACH_AUDIO\": \"附加音频\",\n\t\"ATTACH_DOCUMENT\": \"附加文件\",\n\t\"ATTACH_FILE\": \"附上文件\",\n\t\"ATTACH_IMAGE\": \"附上图片\",\n\t\"ATTACH_VIDEO\": \"附上视频\",\n\t\"AT_A_TIME\": \"一次\",\n\t\"AUDIO_CALL\": \"音频通话\",\n\t\"AVATAR\": \"阿凡达\",\n\t\"BADGE_COUNT\": \"徽章数量\",\n\t\"BAN\": \"禁令\",\n\t\"BANNED\": \"禁止\",\n\t\"BANNED_MEMBERS\": \"被禁止的会员\",\n\t\"BAN_MEMBER_CONFIRM\": \"您确定要禁止 \",\n\t\"BLOCK\": \"阻止\",\n\t\"BLOCKED_USERS\": \"被封锁的用户\",\n\t\"BLOCKED_USER_DESC\": \"无法发送消息，因为用户已被屏蔽\",\n\t\"BLOCK_CONTACT\": \"屏蔽此联系人？\",\n\t\"BLOCK_SURE\": \"您确定要屏蔽此联系人吗？您将不再收到他们的消息。\",\n\t\"BLOCK_USER\": \"屏蔽用户\",\n\t\"CALLING\": \"正在打电话...\",\n\t\"CALLS\": \"通话\",\n\t\"CALL_ACCEPTED\": \"通话已接受\",\n\t\"CALL_ANSWERED\": \"来电已接听\",\n\t\"CALL_BUSY\": \"通话忙碌\",\n\t\"CALL_CANCELLED\": \"通话已取消\",\n\t\"CALL_DETAIL\": \"通话详情\",\n\t\"CALL_DETAILS\": \"通话详情\",\n\t\"CALL_ENDED\": \"通话已结束\",\n\t\"CALL_HISTORY\": \"通话记录\",\n\t\"CALL_INITIATED\": \"呼叫已启动\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"拨打或接听电话，查看此处列出的通话记录\",\n\t\"CALL_REJECTED\": \"呼叫被拒绝\",\n\t\"CALL_UNANSWERED\": \"来电未接听\",\n\t\"CAMERA\": \"相机\",\n\t\"CAMERA_PERMISSION\": \"我们无法访问您的相机。要启用访问权限，请点击 “设置”，然后打开 “相机”。\",\n\t\"CANCEL\": \"取消\",\n\t\"CANCELLED_AUDIO_CALL\": \"取消了语音通话\",\n\t\"CANCELLED_CALL\": \"已取消通话\",\n\t\"CANCELLED_VIDEO_CALL\": \"视频通话已取消\",\n\t\"CANT__LOAD__CHATS\": \"无法加载聊天记录\",\n\t\"CARD\": \"卡片\",\n\t\"CHANGE_ROLE\": \"变更角色\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"您可以更改角色来管理群组权限和责任。\",\n\t\"CHANGE_SCOPE\": \"变更范围\",\n\t\"CHANGE_TO\": \"改为\",\n\t\"CHATS\": \"聊天\",\n\t\"CHAT_PRIVATELY\": \"私下聊天\",\n\t\"CLICK_TO_REMOVE\": \"点击移除\",\n\t\"CLICK_TO_START_CONVERSATION\": \"点击开始对话\",\n\t\"CLOSE\": \"关闭\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"使用文档进行协作\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"使用白板进行协作\",\n\t\"COLLABORATIVE_DOCUMENT\": \"协作文档\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"协作白板\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"向 AI 机器人提问\",\n\t\"COMETCHAT_ASK_BOT\": \"问\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"AI Bot\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"我怎样才能帮助你完成这次对话？请问我一个问题，我会给你建议 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"在这里输入你的信息\",\n\t\"CONTINUE\": \"继续\",\n\t\"CONTINUE_WITH_GOOGLE\": \"使用 Google 继续\",\n\t\"CONVERSATIONS\": \"对话\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"开始新的聊天或邀请其他人加入对话。\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"带消息的对话\",\n\t\"CONVERSATION_DELETED\": \"对话已删除\",\n\t\"CONVERSATION_LIST\": \"对话清单\",\n\t\"CONVERSATION_LIST_ITEM\": \"对话清单项目\",\n\t\"CONVERSATION_STARTER\": \"对话入门\",\n\t\"CONVERSATION_SUMMARY\": \"对话摘要\",\n\t\"COPY\": \"复制\",\n\t\"CREATE\": \"创建\",\n\t\"CREATED_DOCUMENT\": \"你已经创建了一个新的协作文档\",\n\t\"CREATED_WHITEBOARD\": \"你已经创建了一个新的协作白板\",\n\t\"CREATE_GROUP\": \"创建群组\",\n\t\"CREATE_POLL\": \"投票\",\n\t\"CREATING\": \"正在创建\",\n\t\"CUSTOM_MESSAGE\": \"你有一条消息\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"文档\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 位置\",\n\t\"CUSTOM_MESSAGE_POLL\": \"投票\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"贴纸\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"白板\",\n\t\"DATA_ITEM\": \"数据项\",\n\t\"DECLINE\": \"拒绝\",\n\t\"DELETE\": \"删除\",\n\t\"DELETE_AND_EXIT\": \"删除并退出\",\n\t\"DELETE_AND_EXIT_SURE\": \"您确定要删除此聊天并退出群组吗？此操作不可撤销。\",\n\t\"DELETE_CHAT\": \"删除此聊天？\",\n\t\"DELETE_CHAT_TEXT\": \"删除聊天\",\n\t\"DELETE_CONFIRM\": \"你确定要删除吗？\",\n\t\"DELETE_CONVERSATION\": \"删除对话？\",\n\t\"DELETE_MESSAGE\": \"删除留言\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"您确定要删除此消息吗？此操作无法撤销。\",\n\t\"DELETE_MSG_TEXT\": \"此消息已删除\",\n\t\"DELETE_THIS_CONVERSATION\": \" 删除此对话？\",\n\t\"DELETE_THIS_MESSAGE\": \"删除此消息？\",\n\t\"DELIVERED\": \"已交付\",\n\t\"DETAILS\": \"详情\",\n\t\"DOCS\": \"文档\",\n\t\"DOCUMENT\": \"文档\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"打开文档一起编辑内容\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"打开白板一起画画\",\n\t\"EDIT\": \"编辑\",\n\t\"EDITED\": \"已编辑\",\n\t\"EDIT_MESSAGE\": \"编辑消息\",\n\t\"EMOJI\": \"表情符号\",\n\t\"ENTER_GROUP_NAME\": \"输入群组名称\",\n\t\"ENTER_GROUP_PASSWORD\": \"输入群组密码\",\n\t\"ENTER_PASSWORD\": \"输入密码\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"在这里输入你的信息\",\n\t\"ENTER_YOUR_OPTION\": \"输入您的选项\",\n\t\"ENTER_YOUR_PASSWORD\": \"输入你的密码\",\n\t\"ENTER_YOUR_QUESTION\": \"输入你的问题\",\n\t\"ERROR\": \"错误\",\n\t\"ERROR_TEXT\": \"看起来好像出了点问题。请再试一次。\",\n\t\"FLAGS\": \"旗帜\",\n\t\"FOOD_DRINK\": \"食物和饮料\",\n\t\"FORM\": \"表格\",\n\t\"FORM_COMPLETION_MESSAGE\": \"感谢您填写表格。\",\n\t\"FRIDAY\": \"星期五\",\n\t\"FULL_SCREEN_VIEWER\": \"全屏查看器\",\n\t\"GENERATE_SUMMARY\": \"生成摘要\",\n\t\"GENERATING_ICEBREAKERS\": \"生成破冰船\",\n\t\"GENERATING_REPLIES\": \"生成回复\",\n\t\"GENERATING_SUMMARY\": \"生成摘要\",\n\t\"GROUPS\": \"群组\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"创建或加入群组以查看此处列出的群组并开始协作\",\n\t\"GROUPS_WITH_MESSAGES\": \"包含消息的群组\",\n\t\"GROUP_INFO\": \"群组信息\",\n\t\"GROUP_LIST\": \"群组名单\",\n\t\"GROUP_MEMBERS\": \"小组成员\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"添加联系人以查看此处列出的联系人。\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"等待收件人接收和查看邮件\",\n\t\"GROUP_NAME_BLANK\": \"群组名称不能为空\",\n\t\"GROUP_PASSWORD_BLANK\": \"群组密码不能为空\",\n\t\"GROUP_TYPE_BLANK\": \"群组类型不能为空\",\n\t\"HELP\": \"帮帮我\",\n\t\"HISTORY\": \"历史\",\n\t\"IGNORE\": \"忽略\",\n\t\"INCOMING_AUDIO_CALL\": \"来电语音\",\n\t\"INCOMING_CALL\": \"来电\",\n\t\"INCOMING_VIDEO_CALL\": \"来电视频通话\",\n\t\"INFO\": \"信息\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"已发起语音通话\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"已发起视频通话\",\n\t\"INVALID_GROUP_NAME\": \"请输入群组的有效名称，然后重试\",\n\t\"INVALID_GROUP_TYPE\": \"请输入群组的有效类型，然后重试\",\n\t\"INVALID_PASSWORD\": \"请输入群组的有效密码并重试\",\n\t\"INVALID_POLL_OPTION\": \"在创建投票之前，请输入所需的答案\",\n\t\"INVALID_POLL_QUESTION\": \"在创建投票之前，请输入必填的问题\",\n\t\"IN_A_THREAD\": \"在一个话题中\",\n\t\"IS_TYPING\": \"正在打字...\",\n\t\"JOIN\": \"加入\",\n\t\"JOINED\": \"已加入\",\n\t\"JOIN_GROUP\": \"加入群组\",\n\t\"JUMP\": \"跳\",\n\t\"KICK\": \"踢\",\n\t\"KICKED\": \"被踢的\",\n\t\"LAST_ACTIVE_AT\": \"上次活跃时间\",\n\t\"LAST_SEEN\": \"上次看到\",\n\t\"LAUNCH\": \"启动\",\n\t\"LEAVE\": \"离开\",\n\t\"LEAVE_CONFIRM\": \"你确定要离开群组吗？\",\n\t\"LEAVE_GROUP\": \"离开群组\",\n\t\"LEAVE_GROUP_TEXT\": \"退出此群组\",\n\t\"LEAVE_SURE\": \"您确定要退出群组吗？您将不再收到此聊天的任何消息。\",\n\t\"LEFT\": \"左边\",\n\t\"LEFT_THE_CALL\": \"离开了电话\",\n\t\"LINK\": \"链接\",\n\t\"LIVE_REACTION\": \"现场反应\",\n\t\"LOADING\": \"加载中...\",\n\t\"LOCALIZE\": \"本地化\",\n\t\"LOGIN\": \"登录\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"看起来好像出了点问题\",\n\t\"MADE\": \"制作\",\n\t\"MEDIA_MESSAGE\": \"媒体消息\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"预订新老虎机\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"此日期没有时间段。请尝试其他日期。\",\n\t\"MEETING_SCHEDULED\": \"您的会议已经安排好了。\",\n\t\"MEETING_SCHEDULER\": \"会议安排器\",\n\t\"MEETING_SLOT_BOOK\": \"时段不再可用。请选择另一个。\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"请尝试不同的日期。\",\n\t\"MEET_WITH\": \"与... 见面\",\n\t\"MEMBER\": \"会员\",\n\t\"MEMBERS\": \"会员\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"您一次最多可以提及 10 个用户。\",\n\t\"MENTION_UPTO\": \"你最多可以提一下\",\n\t\"MESSAGE\": \"留言\",\n\t\"MESSAGES\": \"消息\",\n\t\"MESSAGE_AUDIO\": \"音频\",\n\t\"MESSAGE_COMPOSER\": \"消息编辑器\",\n\t\"MESSAGE_COPIED\": \"消息已复制到剪贴板。\",\n\t\"MESSAGE_DELETED_TEXT\": \"消息已成功删除。\",\n\t\"MESSAGE_EDITED\": \"消息已成功更新。\",\n\t\"MESSAGE_FILE\": \"文件\",\n\t\"MESSAGE_HEADER\": \"留言标题\",\n\t\"MESSAGE_IMAGE\": \"图片\",\n\t\"MESSAGE_INFORMATION\": \"留言信息\",\n\t\"MESSAGE_IS_DELETED\": \"消息已删除\",\n\t\"MESSAGE_LIST\": \"留言清单\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} 已将 ${onName} 添加到群组\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} 已禁止 ${onName} 进入群组\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} 已加入群组\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} 已将 ${onName} 踢出群组\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} 已离开群组\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} 已将 ${onName} 设为 ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} 已解除对 ${onName} 的禁止\",\n\t\"MESSAGE_PRIVATELY\": \"私下留言\",\n\t\"MESSAGE_RECEIPT\": \"留言回执\",\n\t\"MESSAGE_TRANSLATED\": \"消息已成功翻译。\",\n\t\"MESSAGE_VIDEO\": \"视频\",\n\t\"MICROPHONE_PERMISSION\": \"要录制语音信息，我们需要访问您的麦克风。点击 “设置”，然后打开 “麦克风”。\",\n\t\"MISSED_AUDIO_CALL\": \"错过的语音通话\",\n\t\"MISSED_CALL\": \"未接来电\",\n\t\"MISSED_VIDEO_CALL\": \"错过了视频通话\",\n\t\"MISSED_VOICE_CALL\": \"未接语音通话\",\n\t\"MODERATOR\": \"演示者\",\n\t\"MONDAY\": \"星期一\",\n\t\"MORE\": \"更多\",\n\t\"MORE_TIMES\": \"更多次数\",\n\t\"NAME\": \"姓名\",\n\t\"NEW_CHAT\": \"新聊天\",\n\t\"NEW_MESSAGE\": \"新消息\",\n\t\"NEW_MESSAGES\": \"新消息\",\n\t\"NEW__GROUP\": \"新群组\",\n\t\"NO\": \"没有\",\n\t\"NOTIFICATIONS\": \"通知\",\n\t\"NOT_SUPPORTED\": \"不支持此消息类型\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"未找到被禁会员\",\n\t\"NO_CALLS_FOUND\": \"未找到来电\",\n\t\"NO_CALLS_SELECTED\": \"未选择任何呼叫\",\n\t\"NO_CALL_LOGS\": \"还没有通话记录\",\n\t\"NO_CHATS_FOUND\": \"未找到聊天记录\",\n\t\"NO_CHATS_SELECTED\": \"未选择任何聊天记录\",\n\t\"NO_CONVERSATIONS\": \"还没有对话”;  \",\n\t\"NO_DOCUMENTS\": \"没有文件\",\n\t\"NO_GROUPS_AVAILABLE\": \"没有可用的群组\",\n\t\"NO_GROUPS_FOUND\": \"未找到群组\",\n\t\"NO_GROUPS_SELECTED\": \"未选择任何群组\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"没有群组成员可用\",\n\t\"NO_MESSAGES_FOUND\": \"未找到任何消息\",\n\t\"NO_PHOTOS\": \"没有照片\",\n\t\"NO_RECIPIENT\": \"没有收件人\",\n\t\"NO_RECIPIENTS\": \"没有收件人\",\n\t\"NO_RECORDS_FOUND\": \"未找到任何记录\",\n\t\"NO_REPLIES_FOUND\": \"未找到回复\",\n\t\"NO_STICKERS_AVAILABLE\": \"没有可用的贴纸\",\n\t\"NO_STICKERS_FOUND\": \"未找到贴纸\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"没有可用的建议\",\n\t\"NO_SUMMARY_AVAILABLE\": \"没有可用的摘要\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"没有可用的时段\",\n\t\"NO_USERS_AVAILABLE\": \"没有可用用户\",\n\t\"NO_USERS_FOUND\": \"未找到用户\",\n\t\"NO_USERS_SELECTED\": \"未选择任何用户\",\n\t\"NO_VIDEOS\": \"没有视频\",\n\t\"NO_VOTE\": \"不投票\",\n\t\"OBJECTS\": \"物体\",\n\t\"OFFLINE\": \"离线\",\n\t\"ONGOING_AUDIO_CALL\": \"正在进行的音频通话\",\n\t\"ONGOING_CALL\": \"正在进行的通话\",\n\t\"ONGOING_VIDEO_CALL\": \"正在进行的视频通话\",\n\t\"ONLINE\": \"在线\",\n\t\"ON_ANOTHER_CALL\": \"正在接另一个电话\",\n\t\"OOPS\": \"哎哟\",\n\t\"OOPS!\": \"哎哟！\",\n\t\"OPEN_DOCUMENT\": \"打开文档\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"打开文档一起画画\",\n\t\"OPEN_WHITEBOARD\": \"打开白板\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"打开白板一起画画\",\n\t\"OPTIONS\": \"选项\",\n\t\"OTHER\": \"其他\",\n\t\"OTHERS\": \"其他的\",\n\t\"OUTGOING_AUDIO_CALL\": \"拨出语音通话\",\n\t\"OUTGOING_CALL\": \"拨出电话\",\n\t\"OUTGOING_VIDEO_CALL\": \"拨出视频通话\",\n\t\"OWNER\": \"所有者\",\n\t\"PARTICIPANT\": \"参与者\",\n\t\"PARTICIPANTS\": \"参与者\",\n\t\"PASSWORD\": \"密码\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"群组密码错误！\",\n\t\"PASSWORD_PROTECTED\": \"密码保护\",\n\t\"PHOTOS\": \"照片\",\n\t\"PICK_YOUR_EMOJI\": \"选择你的表情符号\",\n\t\"POLLS\": \"民意调查\",\n\t\"PREFERENCES\": \"首选项\",\n\t\"PRIVACY\": \"隐私\",\n\t\"PRIVACY_AND_SECURITY\": \"隐私和安全\",\n\t\"PRIVATE\": \"私人\",\n\t\"PRIVATE_GROUP\": \"私人群组\",\n\t\"PROTECTED_GROUP\": \"受保护群组\",\n\t\"PUBLIC\": \"公开\",\n\t\"QUESTION\": \"问题\",\n\t\"QUESTIONS\": \"问题\",\n\t\"REACHED_MAX_LIMIT\": \"你已经达到了极限。您最多可以添加 12 个选项。\",\n\t\"REACT\": \"做出反应\",\n\t\"REACTED\": \"反应\",\n\t\"REACT_TO_MESSAGE\": \"对消息做出反应\",\n\t\"READ\": \"阅读\",\n\t\"READ_MORE\": \"阅读更多\",\n\t\"RECEIPT_INFORMATION\": \"收据信息\",\n\t\"RECORDING\": \"录音\",\n\t\"REGION\": \"地区\",\n\t\"REJECTED_AUDIO_CALL\": \"音频通话被拒绝\",\n\t\"REJECTED_CALL\": \"呼叫被拒绝\",\n\t\"REJECTED_VIDEO_CALL\": \"视频通话被拒绝\",\n\t\"REMOVE\": \"移除\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"您确定要删除\",\n\t\"REPLIES\": \"回复\",\n\t\"REPLY\": \"答复\",\n\t\"REPLY_IN_THREAD\": \"在话题中回复\",\n\t\"REPLY_TO_THREAD\": \"回复话题\",\n\t\"REPORT_PROBLEM\": \"报告问题\",\n\t\"REQUIRED_FIELDS_WARNING\": \"在创建投票之前，请填写所有必填字段\",\n\t\"RESIZE\": \"调整大小\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"此功能需要特定的资源权限。请在您的设备设置中启用必要的权限\",\n\t\"RETRY\": \"重试\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"选择的翻译语言与原始消息的语言类似\",\n\t\"SATURDAY\": \"星期六\",\n\t\"SAVE\": \"保存\",\n\t\"SCHEDULE\": \"日程安排\",\n\t\"SCOPE\": \"范围\",\n\t\"SCOPE_CHANGE_INFO\": \"您可以更改角色以管理组权限和职责\",\n\t\"SEARCH\": \"搜寻\",\n\t\"SEARCH_EMOJI\": \"搜索表情符号\",\n\t\"SEEN\": \"看见了\",\n\t\"SELECT_A_DATE\": \"选择一个日期\",\n\t\"SELECT_DAY\": \"选择一天\",\n\t\"SELECT_GROUP_TYPE\": \"选择群组类型\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"选择输入音频源\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"选择输出音频源\",\n\t\"SELECT_TIME\": \"选择时间\",\n\t\"SELECT_VIDEO_SOURCE\": \"选择视频来源\",\n\t\"SELECT__GROUP\": \"选择一个群组开始发送消息\",\n\t\"SELECT__USER\": \"选择一个用户开始发送消息\",\n\t\"SEND\": \"发送\",\n\t\"SEND_MESSAGE\": \"发送消息\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"私下发送消息\",\n\t\"SENT\": \"已发送\",\n\t\"SETTINGS\": \"设置\",\n\t\"SET_THE_ANSWERS\": \"设定答案\",\n\t\"SHARE\": \"分享\",\n\t\"SHARED\": \"共享的\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"共享了一份协作文档\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"共享了协作白板\",\n\t\"SHARED_FILE\": \"共享文件\",\n\t\"SHARED_LOCATION\": \"共享位置\",\n\t\"SHARED_MEDIA\": \"共享媒体\",\n\t\"SHOW_LESS\": \"少显示\",\n\t\"SHOW_UNSAFE_CONTENT\": \"您确定要查看不安全的内容吗？\",\n\t\"SMART_REPLIES\": \"智能回复\",\n\t\"SMILEY_PEOPLE\": \"笑脸与人物\",\n\t\"SOMETHING_WENT_WRONG\": \"看起来出了点问题。\",\n\t\"SOMETHING_WRONG\": \"出了点问题。请再试一次。\",\n\t\"SOUND_MANAGER\": \"声音管理器\",\n\t\"STATUS_INDICATOR\": \"状态指示器\",\n\t\"STICKER\": \"贴纸\",\n\t\"SUGGEST_A_REPLY\": \"建议回复\",\n\t\"SUNDAY\": \"星期日\",\n\t\"SURE_TO_DELETE_CHAT\": \"你确定要删除这个聊天吗？此操作无法撤消。\",\n\t\"SYMBOLS\": \"符号\",\n\t\"TAP_TO_REMOVE\": \"点按即可移除\",\n\t\"TAP_TO_START_CONVERSATION\": \"轻触开始对话\",\n\t\"TEXT\": \"文本\",\n\t\"TEXT_TRANSLATE\": \"文字翻译\",\n\t\"TEXT_TRANSLATED\": \"翻译后的文本\",\n\t\"THEME\": \"主题\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ 此消息已删除\",\n\t\"THREAD\": \"线程\",\n\t\"THURSDAY\": \"星期四\",\n\t\"TIME\": \"时间\",\n\t\"TIMES\": \"倍\",\n\t\"TIME_ZONE\": \"时区\",\n\t\"TODAY\": \"今天\",\n\t\"TRANSFER\": \"转移\",\n\t\"TRANSFERRING\": \"转移\",\n\t\"TRANSFER_CONFIRM\": \"您是群组所有者；请在离开群组前将所有权转让给成员\",\n\t\"TRANSFER_OWNERSHIP\": \"转让所有权\",\n\t\"TRANSFER_SURE\": \"您确定要转让所有权吗？此操作无法撤销，新所有者将获得完全控制权。\",\n\t\"TRANSLATE\": \"翻译\",\n\t\"TRANSLATED_MESSAGE\": \"已翻译的消息\",\n\t\"TRANSLATE_MESSAGE\": \"翻译消息\",\n\t\"TRAVEL_PLACES\": \"旅行与地点\",\n\t\"TRY_AGAIN\": \"再试一次\",\n\t\"TUESDAY\": \"星期二\",\n\t\"TYPE\": \"类型\",\n\t\"TYPING\": \"輸入...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"未接听的语音通话\",\n\t\"UNANSWERED_CALL\": \"未接电话\",\n\t\"UNANSWERED_VIDEO_CALL\": \"未接听的视频通话\",\n\t\"UNBAN\": \"取消封禁\",\n\t\"UNBANNED\": \"未被禁止\",\n\t\"UNBAN_SURE\": \"您确定要解除对该用户的封禁吗？\",\n\t\"UNBLOCK\": \"解除屏蔽\",\n\t\"UNBLOCK_CONTACT\": \"解除此联系人的屏蔽\",\n\t\"UNBLOCK_SURE\": \"您确定要解除此联系人的屏蔽吗？您将再次收到他们的消息。\",\n\t\"UNBLOCK_USER\": \"解除屏蔽用户\",\n\t\"USERS\": \"用户\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"我们找不到任何符合您搜索条件的用户。尝试调整搜索条件。\",\n\t\"USERS_WITH_MESSAGES\": \"有消息的用户\",\n\t\"USER_INFO\": \"用户信息\",\n\t\"USER_LIST\": \"用户名单\",\n\t\"VIDEOS\": \"视频\",\n\t\"VIDEO_CALL\": \"视频通话\",\n\t\"VIEW\": \"查看\",\n\t\"VIEW_DETAIL\": \"查看详情\",\n\t\"VIEW_MEMBERS\": \"查看会员\",\n\t\"VIEW_ON_YOUTUBE\": \"在优酷上观看\",\n\t\"VIEW_PROFILE\": \"查看个人资料\",\n\t\"VISIT\": \"参观\",\n\t\"VOICE\": \"语音\",\n\t\"VOICE_CALL\": \"语音通话\",\n\t\"VOICE_RECORDING\": \"语音录制\",\n\t\"VOTE\": \"投票\",\n\t\"VOTES\": \"选票\",\n\t\"WEDNESDAY\": \"周三\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"你想删除这个对话吗？此对话将从您的所有设备中删除。\",\n\t\"WRONG_FILE_TYPE\": \"您选择了其他类型的文件。请选择相应的文件。\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"不允许使用此文件类型。\",\n\t\"WRONG_PASSWORD\": \"请输入正确的密码并重试\",\n\t\"WRONG_TEXT\": \"看起来出了点问题.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"请再试一次.\",\n\t\"YES\": \"是的\",\n\t\"YESTERDAY\": \"昨天\",\n\t\"YOU\": \"你\",\n\t\"YOU'VE_BLOCKED\": \"你已经封锁了\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"您已经在通话中\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ 你删除了这条消息\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"你还没有任何贴纸。\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"你已经发起了语音通话\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"你发起了视频通话\",\n\t\"meeting\": \"会议\",\n\t\"Flag_Message_Title\": \"举报消息\",\n\t\"Flag_Message_Subtitle\": \"如果此聊天违反我们的社区标准，请举报。我们不会告诉对方您已举报他们。\",\n\t\"Flag_Message_Remark_Label\": \"原因\",\n\t\"Flag_Message_Remark_Optional\": \"可选\",\n\t\"Flag_Message_Remark_Placeholder\": \"为您的举报提供额外说明…\",\n\t\"Flag_Message_Confirm_Yes\": \"举报\",\n\t\"Flag_Message_Confirm_No\": \"取消\",\n\t\"Flag_Message_Reason_Id_Spam\": \"垃圾邮件／不想要的内容\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"露骨或不当内容\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"侮辱或威胁行为\",\n\t\"Message_List_Option_Flag_Message\": \"举报\",\n\t\"Flag_Error_Text\":\"出现错误。请检查后重试。\",\n\t\"Flag_Empty_Submit_Text\":\"无法提交。请在举报此消息前选择一个原因。\",\n\t\"START_REPORTING\": \"开始报告\",\n\t\"LOGOUT\": \"退出登录\",\n\t\"AI_ASSISTANTS\": \"AI 助手\",\n\t\"CREATE_CONVERSATION\": \"创建对话\",\n\t\"EDIT_MODERATION\": \"编辑失败。您的消息因 moderation 政策而被屏蔽。\",\n\t\"BLOCKED_MODERATION\": \"您的消息因 moderation 政策而被屏蔽。\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"找不到对话历史\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"我们这边出了点问题，请重试\",\n\t\"START_A_CHAT\": \"通过点击“新聊天”按钮开始聊天\",\n\t\"CHAT_HISTORY\": \"聊天记录\",\n\t\"ASK_ANYTHING\": \"提问任何问题...\",\n\t\"AI_ASSISTANT\": \"AI 助手\",\n\t\"SEARCH_PLACEHOLDER\": \"搜索...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"没有结果\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"开始输入以搜索消息和会话\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"未找到结果\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"未找到匹配项。请尝试其他关键词。\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"加载会话时出错\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"加载消息时出错\",\n\t\"SEARCH_TRY_AGAIN\": \"请稍后再试\",\n\t\"SEARCH_SEE_MORE\": \"查看更多\",\n\t\"SEARCH_LOADING\": \"加载中...\",\n\t\"SEARCH_FILTER_UNREAD\": \"未读\",\n\t\"SEARCH_FILTER_GROUPS\": \"群组\",\n\t\"SEARCH_FILTER_PHOTOS\": \"照片\",\n\t\"SEARCH_FILTER_VIDEOS\": \"视频\",\n\t\"SEARCH_FILTER_LINKS\": \"链接\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"文档\",\n\t\"SEARCH_FILTER_AUDIO\": \"音频\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"会话\",\n\t\"SEARCH_FILTER_MESSAGES\": \"消息\",\n\t\"SEARCH_NO_MESSAGES\": \"没有消息\",\n\t\"MESSAGE_REPORTED\": \"消息已举报\",\n\t\"MARK_AS_UNREAD\": \"标记未读\",\n\t\"NEW\": \"新\",\n\t\"INSERT_LINK\": \"插入链接\",\n\t\"EDIT_LINK\": \"编辑链接\",\n\t\"LINK_TEXT\": \"链接文本\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"插入\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"您无法向此群组发送消息，因为您已不再是成员。\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/resources/zh-tw/translation.json",
    "content": "{\n\t\"ACCEPT\": \"接受\",\n\t\"ACTIONS\": \"行動\",\n\t\"ACTIVITY\": \"活動\",\n\t\"ADD\": \"新增\",\n\t\"ADDED\": \"添加\",\n\t\"ADDING\": \"正在加入...\",\n\t\"ADD_ANOTHER_ANSWER\": \"添加另一個答案\",\n\t\"ADD_CONTACTS\": \"添加聯絡人以開始對話並在此處查看列表。\",\n\t\"ADD_MEMBERS\": \"新增成員\",\n\t\"ADD_NEW_OPTION\": \"新增選項\",\n\t\"ADD_OPTION\": \"新增選項\",\n\t\"ADD_OPTIONS\": \"添加選項\",\n\t\"ADD_REACTION\": \"加入反應\",\n\t\"ADD_TO_CHAT\": \"加入聊天\",\n\t\"ADMIN\": \"管理員\",\n\t\"ADMINISTRATOR\": \"管理員\",\n\t\"AI\": \"艾\",\n\t\"ALL\": \"全部\",\n\t\"MESSAGE_COMPOSER_MENTION_ALL\": \"全部\",\n\t\"MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL\": \"通知此群組中的所有人\",\n\t\"AND\": \"一個\",\n\t\"ANIMALES_NATURE\": \"動物與自然\",\n\t\"ANSWER\": \"答案\",\n\t\"APP_CREDENTIALS\": \"應用程式憑證\",\n\t\"ASK_QUESTION\": \"提出問題\",\n\t\"AT\": \"在\",\n\t\"ATTACH\": \"附加\",\n\t\"ATTACH_AUDIO\": \"附加音訊\",\n\t\"ATTACH_DOCUMENT\": \"附加文件\",\n\t\"ATTACH_FILE\": \"附加檔案\",\n\t\"ATTACH_IMAGE\": \"附加影像\",\n\t\"ATTACH_VIDEO\": \"附加影片\",\n\t\"AT_A_TIME\": \"一次\",\n\t\"AUDIO_CALL\": \"音訊通話\",\n\t\"AVATAR\": \"阿凡達\",\n\t\"BADGE_COUNT\": \"徽章數\",\n\t\"BAN\": \"禁令\",\n\t\"BANNED\": \"取締\",\n\t\"BANNED_MEMBERS\": \"被禁用的會員\",\n\t\"BAN_MEMBER_CONFIRM\": \"您確定要禁止 \",\n\t\"BLOCK\": \"區塊\",\n\t\"BLOCKED_USERS\": \"封鎖的使用者\",\n\t\"BLOCKED_USER_DESC\": \"無法傳送訊息，因為該用戶已被封鎖\",\n\t\"BLOCK_CONTACT\": \"封鎖此聯絡人？\",\n\t\"BLOCK_SURE\": \"您確定要封鎖此聯絡人嗎？您將不再收到他們的訊息。\",\n\t\"BLOCK_USER\": \"封鎖使用者\",\n\t\"CALLING\": \"打電話...\",\n\t\"CALLS\": \"通話\",\n\t\"CALL_ACCEPTED\": \"接受電話\",\n\t\"CALL_ANSWERED\": \"已接聽電話\",\n\t\"CALL_BUSY\": \"打電話忙碌\",\n\t\"CALL_CANCELLED\": \"電話已取消\",\n\t\"CALL_DETAIL\": \"通話詳情\",\n\t\"CALL_DETAILS\": \"通話詳情\",\n\t\"CALL_ENDED\": \"通話結束\",\n\t\"CALL_HISTORY\": \"通話記錄\",\n\t\"CALL_INITIATED\": \"通話已啟動\",\n\t\"CALL_LOGS_EMPTY_MESSAGE\": \"撥打或接聽電話以查看此處列出的通話記錄\",\n\t\"CALL_REJECTED\": \"通話拒絕\",\n\t\"CALL_UNANSWERED\": \"電話未回覆\",\n\t\"CAMERA\": \"相機\",\n\t\"CAMERA_PERMISSION\": \"我們無法訪問您的相機。若要啟用存取權，請點選設定並開啟「相機」。\",\n\t\"CANCEL\": \"取消\",\n\t\"CANCELLED_AUDIO_CALL\": \"已取消音訊通話\",\n\t\"CANCELLED_CALL\": \"已取消通話\",\n\t\"CANCELLED_VIDEO_CALL\": \"已取消視頻通話\",\n\t\"CANT__LOAD__CHATS\": \"無法載入聊天\",\n\t\"CARD\": \"卡\",\n\t\"CHANGE_ROLE\": \"變更角色\",\n\t\"CHANGE_ROLE_DESCRIPTION\": \"您可以變更角色來管理群組權限和職責。\",\n\t\"CHANGE_SCOPE\": \"變更範圍\",\n\t\"CHANGE_TO\": \"更改為\",\n\t\"CHATS\": \"聊天\",\n\t\"CHAT_PRIVATELY\": \"私下聊天\",\n\t\"CLICK_TO_REMOVE\": \"點擊移除\",\n\t\"CLICK_TO_START_CONVERSATION\": \"點擊開始對話\",\n\t\"CLOSE\": \"關閉\",\n\t\"COLLABORATE_USING_DOCUMENT\": \"使用文件進行協作\",\n\t\"COLLABORATE_USING_WHITEBOARD\": \"使用白板進行協作\",\n\t\"COLLABORATIVE_DOCUMENT\": \"協作文件\",\n\t\"COLLABORATIVE_WHITEBOARD\": \"協作白板\",\n\t\"COMETCHAT_ASK_AI_BOT\": \"詢問 AI 機器人\",\n\t\"COMETCHAT_ASK_BOT\": \"詢問\",\n\t\"COMETCHAT_ASK_BOT_SUBTITLE\": \"人工智能機器人\",\n\t\"COMETCHAT_BOT_FIRST_MESSAGE\": \"我該如何幫助您進行這個對話？請問我一個問題，我會給你建議 🙂\",\n\t\"COMPOSER_PLACEHOLDER_TEXT\": \"在此輸入您的訊息\",\n\t\"CONTINUE\": \"繼續\",\n\t\"CONTINUE_WITH_GOOGLE\": \"使用 Google 繼續\",\n\t\"CONVERSATIONS\": \"對話\",\n\t\"CONVERSATIONS_EMPTY_MESSAGE\": \"開始新聊天或邀請其他人加入對話。\",\n\t\"CONVERSATIONS_WITH_MESSAGES\": \"使用訊息的對話\",\n\t\"CONVERSATION_DELETED\": \"已刪除對話\",\n\t\"CONVERSATION_LIST\": \"對話清單\",\n\t\"CONVERSATION_LIST_ITEM\": \"對話清單項目\",\n\t\"CONVERSATION_STARTER\": \"對話開始器\",\n\t\"CONVERSATION_SUMMARY\": \"對話摘要\",\n\t\"COPY\": \"複製\",\n\t\"CREATE\": \"建立\",\n\t\"CREATED_DOCUMENT\": \"您已建立新的協作文件\",\n\t\"CREATED_WHITEBOARD\": \"您已建立新的協作白板\",\n\t\"CREATE_GROUP\": \"建立群組\",\n\t\"CREATE_POLL\": \"投票 \",\n\t\"CREATING\": \"建立\",\n\t\"CUSTOM_MESSAGE\": \"你有一條消息\",\n\t\"CUSTOM_MESSAGE_DOCUMENT\": \"文件\",\n\t\"CUSTOM_MESSAGE_LOCATION\": \"📍 位置\",\n\t\"CUSTOM_MESSAGE_POLL\": \"投票\",\n\t\"CUSTOM_MESSAGE_STICKER\": \"貼紙\",\n\t\"CUSTOM_MESSAGE_WHITEBOARD\": \"白板\",\n\t\"DATA_ITEM\": \"資料項目\",\n\t\"DECLINE\": \"下降\",\n\t\"DELETE\": \"刪除\",\n\t\"DELETE_AND_EXIT\": \"刪除並結束\",\n\t\"DELETE_AND_EXIT_SURE\": \"您確定要刪除此聊天並退出群組嗎？此操作無法撤銷。\",\n\t\"DELETE_CHAT\": \"刪除此聊天？\",\n\t\"DELETE_CHAT_TEXT\": \"刪除聊天\",\n\t\"DELETE_CONFIRM\": \"您確定要刪除嗎？\",\n\t\"DELETE_CONVERSATION\": \"刪除對話？\",\n\t\"DELETE_MESSAGE\": \"刪除訊息\",\n\t\"DELETE_MESSAGE_CONFIRM\": \"您確定要刪除此訊息嗎？此操作無法撤銷。\",\n\t\"DELETE_MSG_TEXT\": \"此訊息已刪除\",\n\t\"DELETE_THIS_CONVERSATION\": \"刪除此對話？\",\n\t\"DELETE_THIS_MESSAGE\": \"刪除此訊息？\",\n\t\"DELIVERED\": \"已交付\",\n\t\"DETAILS\": \"詳情\",\n\t\"DOCS\": \"文件\",\n\t\"DOCUMENT\": \"文件\",\n\t\"DRAW_DOCUMENT_TOGETHER\": \"開啟文件以一起編輯內容\",\n\t\"DRAW_WHITEBOARD_TOGETHER\": \"打開白板以一起繪製\",\n\t\"EDIT\": \"編輯\",\n\t\"EDITED\": \"編輯\",\n\t\"EDIT_MESSAGE\": \"編輯訊息\",\n\t\"EMOJI\": \"表情符號\",\n\t\"ENTER_GROUP_NAME\": \"輸入群組名稱\",\n\t\"ENTER_GROUP_PASSWORD\": \"輸入群組密碼\",\n\t\"ENTER_PASSWORD\": \"輸入密碼\",\n\t\"ENTER_YOUR_MESSAGE_HERE\": \"在此輸入您的訊息\",\n\t\"ENTER_YOUR_OPTION\": \"輸入您的選項\",\n\t\"ENTER_YOUR_PASSWORD\": \"輸入您的密碼\",\n\t\"ENTER_YOUR_QUESTION\": \"輸入您的問題\",\n\t\"ERROR\": \"錯誤\",\n\t\"ERROR_TEXT\": \"好像出了什麼問題請再試一次。\",\n\t\"FLAGS\": \"旗幟\",\n\t\"FOOD_DRINK\": \"食品和飲料\",\n\t\"FORM\": \"表格\",\n\t\"FORM_COMPLETION_MESSAGE\": \"感謝您填寫表格。\",\n\t\"FRIDAY\": \"星期五\",\n\t\"FULL_SCREEN_VIEWER\": \"全螢幕檢視器\",\n\t\"GENERATE_SUMMARY\": \"產生摘要\",\n\t\"GENERATING_ICEBREAKERS\": \"產生破冰器\",\n\t\"GENERATING_REPLIES\": \"產生回覆\",\n\t\"GENERATING_SUMMARY\": \"產生摘要\",\n\t\"GROUPS\": \"群組\",\n\t\"GROUPS_EMPTY_STATE_MESSAGE\": \"建立或加入群組以查看這裡列出群組，並開始合作\",\n\t\"GROUPS_WITH_MESSAGES\": \"帶有訊息的群組\",\n\t\"GROUP_INFO\": \"群組資訊\",\n\t\"GROUP_LIST\": \"群組列表\",\n\t\"GROUP_MEMBERS\": \"群組成員\",\n\t\"GROUP_MEMBER_EMPTY_STATE_MESSAGE\": \"添加聯繫人以查看這裡列出的聯繫人。\",\n\t\"GROUP_MSG_INFO_EMPTY_STATE_MESSAGE\": \"等待收件者接收和檢視訊息\",\n\t\"GROUP_NAME_BLANK\": \"群組名稱不能為空白\",\n\t\"GROUP_PASSWORD_BLANK\": \"群組密碼不能為空白\",\n\t\"GROUP_TYPE_BLANK\": \"群組類型不能為空白\",\n\t\"HELP\": \"幫助\",\n\t\"HISTORY\": \"歷史\",\n\t\"IGNORE\": \"忽略\",\n\t\"INCOMING_AUDIO_CALL\": \"傳入音訊通話\",\n\t\"INCOMING_CALL\": \"來電\",\n\t\"INCOMING_VIDEO_CALL\": \"傳入視訊通話\",\n\t\"INFO\": \"資訊\",\n\t\"INITIATED_GROUP_AUDIO_CALL\": \"已啟動音訊通話\",\n\t\"INITIATED_GROUP_VIDEO_CALL\": \"已啟動視訊通話\",\n\t\"INVALID_GROUP_NAME\": \"請輸入群組的有效名稱，然後再試一次\",\n\t\"INVALID_GROUP_TYPE\": \"請輸入有效的群組類型，然後再試一次\",\n\t\"INVALID_PASSWORD\": \"請輸入群組的有效密碼，然後再試一次\",\n\t\"INVALID_POLL_OPTION\": \"請在建立投票前輸入必要答案\",\n\t\"INVALID_POLL_QUESTION\": \"請在建立投票前輸入必要的問題\",\n\t\"IN_A_THREAD\": \"在一個線程中\",\n\t\"IS_TYPING\": \"正在打字...\",\n\t\"JOIN\": \"加入\",\n\t\"JOINED\": \"加入\",\n\t\"JOIN_GROUP\": \"加入群組\",\n\t\"JUMP\": \"跳躍\",\n\t\"KICK\": \"踢\",\n\t\"KICKED\": \"踢\",\n\t\"LAST_ACTIVE_AT\": \"最後一次活動時間\",\n\t\"LAST_SEEN\": \"上次看到\",\n\t\"LAUNCH\": \"啟動\",\n\t\"LEAVE\": \"離開\",\n\t\"LEAVE_CONFIRM\": \"你確定要離開團隊嗎？\",\n\t\"LEAVE_GROUP\": \"離開組\",\n\t\"LEAVE_GROUP_TEXT\": \"退出此群組\",\n\t\"LEAVE_SURE\": \"您確定要退出群組嗎？您將不再收到此聊天的任何訊息。\",\n\t\"LEFT\": \"剩下\",\n\t\"LEFT_THE_CALL\": \"離開通話\",\n\t\"LINK\": \"連結\",\n\t\"LIVE_REACTION\": \"實時反應\",\n\t\"LOADING\": \"正在載入...\",\n\t\"LOCALIZE\": \"本地化\",\n\t\"LOGIN\": \"登入\",\n\t\"LOOKS_LIKE_SOMETHING_WENT_WRONG\": \"看起來出了什麼問題\",\n\t\"MADE\": \"製作\",\n\t\"MEDIA_MESSAGE\": \"媒體訊息\",\n\t\"MEETING_BOOK_NEW_SLOT\": \"預訂新老虎機\",\n\t\"MEETING_NO_SLOTS_AVAILABLE\": \"此日期沒有可用的時段。請嘗試另一個日期。\",\n\t\"MEETING_SCHEDULED\": \"您的會議已安排。\",\n\t\"MEETING_SCHEDULER\": \"會議排程器\",\n\t\"MEETING_SLOT_BOOK\": \"時段不再可用。請選擇其他。\",\n\t\"MEETING_TRY_DIFFERENT_DATE\": \"請嘗試不同的日期。\",\n\t\"MEET_WITH\": \"會面\",\n\t\"MEMBER\": \"會員\",\n\t\"MEMBERS\": \"會員\",\n\t\"MENTIONS_LIMIT_WARNING_MESSAGE\": \"您最多可以一次提及 10 個用戶。\",\n\t\"MENTION_UPTO\": \"您最多可以提到\",\n\t\"MESSAGE\": \"留言\",\n\t\"MESSAGES\": \"訊息\",\n\t\"MESSAGE_AUDIO\": \"音頻\",\n\t\"MESSAGE_COMPOSER\": \"訊息撰寫器\",\n\t\"MESSAGE_COPIED\": \"郵件複製到剪貼簿。\",\n\t\"MESSAGE_DELETED_TEXT\": \"郵件已成功刪除。\",\n\t\"MESSAGE_EDITED\": \"訊息已成功更新。\",\n\t\"MESSAGE_FILE\": \"文件\",\n\t\"MESSAGE_HEADER\": \"郵件標題\",\n\t\"MESSAGE_IMAGE\": \"圖像\",\n\t\"MESSAGE_INFORMATION\": \"訊息資訊\",\n\t\"MESSAGE_IS_DELETED\": \"訊息已刪除\",\n\t\"MESSAGE_LIST\": \"訊息清單\",\n\t\"MESSAGE_LIST_ACTION_ADDED\": \"${byName} 已將 ${onName} 加入到群組\",\n\t\"MESSAGE_LIST_ACTION_BANNED\": \"${byName} 已禁止 ${onName} 進入群組\",\n\t\"MESSAGE_LIST_ACTION_JOINED\": \"${byName} 已加入群組\",\n\t\"MESSAGE_LIST_ACTION_KICKED\": \"${byName} 已將 ${onName} 踢出群組\",\n\t\"MESSAGE_LIST_ACTION_LEFT\": \"${byName} 已離開群組\",\n\t\"MESSAGE_LIST_ACTION_SCOPE_CHANGED\": \"${byName} 已將 ${onName} 設為 ${role}\",\n\t\"MESSAGE_LIST_ACTION_UNBANNED\": \"${byName} 已解除對 ${onName} 的禁止\",\n\t\"MESSAGE_PRIVATELY\": \"私下留言\",\n\t\"MESSAGE_RECEIPT\": \"訊息收據\",\n\t\"MESSAGE_TRANSLATED\": \"郵件已成功翻譯。\",\n\t\"MESSAGE_VIDEO\": \"視頻\",\n\t\"MICROPHONE_PERMISSION\": \"要錄製語音消息，我們需要訪問您的麥克風。點選設定，然後開啟麥克風。\",\n\t\"MISSED_AUDIO_CALL\": \"未接聽音訊通話\",\n\t\"MISSED_CALL\": \"未接來電\",\n\t\"MISSED_VIDEO_CALL\": \"未接視訊通話\",\n\t\"MISSED_VOICE_CALL\": \"未接的語音通話\",\n\t\"MODERATOR\": \"主持人\",\n\t\"MONDAY\": \"星期一\",\n\t\"MORE\": \"更多\",\n\t\"MORE_TIMES\": \"更多次數\",\n\t\"NAME\": \"姓名\",\n\t\"NEW_CHAT\": \"新聊天\",\n\t\"NEW_MESSAGE\": \"新消息\",\n\t\"NEW_MESSAGES\": \"新消息\",\n\t\"NEW__GROUP\": \"新集團\",\n\t\"NO\": \"否\",\n\t\"NOTIFICATIONS\": \"通知\",\n\t\"NOT_SUPPORTED\": \"不支援此訊息類型\",\n\t\"NO_BANNED_MEMBERS_FOUND\": \"找不到被禁用的會員\",\n\t\"NO_CALLS_FOUND\": \"找不到電話\",\n\t\"NO_CALLS_SELECTED\": \"未選取通話\",\n\t\"NO_CALL_LOGS\": \"尚未通話記錄\",\n\t\"NO_CHATS_FOUND\": \"找不到聊天\",\n\t\"NO_CHATS_SELECTED\": \"未選取任何對話\",\n\t\"NO_CONVERSATIONS\": \"尚未對話」；  \",\n\t\"NO_DOCUMENTS\": \"沒有文件\",\n\t\"NO_GROUPS_AVAILABLE\": \"沒有可用的群組\",\n\t\"NO_GROUPS_FOUND\": \"找不到群組\",\n\t\"NO_GROUPS_SELECTED\": \"未選取群組\",\n\t\"NO_GROUP_MEMBER_AVAILABLE\": \"沒有可用的群組成員\",\n\t\"NO_MESSAGES_FOUND\": \"找不到任何訊息\",\n\t\"NO_PHOTOS\": \"沒有照片\",\n\t\"NO_RECIPIENT\": \"沒有收件人\",\n\t\"NO_RECIPIENTS\": \"沒有收件人\",\n\t\"NO_RECORDS_FOUND\": \"找不到任何記錄\",\n\t\"NO_REPLIES_FOUND\": \"找不到回覆\",\n\t\"NO_STICKERS_AVAILABLE\": \"沒有貼紙可用\",\n\t\"NO_STICKERS_FOUND\": \"找不到貼紙\",\n\t\"NO_SUGGESTIONS_AVAILABLE\": \"沒有提供建議\",\n\t\"NO_SUMMARY_AVAILABLE\": \"沒有提供摘要\",\n\t\"NO_TIME_SLOTS_AVAILABLE\": \"沒有可用的時間段\",\n\t\"NO_USERS_AVAILABLE\": \"沒有可用的使用者\",\n\t\"NO_USERS_FOUND\": \"找不到使用者\",\n\t\"NO_USERS_SELECTED\": \"未選取使用者\",\n\t\"NO_VIDEOS\": \"沒有影片\",\n\t\"NO_VOTE\": \"沒有投票\",\n\t\"OBJECTS\": \"物件\",\n\t\"OFFLINE\": \"離線\",\n\t\"ONGOING_AUDIO_CALL\": \"正在進行的音訊通話\",\n\t\"ONGOING_CALL\": \"正在進行的電話\",\n\t\"ONGOING_VIDEO_CALL\": \"正在進行的視頻通話\",\n\t\"ONLINE\": \"在線\",\n\t\"ON_ANOTHER_CALL\": \"正在另一次通話\",\n\t\"OOPS\": \"抱歉！\",\n\t\"OOPS!\": \"抱歉！\",\n\t\"OPEN_DOCUMENT\": \"開啟文件\",\n\t\"OPEN_DOCUMENT_TO_DRAW\": \"打開文件以一起繪製\",\n\t\"OPEN_WHITEBOARD\": \"打開白板\",\n\t\"OPEN_WHITEBOARD_TO_DRAW\": \"打開白板以一起繪製\",\n\t\"OPTIONS\": \"選項\",\n\t\"OTHER\": \"其他\",\n\t\"OTHERS\": \"其他\",\n\t\"OUTGOING_AUDIO_CALL\": \"外出音訊通話\",\n\t\"OUTGOING_CALL\": \"外出來電話\",\n\t\"OUTGOING_VIDEO_CALL\": \"外出視訊通話\",\n\t\"OWNER\": \"業主\",\n\t\"PARTICIPANT\": \"參與者\",\n\t\"PARTICIPANTS\": \"參加者\",\n\t\"PASSWORD\": \"密碼\",\n\t\"PASSWORD_INCORRECT_GROUP\": \"群組密碼錯誤！\",\n\t\"PASSWORD_PROTECTED\": \"密碼保護\",\n\t\"PHOTOS\": \"照片\",\n\t\"PICK_YOUR_EMOJI\": \"選擇您的表情符號\",\n\t\"POLLS\": \"民意調查\",\n\t\"PREFERENCES\": \"偏好設定\",\n\t\"PRIVACY\": \"隱私\",\n\t\"PRIVACY_AND_SECURITY\": \"隱私與安全\",\n\t\"PRIVATE\": \"私人\",\n\t\"PRIVATE_GROUP\": \"私人團體\",\n\t\"PROTECTED_GROUP\": \"受保護群組\",\n\t\"PUBLIC\": \"公眾\",\n\t\"QUESTION\": \"問題\",\n\t\"QUESTIONS\": \"問題\",\n\t\"REACHED_MAX_LIMIT\": \"你已經達到了極限。您最多可以添加 12 個選項。\",\n\t\"REACT\": \"反應\",\n\t\"REACTED\": \"反應\",\n\t\"REACT_TO_MESSAGE\": \"對訊息做出反應\",\n\t\"READ\": \"閱讀\",\n\t\"READ_MORE\": \"閱讀更多\",\n\t\"RECEIPT_INFORMATION\": \"收據信息\",\n\t\"RECORDING\": \"錄音\",\n\t\"REGION\": \"地區\",\n\t\"REJECTED_AUDIO_CALL\": \"拒絕的音訊通話\",\n\t\"REJECTED_CALL\": \"拒絕的呼叫\",\n\t\"REJECTED_VIDEO_CALL\": \"拒絕的視訊通話\",\n\t\"REMOVE\": \"移除\",\n\t\"REMOVE_MEMBER_CONFIRM\": \"您確定要刪除\",\n\t\"REPLIES\": \"回覆\",\n\t\"REPLY\": \"回复\",\n\t\"REPLY_IN_THREAD\": \"在主題中回复\",\n\t\"REPLY_TO_THREAD\": \"回覆主題\",\n\t\"REPORT_PROBLEM\": \"報告問題\",\n\t\"REQUIRED_FIELDS_WARNING\": \"請在建立投票前填寫所有必填欄位\",\n\t\"RESIZE\": \"調整大小\",\n\t\"RESOURCE_PERMISSION_REQUIRED\": \"此功能需要特定的資源權限。請在設備設置中啟用必要的權限\",\n\t\"RETRY\": \"重試\",\n\t\"SAME_LANGUAGE_MESSAGE\": \"用於翻譯的選擇語言類似於原始消息的語言\",\n\t\"SATURDAY\": \"星期六\",\n\t\"SAVE\": \"儲存\",\n\t\"SCHEDULE\": \"時間表\",\n\t\"SCOPE\": \"範圍\",\n\t\"SCOPE_CHANGE_INFO\": \"您可以更改角色以管理群組權限和職責\",\n\t\"SEARCH\": \"搜索\",\n\t\"SEARCH_EMOJI\": \"搜索表情符號\",\n\t\"SEEN\": \"已經看到\",\n\t\"SELECT_A_DATE\": \"選擇日期\",\n\t\"SELECT_DAY\": \"選擇一天\",\n\t\"SELECT_GROUP_TYPE\": \"選擇群組類型\",\n\t\"SELECT_INPUT_AUDIO_SOURCE\": \"選擇輸入音頻源\",\n\t\"SELECT_OUTPUT_AUDIO_SOURCE\": \"選擇輸出音頻源\",\n\t\"SELECT_TIME\": \"選擇時間\",\n\t\"SELECT_VIDEO_SOURCE\": \"選擇視頻來源\",\n\t\"SELECT__GROUP\": \"選取群組以開始傳送訊息\",\n\t\"SELECT__USER\": \"選取用戶以開始傳送訊息\",\n\t\"SEND\": \"發送\",\n\t\"SEND_MESSAGE\": \"發送訊息\",\n\t\"SEND_MESSAGE_IN_PRIVATE\": \"私下傳送訊息\",\n\t\"SENT\": \"已發送\",\n\t\"SETTINGS\": \"設定\",\n\t\"SET_THE_ANSWERS\": \"設置答案\",\n\t\"SHARE\": \"分享\",\n\t\"SHARED\": \"共用\",\n\t\"SHARED_COLLABORATIVE_DOCUMENT\": \"已共用協作文件\",\n\t\"SHARED_COLLABORATIVE_WHITEBOARD\": \"共享了一個協作白板\",\n\t\"SHARED_FILE\": \"共用檔案\",\n\t\"SHARED_LOCATION\": \"共用位置\",\n\t\"SHARED_MEDIA\": \"共享媒體\",\n\t\"SHOW_LESS\": \"顯示更少\",\n\t\"SHOW_UNSAFE_CONTENT\": \"您確定要查看不安全的內容嗎？\",\n\t\"SMART_REPLIES\": \"智能回覆\",\n\t\"SMILEY_PEOPLE\": \"笑臉和人物\",\n\t\"SOMETHING_WENT_WRONG\": \"看起來出了點問題。\",\n\t\"SOMETHING_WRONG\": \"出了什麼問題請再試一次。\",\n\t\"SOUND_MANAGER\": \"聲音管理員\",\n\t\"STATUS_INDICATOR\": \"狀態指示器\",\n\t\"STICKER\": \"貼紙\",\n\t\"SUGGEST_A_REPLY\": \"提出回覆\",\n\t\"SUNDAY\": \"星期日\",\n\t\"SURE_TO_DELETE_CHAT\": \"您確定要刪除此聊天嗎？此動作無法復原。\",\n\t\"SYMBOLS\": \"符號\",\n\t\"TAP_TO_REMOVE\": \"點擊以移除\",\n\t\"TAP_TO_START_CONVERSATION\": \"點擊開始對話\",\n\t\"TEXT\": \"文字\",\n\t\"TEXT_TRANSLATE\": \"文字翻譯\",\n\t\"TEXT_TRANSLATED\": \"翻譯後的文本\",\n\t\"THEME\": \"主題\",\n\t\"THIS_MESSAGE_DELETED\": \"⚠️ 此消息已刪除\",\n\t\"THREAD\": \"線程\",\n\t\"THURSDAY\": \"星期四\",\n\t\"TIME\": \"時間\",\n\t\"TIMES\": \"倍\",\n\t\"TIME_ZONE\": \"時區\",\n\t\"TODAY\": \"今天\",\n\t\"TRANSFER\": \"轉移\",\n\t\"TRANSFERRING\": \"轉移\",\n\t\"TRANSFER_CONFIRM\": \"您是群組擁有者，請在離開群組之前將所有權轉移給成員\",\n\t\"TRANSFER_OWNERSHIP\": \"轉讓所有權\",\n\t\"TRANSFER_SURE\": \"您確定要轉移所有權嗎？此操作無法撤銷，新所有者將獲得完全控制權。\",\n\t\"TRANSLATE\": \"翻譯\",\n\t\"TRANSLATED_MESSAGE\": \"已翻譯的訊息\",\n\t\"TRANSLATE_MESSAGE\": \"翻譯訊息\",\n\t\"TRAVEL_PLACES\": \"旅遊與地點\",\n\t\"TRY_AGAIN\": \"再試一次\",\n\t\"TUESDAY\": \"星期二\",\n\t\"TYPE\": \"類型\",\n\t\"TYPING\": \"輸入...\",\n\t\"UNANSWERED_AUDIO_CALL\": \"未接聽的音訊通話\",\n\t\"UNANSWERED_CALL\": \"未接聽的電話\",\n\t\"UNANSWERED_VIDEO_CALL\": \"未接聽的視頻通話\",\n\t\"UNBAN\": \"取消禁\",\n\t\"UNBANNED\": \"未禁止\",\n\t\"UNBAN_SURE\": \"您確定要解除對該用戶的封禁嗎？\",\n\t\"UNBLOCK\": \"解除封鎖\",\n\t\"UNBLOCK_CONTACT\": \"解除此聯絡人的封鎖\",\n\t\"UNBLOCK_SURE\": \"您確定要解除此聯絡人的封鎖嗎？您將再次收到他們的訊息。\",\n\t\"UNBLOCK_USER\": \"解除封鎖使用者\",\n\t\"USERS\": \"使用者\",\n\t\"USERS_EMPTY_STATE_MESSAGE\": \"我們找不到符合您搜尋的使用者。嘗試調整搜尋。\",\n\t\"USERS_WITH_MESSAGES\": \"具有訊息的使用者\",\n\t\"USER_INFO\": \"用戶資訊\",\n\t\"USER_LIST\": \"使用者清單\",\n\t\"VIDEOS\": \"影片\",\n\t\"VIDEO_CALL\": \"視訊通話\",\n\t\"VIEW\": \"查看\",\n\t\"VIEW_DETAIL\": \"查看詳細資料\",\n\t\"VIEW_MEMBERS\": \"查看成員\",\n\t\"VIEW_ON_YOUTUBE\": \"在 YouTube 上查看\",\n\t\"VIEW_PROFILE\": \"查看個人資料\",\n\t\"VISIT\": \"參觀\",\n\t\"VOICE\": \"語音\",\n\t\"VOICE_CALL\": \"語音通話\",\n\t\"VOICE_RECORDING\": \"語音錄音\",\n\t\"VOTE\": \"投票\",\n\t\"VOTES\": \"票\",\n\t\"WEDNESDAY\": \"星期三\",\n\t\"WOULD__YOU_LIKE_TO_DELETE_THIS_CONVERSATION\": \"您想刪除此對話嗎？此對話將從您的所有設備中刪除。\",\n\t\"WRONG_FILE_TYPE\": \"您選取了不同類型的檔案。請選擇適當的檔案。\",\n\t\"FILE_TYPE_NOT_ALLOWED\": \"不允許使用此檔案類型。\",\n\t\"WRONG_PASSWORD\": \"請輸入正確的密碼，然後再試一次\",\n\t\"WRONG_TEXT\": \"看起來出了點問題.\",\n\t\"WRONG_TEXT_TRY_AGAIN\": \"請再試一次.\",\n\t\"YES\": \"是\",\n\t\"YESTERDAY\": \"昨天\",\n\t\"YOU\": \"你\",\n\t\"YOU'VE_BLOCKED\": \"您已封鎖\",\n\t\"YOU_ALREADY_ONGOING_CALL\": \"您已經在進行中的通話\",\n\t\"YOU_DELETED_THIS_MESSAGE\": \"⚠️ 您刪除了此消息\",\n\t\"YOU_DONT_HAVE_STICKERS_YET\": \"你還沒有任何貼圖。\",\n\t\"YOU_INITIATED_GROUP_AUDIO_CALL\": \"您已啟動音訊通話\",\n\t\"YOU_INITIATED_GROUP_VIDEO_CALL\": \"您已啟動視訊通話\",\n\t\"meeting\": \"會議\",\n\t\"Flag_Message_Title\": \"檢舉訊息\",\n\t\"Flag_Message_Subtitle\": \"若此對話違反我們的社群規範，請檢舉。我們不會告知該帳戶您已檢舉他們。\",\n\t\"Flag_Message_Remark_Label\": \"理由\",\n\t\"Flag_Message_Remark_Optional\": \"選填\",\n\t\"Flag_Message_Remark_Placeholder\": \"為您的檢舉提供更多說明…\",\n\t\"Flag_Message_Confirm_Yes\": \"檢舉\",\n\t\"Flag_Message_Confirm_No\": \"取消\",\n\t\"Flag_Message_Reason_Id_Spam\": \"垃圾訊息／不想要的內容\",\n\t\"Flag_Message_Reason_Id_Sexual\": \"露骨或不當內容\",\n\t\"Flag_Message_Reason_Id_Harassment\": \"侮辱或威脅行為\",\n\t\"Message_List_Option_Flag_Message\": \"檢舉\",\n\t\"Flag_Error_Text\":\"發生錯誤。請檢查後再試一次。\",\n\t\"Flag_Empty_Submit_Text\":\"無法提交。請在回報此訊息前選擇一個原因。\",\n\t\"START_REPORTING\": \"開始報告\",\n\t\"LOGOUT\": \"登出\",\n\t\"AI_ASSISTANTS\": \"AI 助手\",\n\t\"CREATE_CONVERSATION\": \"建立對話\",\n\t\"EDIT_MODERATION\": \"編輯失敗。您的訊息因為管理政策被封鎖。\",\n\t\"BLOCKED_MODERATION\": \"您的訊息因為管理政策被封鎖。\",\n\t\"NO_CONVERSATION_HISTORY_FOUND\": \"找不到對話歷史\",\n\t\"SOMETHING_WENT_WRONG_ON_OUR_END\": \"我們這邊出了點問題，請重試\",\n\t\"START_A_CHAT\": \"點擊“新聊天”按鈕開始聊天\",\n\t\"CHAT_HISTORY\": \"聊天記錄\",\n\t\"ASK_ANYTHING\": \"提問任何問題...\",\n\t\"AI_ASSISTANT\": \"AI 助手\",\n\t\"SEARCH_PLACEHOLDER\": \"搜尋...\",\n\t\"SEARCH_NO_RESULTS_TITLE\": \"沒有結果\",\n\t\"SEARCH_NO_RESULTS_SUBTITLE\": \"開始輸入以搜尋訊息和對話\",\n\t\"SEARCH_NO_RESULTS_FOUND_TITLE\": \"未找到結果\",\n\t\"SEARCH_NO_RESULTS_FOUND_SUBTITLE\": \"找不到任何相符項目。請嘗試其他關鍵字。\",\n\t\"SEARCH_ERROR_LOADING_CONVERSATIONS\": \"載入對話時發生錯誤\",\n\t\"SEARCH_ERROR_LOADING_MESSAGES\": \"載入訊息時發生錯誤\",\n\t\"SEARCH_TRY_AGAIN\": \"請稍後再試\",\n\t\"SEARCH_SEE_MORE\": \"查看更多\",\n\t\"SEARCH_LOADING\": \"載入中...\",\n\t\"SEARCH_FILTER_UNREAD\": \"未讀\",\n\t\"SEARCH_FILTER_GROUPS\": \"群組\",\n\t\"SEARCH_FILTER_PHOTOS\": \"照片\",\n\t\"SEARCH_FILTER_VIDEOS\": \"影片\",\n\t\"SEARCH_FILTER_LINKS\": \"連結\",\n\t\"SEARCH_FILTER_DOCUMENTS\": \"文件\",\n\t\"SEARCH_FILTER_AUDIO\": \"音訊\",\n\t\"SEARCH_FILTER_CONVERSATIONS\": \"對話\",\n\t\"SEARCH_FILTER_MESSAGES\": \"訊息\",\n\t\"SEARCH_NO_MESSAGES\": \"沒有訊息\",\n\t\"MESSAGE_REPORTED\": \"訊息已檢舉\",\n\t\"MARK_AS_UNREAD\": \"標記未讀\",\n\t\"NEW\": \"新\",\n\t\"INSERT_LINK\": \"插入連結\",\n\t\"EDIT_LINK\": \"編輯連結\",\n\t\"LINK_TEXT\": \"連結文字\",\n\t\"URL\": \"URL\",\n\t\"INSERT\": \"插入\",\n\t\"GROUP_NO_LONGER_MEMBER\": \"您無法向此群組發送訊息，因為您已不再是成員。\"\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/type.ts",
    "content": "export type Language = string; \n\nexport type CometChatLocalizeContextType = {\n    language: Language;\n    t: (key: string) => string;\n};\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatLocalizeNew/useCometChatTranslationHook.ts",
    "content": "import { useContext } from \"react\";\nimport { CometChatLocalizeContext } from \"./CometChatLocalizeContext\";\nimport { getAvailableLanguages, translate } from \"./CometChatLocalizationHelper\";\n\nexport const useCometChatTranslation = () => {\n    const context = useContext(CometChatLocalizeContext);\n\n\n    if (!context) {\n        console.warn('useCometChatTranslation used outside provider, using fallback translations');\n\n        return {\n            language: 'en' as const,\n            t: (key: string) => translate('en', key, undefined, 'en'), \n            availableLanguages: getAvailableLanguages(),\n        };\n    }\n\n\n    return {\n        ...context,\n        availableLanguages: getAvailableLanguages(),\n    };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatSoundManager/CometChatSoundManager.js",
    "content": "import { Vibration, NativeModules } from 'react-native';\nconst { CometChatSoundModule } = NativeModules;\nimport * as consts from '../../constants/UIKitConstants';\nimport Sound from './sound';\nimport {\n  incomingMessageAlert,\n  incomingCallAlert,\n  incomingOtherMessageAlert,\n  outgoingMessageAlert,\n  outgoingCallAlert,\n} from './resources/index';\nexport class CometChatSoundManager {\n  static SoundOutput = Object.freeze({\n    incomingCall: incomingCallAlert,\n    incomingMessage: incomingMessageAlert,\n    incomingMessageFromOther: incomingOtherMessageAlert,\n    outgoingCall: outgoingCallAlert,\n    outgoingMessage: outgoingMessageAlert,\n  });\n\n  static audio = null;\n  static onPlay = async (resource, loop, isRequire) => {\n    try {\n      let otherAudioPlaying =\n        await CometChatSoundModule.checkOtherAudioPlaying();\n      if (otherAudioPlaying) {\n        Vibration.vibrate(consts.PATTERN, loop);\n      } else {\n        if (CometChatSoundManager.audio != null) {\n          CometChatSoundManager.pause();\n        }\n        if (isRequire) {\n          CometChatSoundManager.audio = new Sound(\n            resource,\n            () => { },\n          );\n        } else {\n          CometChatSoundManager.audio = new Sound(\n            resource,\n            Sound.MAIN_BUNDLE,\n            () => { },\n          );\n        }\n        CometChatSoundManager.audio.setCategory('playback', true);\n        CometChatSoundManager.audio.setVolume(1);\n        CometChatSoundManager.audio.setCurrentTime(0);\n        setTimeout(() => {\n          if (loop) {\n            CometChatSoundManager.audio.setNumberOfLoops(-1);\n          }\n          CometChatSoundManager.audio.play((e) => { console.log({ e }) });\n        }, 500);\n\n      }\n    } catch (error) {\n      console.log(\"error : \", error);\n    }\n  };\n\n  static async play(sound, customSound, isRequire = false) {\n    let resource = null;\n    if (customSound) {\n      resource = customSound;\n    } else {\n      resource = CometChatSoundManager.SoundOutput[sound];\n    }\n    switch (sound) {\n      case 'incomingCall':\n        this.onPlay(resource, true, isRequire);\n        break;\n      case 'incomingMessage':\n        this.onPlay(resource, false, isRequire);\n        break;\n      case 'incomingMessageFromOther':\n        this.onPlay(resource, false, isRequire);\n        break;\n      case 'outgoingCall':\n        this.onPlay(resource, true, isRequire);\n        break;\n      case 'outgoingMessage':\n        this.onPlay(resource, false, isRequire);\n        break;\n      case 'default':\n        return false;\n    }\n  }\n\n  static pause() {\n    if (CometChatSoundManager.audio) {\n      CometChatSoundManager.audio.pause();\n      Vibration.cancel();\n      CometChatSoundManager.audio.release();\n    }\n  }\n}"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatSoundManager/index.ts",
    "content": "//@ts-ignore\nimport { CometChatSoundManager } from \"./CometChatSoundManager.js\";\nexport { CometChatSoundManager };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatSoundManager/resources/index.js",
    "content": "export { default as incomingMessageAlert } from \"./incomingmessage.wav\";\nexport { default as incomingOtherMessageAlert } from \"./incomingothermessage.wav\";\nexport { default as outgoingMessageAlert } from \"./outgoingmessage.wav\";\nexport { default as incomingCallAlert } from \"./incomingcall.wav\";\nexport { default as outgoingCallAlert } from \"./outgoingcall.wav\";"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/CometChatSoundManager/sound.js",
    "content": "import { NativeModules, NativeEventEmitter } from 'react-native';\nconst {CometChatSoundModule} = NativeModules ;\nvar IsAndroid = CometChatSoundModule.IsAndroid;\nvar IsWindows = CometChatSoundModule.IsWindows;\nimport resolveAssetSource from \"react-native/Libraries/Image/resolveAssetSource\";\nvar eventEmitter = new NativeEventEmitter(CometChatSoundModule);\n\nvar nextKey = 0;\n\nfunction isRelativePath(path) {\n  return !/^(\\/|http(s?)|asset|file)/.test(path);\n}\n\nfunction calculateRelativeVolume(volume, pan) {\n  // calculates a lower volume relative to the pan value\n  const relativeVolume = (volume * (1 - Math.abs(pan)));\n  return Number(relativeVolume.toFixed(1));\n}\n\nfunction setAndroidVolumes(sound) {\n  // calculates the volumes for left and right channels\n  if (sound._pan) {\n    const relativeVolume = calculateRelativeVolume(sound._volume, sound._pan);\n    if (sound._pan < 0) {\n      // left is louder\n      CometChatSoundModule.setVolume(sound._key, sound._volume, relativeVolume);\n    } else {\n      // right is louder\n      CometChatSoundModule.setVolume(sound._key, relativeVolume, sound._volume);\n    }\n  } else {\n    // no panning, same volume on both channels\n    CometChatSoundModule.setVolume(sound._key, sound._volume, sound._volume);\n  }\n}\n\nclass Sound {\n  constructor(filename, basePath, onError, options) {\n    var asset = resolveAssetSource(filename);\n    if (asset) {\n      this._filename = asset.uri;\n      onError = basePath;\n    } else {\n      this._filename = basePath ? basePath + '/' + filename : filename;\n\n      if (IsAndroid && !basePath && isRelativePath(filename)) {\n        this._filename = filename.toLowerCase().replace(/\\.[^.]+$/, '');\n      }\n    }\n\n    \n\n    this._loaded = false;\n    this._key = nextKey++;\n    this._playing = false;\n    this._duration = -1;\n    this._numberOfChannels = -1;\n    this._volume = 1;\n    this._pan = 0;\n    this._numberOfLoops = 0;\n    this._speed = 1;\n    this._pitch = 1;\n    CometChatSoundModule.prepare(this._filename, this._key, options || {}, (error, props) => {\n      if (props) {\n        if (typeof props.duration === 'number') {\n          this._duration = props.duration;\n        }\n        if (typeof props.numberOfChannels === 'number') {\n          this._numberOfChannels = props.numberOfChannels;\n        }\n      }\n      if (error === null) {\n        this._loaded = true;\n        this.registerOnPlay();\n      }\n     // onError && onError(error, props);\n    });\n  }\n\n  registerOnPlay() {\n    if (this.onPlaySubscription != null) {\n      console.warn('On Play change event listener is already registered');\n      return;\n    }\n\n    if (!IsWindows) {\n      this.onPlaySubscription = eventEmitter.addListener(\n        'onPlayChange',\n        (param) => {\n          const { isPlaying, playerKey } = param;\n          if (playerKey === this._key) {\n            if (isPlaying) {\n              this._playing = true;\n            }\n            else {\n              this._playing = false;\n            }\n          }\n        }\n      );\n    }\n  };\n  static enable(enabled) {\n    CometChatSoundModule.enable(enabled);\n  }\n  static enableInSilenceMode(enabled) {\n    if (!IsAndroid && !IsWindows) {\n      CometChatSoundModule.enableInSilenceMode(enabled);\n    }\n  }\n  static setActive(value) {\n    if (!IsAndroid && !IsWindows) {\n      CometChatSoundModule.setActive(value);\n    }\n  }\n  static setCategory(value, mixWithOthers = false) {\n    if (!IsWindows) {\n      CometChatSoundModule.setCategory(value, mixWithOthers);\n    }\n  }\n  static setMode(value) {\n    if (!IsAndroid && !IsWindows) {\n      CometChatSoundModule.setMode(value);\n    }\n  }\n  static setSpeakerPhone(value) {\n    if (!IsAndroid && !IsWindows) {\n      CometChatSoundModule.setSpeakerPhone(value);\n    }\n  }\n  isLoaded() {\n    return this._loaded;\n  }\n  play(onEnd) {\n    if (this._loaded) {\n      CometChatSoundModule.play(this._key, (successfully) => onEnd && onEnd(successfully));\n    } else {\n      onEnd && onEnd(false);\n    }\n    return this;\n  }\n  pause(callback) {\n    if (this._loaded) {\n      CometChatSoundModule.pause(this._key, () => {\n        this._playing = false;\n        callback && callback();\n      });\n    }\n    return this;\n  }\n  stop(callback) {\n    if (this._loaded) {\n      CometChatSoundModule.stop(this._key, () => {\n        this._playing = false;\n        callback && callback();\n      });\n    }\n    return this;\n  }\n  reset() {\n    if (this._loaded && IsAndroid) {\n      CometChatSoundModule.reset(this._key);\n      this._playing = false;\n    }\n    return this;\n  }\n  release() {\n    if (this._loaded) {\n      CometChatSoundModule.release(this._key);\n      this._loaded = false;\n      if (!IsWindows) {\n        if (this.onPlaySubscription != null) {\n          this.onPlaySubscription.remove();\n          this.onPlaySubscription = null;\n        }\n      }\n    }\n    return this;\n  }\n  getFilename() {\n    return this._filename;\n  }\n  getDuration() {\n    return this._duration;\n  }\n  getNumberOfChannels() {\n    return this._numberOfChannels;\n  }\n  getVolume() {\n    return this._volume;\n  }\n  getSpeed() {\n    return this._speed;\n  }\n  getPitch() {\n    return this._pitch;\n  }\n  setVolume(value) {\n    this._volume = value;\n    if (this._loaded) {\n      if (IsAndroid) {\n        setAndroidVolumes(this);\n      } else {\n        CometChatSoundModule.setVolume(this._key, value);\n      }\n    }\n    return this;\n  }\n  setPan(value) {\n    this._pan = value;\n    if (this._loaded) {\n      if (IsWindows) {\n        throw new Error('#setPan not supported on windows');\n      } else if (IsAndroid) {\n        setAndroidVolumes(this);\n      } else {\n        CometChatSoundModule.setPan(this._key, value);\n      }\n    }\n    return this;\n  }\n  getSystemVolume(callback) {\n    if (!IsWindows) {\n      CometChatSoundModule.getSystemVolume(callback);\n    }\n    return this;\n  }\n  setSystemVolume(value) {\n    if (IsAndroid) {\n      CometChatSoundModule.setSystemVolume(value);\n    }\n    return this;\n  }\n  getPan() {\n    return this._pan;\n  }\n  getNumberOfLoops() {\n    return this._numberOfLoops;\n  }\n  setNumberOfLoops(value) {\n    this._numberOfLoops = value;\n    if (this._loaded) {\n      if (IsAndroid || IsWindows) {\n        CometChatSoundModule.setLooping(this._key, !!value);\n      } else {\n        CometChatSoundModule.setNumberOfLoops(this._key, value);\n      }\n    }\n    return this;\n  }\n  setSpeed(value) {\n    this._speed = value;\n    if (this._loaded) {\n      if (!IsWindows) {\n        CometChatSoundModule.setSpeed(this._key, value);\n      }\n    }\n    return this;\n  }\n  setPitch(value) {\n    this._pitch = value;\n    if (this._loaded) {\n      if (IsAndroid) {\n        CometChatSoundModule.setPitch(this._key, value);\n      }\n    }\n    return this;\n  }\n  getCurrentTime(callback) {\n    if (this._loaded) {\n      CometChatSoundModule.getCurrentTime(this._key, callback);\n    }\n  }\n  setCurrentTime(value) {\n    if (this._loaded) {\n      CometChatSoundModule.setCurrentTime(this._key, value);\n    }\n    return this;\n  }\n  // android only\n  setSpeakerphoneOn(value) {\n    if (IsAndroid) {\n      CometChatSoundModule.setSpeakerphoneOn(this._key, value);\n    }\n  }\n  isPlaying() {\n    return this._playing;\n  }\n  async checkOtherAudioPlaying() {\n    try{\n     return await CometChatSoundModule.checkOtherAudioPlaying();\n    }\n     catch(error){\n       return false;\n     } \n   }\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n// ios only\n\n// This is deprecated.  Call the static one instead.\n\nSound.prototype.setCategory = function(value) {\n  Sound.setCategory(value, false);\n}\n\nSound.MAIN_BUNDLE = CometChatSoundModule.MainBundlePath;\nSound.DOCUMENT = CometChatSoundModule.NSDocumentDirectory;\nSound.LIBRARY = CometChatSoundModule.NSLibraryDirectory;\nSound.CACHES = CometChatSoundModule.NSCachesDirectory;\n\nexport default Sound;"
  },
  {
    "path": "packages/ChatUiKit/src/shared/resources/index.ts",
    "content": "\nimport { CometChatSoundManager } from \"./CometChatSoundManager\";\n\nexport { CometChatSoundManager };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/services/stream-message.service.ts",
    "content": "import { Subject, of, BehaviorSubject, Subscription } from 'rxjs';\nimport { concatMap, delay, tap } from 'rxjs/operators';\nimport * as CometChatUIKitConstants from '../constants/UIKitConstants';\nimport { CometChatAIAssistantTools } from '../modals/CometChatAIAssistantTools';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\n\n/**\n * Interface representing streaming message data that contains both the original message event\n * and the accumulated streamed text content that has been processed so far.\n */\nexport interface IStreamData {\n  message: CometChat.AIAssistantBaseEvent,\n  streamedMessages?: string;\n}\n\n/**\n * RxJS subjects for managing message streaming\n */\nconst messageSubject = new Subject<IStreamData>();\nconst messageQueue = new Subject<{ runId: string, event: CometChat.AIAssistantBaseEvent }>();\n\n/**\n * Completion callbacks for each runId\n * Now supports aiToolResultMessage and aiToolArgumentMessage (as any, since not in SDK typings)\n */\nexport type QueueCompletionCallback = (\n  aiAssistantMessage?: CometChat.AIAssistantMessage,\n  aiToolResultMessage?: CometChat.AIToolResultMessage,\n  aiToolArgumentMessage?: CometChat.AIToolArgumentMessage\n) => void;\nconst queueCompletionCallbacks: { [runId: string]: QueueCompletionCallback } = {};\n\n/**\n * Store tool result and argument messages by runId\n */\nlet lastAIToolResultMessages: { [runId: string]: any } = {};\nlet lastAIToolArgumentMessages: { [runId: string]: any } = {};\n\n\n/**\n * Subscription for the message processing pipeline\n */\nlet subscription: Subscription | null;\n\n/**\n * Storage for accumulated content by message ID during streaming\n */\nlet streamedMessages: { [runId: string]: string } = {};\n\n\n/**\n * Per-runId event queue, matching Android logic\n */\nconst eventQueues: { [runId: string]: CometChat.AIAssistantBaseEvent[] } = {};\nlet lastAIAssistantMessages: Array<{ runId: string, message: CometChat.AIAssistantMessage }> = [];\n\n/**\n * Store AIAssistantMessage for a runId\n */\nexport const storeAIAssistantMessage = (runId: string, message: CometChat.AIAssistantMessage) => {\n  const existingIndex = lastAIAssistantMessages.findIndex(item => item.runId === runId);\n  if (existingIndex !== -1) {\n    lastAIAssistantMessages[existingIndex].message = message;\n  } else {\n    lastAIAssistantMessages.push({ runId, message });\n  }\n  checkQueueEmptyStatus(runId);\n};\n\n/**\n * Check and trigger queue completion for a runId\n */\n\n\nfunction checkQueueEmptyStatus(runId: string) {\n  const queue = eventQueues[runId];\n  if (\n    queue && queue.length === 0 &&\n    streamingComplete[runId] === true &&\n    renderingComplete[runId] === true &&\n    queueCompletionCallbacks[runId]\n  ) {\n    const aiMsgEntry = lastAIAssistantMessages.find(item => item.runId === runId);\n    const aiMsg = aiMsgEntry?.message;\n    const toolResult = lastAIToolResultMessages[runId]?.message;\n    const toolArg = lastAIToolArgumentMessages[runId]?.message;\n    const indexToDelete = lastAIAssistantMessages.findIndex(item => item.runId === runId);\n    if (indexToDelete !== -1) {\n      lastAIAssistantMessages.splice(indexToDelete, 1);\n    }\n    if (aiMsg || toolResult || toolArg) {\n      queueCompletionCallbacks[runId](aiMsg, toolResult, toolArg);\n    }\n  }\n}\n\nexport const checkAndTriggerQueueCompletion = (runId: string) => {\n  checkQueueEmptyStatus(runId);\n};\n\n/**\n * Storage for processed deltas to prevent duplicates\n */\nlet processedDeltas: { [runId: string]: Set<string> } = {};\n\n/**\n * Configurable typing speed delay for text message content chunks\n */\nlet streamSpeed = 30;\n\nlet toolKitActions: CometChatAIAssistantTools;\n\n/**\n * BehaviorSubject to track streaming state\n */\nconst streamingStateSubject = new BehaviorSubject<boolean>(false);\n\n/**\n * Observable stream for streaming state changes\n */\nexport const streamingState$ = streamingStateSubject.asObservable();\n\n/**\n * Observable stream for message updates\n */\nexport const messageStream = messageSubject.asObservable();\n\nconst toolEventsMap = [\n  CometChatUIKitConstants.streamMessageTypes.tool_call_args,\n  CometChatUIKitConstants.streamMessageTypes.tool_call_end,\n  CometChatUIKitConstants.streamMessageTypes.tool_call_result,\n  CometChatUIKitConstants.streamMessageTypes.tool_call_start\n];\n\n/**\n * Initializes the message processing pipeline with configurable delays\n * Processes messages sequentially with appropriate timing delays for different message types\n */\nconst initializeMessageProcessor = () => {\n  if (subscription) {\n    subscription.unsubscribe();\n  }\n  subscription = messageQueue.pipe(\n    concatMap(({ runId, event }) => {\n      let delayTime = 0;\n      if (event.getType() === CometChatUIKitConstants.streamMessageTypes.run_started) {\n        delayTime = 2000;\n      } else if (event.getType() === CometChatUIKitConstants.streamMessageTypes.tool_call_start) {\n        delayTime = 500; \n      } else if (event.getType() === CometChatUIKitConstants.streamMessageTypes.tool_call_args) {\n        delayTime = 500;\n      } else if (toolEventsMap.includes(event.getType())) {\n        delayTime = 100;\n      } else if (event.getType() === CometChatUIKitConstants.streamMessageTypes.text_message_start) {\n        delayTime = 2000;\n      } else if (event.getType() === CometChatUIKitConstants.streamMessageTypes.text_message_content) {\n        delayTime = streamSpeed;\n      } else if (event.getType() === CometChatUIKitConstants.streamMessageTypes.run_finished) {\n        streamingStateSubject.next(false);\n      }\n      return of({ runId, event }).pipe(\n        delay(delayTime),\n        tap(() => processMessage(runId, event))\n      );\n    })\n  ).subscribe();\n};\n\n// Initialize the processor on service load\ninitializeMessageProcessor();\n\n\n/**\n * Connection status subject and observable for broadcasting connection events\n */\nconst streamConnectionSubject = new BehaviorSubject<{ status: 'connected' | 'disconnected' | 'error', error?: any }>({ status: 'connected' });\nexport const streamConnection$ = streamConnectionSubject.asObservable();\n\n/**\n * Methods to be called by MessageList on connection events\n */\nexport const onConnected = () => {\n  streamConnectionSubject.next({ status: 'connected' });\n};\nexport const onDisconnected = () => {\n  streamConnectionSubject.next({ status: 'disconnected' });\n};\nexport const onConnectionError = (error?: any) => {\n  streamConnectionSubject.next({ status: 'error', error });\n};\n\n/**\n * Starts a new streaming message session for a required runId and attaches a listener callback\n * Resets accumulated content and initializes the message processor\n * @param runId - The runId for the streaming session (required)\n * @param listenerCallback - Callback for each event in the stream (optional)\n */\n/**\n * Starts a new streaming message session for a required runId and attaches a listener callback\n * Adds an onError callback for error handling.\n */\nexport const startStreamingForRunId = (\n  runId: string,\n  listenerCallback?: (event: CometChat.AIAssistantBaseEvent) => void,\n  onError?: (error: any) => void\n) => {\n  if (!runId) {\n    if (onError) onError(new Error('runId is required for startStreamingForRunId'));\n    else throw new Error('runId is required for startStreamingForRunId');\n    return;\n  }\n  streamedMessages[runId] = '';\n  processedDeltas[runId] = new Set();\n  if (listenerCallback) {\n    listeners[runId] = listenerCallback;\n  }\n  if (onError) {\n    errorListeners[runId] = onError;\n  }\n  initializeMessageProcessor();\n  streamingStateSubject.next(true);\n};\n\n/**\n * Internal error listeners map for runId-based error callbacks\n */\nconst errorListeners: { [runId: string]: (error: any) => void } = {};\n\n/**\n * Internal listeners map for runId-based event callbacks\n */\nconst listeners: { [runId: string]: (event: CometChat.AIAssistantBaseEvent) => void } = {};\n\n/**\n * Handles incoming websocket messages by adding them to the processing queue\n * @param msg - The message update to process\n */\nexport const handleWebsocketMessage = (msg: CometChat.AIAssistantBaseEvent, runId?: string) => {\n  const id = runId || (typeof msg.getMessageId === 'function' ? msg.getMessageId() : undefined);\n  messageQueue.next({ runId: String(id), event: msg });\n  if (listeners[String(id)]) {\n    listeners[String(id)](msg);\n  }\n};\n\n/**\n * Normalize and fix markdown fences\n */\nfunction fixMarkdownFences(text: string): string {\n  return text.replace(/(^|\\n)``(\\n|$)/g, '$1```$2');\n}\n\n/**\n * Auto-close unclosed fences at the end\n */\nfunction autoCloseFences(text: string): string {\n  const fenceCount = (text.match(/```/g) || []).length;\n  if (fenceCount % 2 !== 0) {\n    return text + \"\\n```\";\n  }\n  return text;\n}\n/**\n * Track if streaming is actually complete for each runId\n */\nlet streamingComplete: { [runId: string]: boolean } = {};\n\n/**\n * Track which streams have finished UI rendering\n */\nlet renderingComplete: { [runId: string]: boolean } = {};\n\n/**\n * Called by the UI component when it finishes rendering\n */\nexport const notifyStreamRenderComplete = (runId: string) => {\n  renderingComplete[runId] = true;\n  checkQueueEmptyStatus(runId);\n};\n\n\n\nconst processMessage = (runId: string, msg: CometChat.AIAssistantBaseEvent) => {\n  const type = msg.getType();\n  if (!streamedMessages[runId]) {\n    streamedMessages[runId] = '';\n    processedDeltas[runId] = new Set();\n  }\n  // Initialize event queue for runId if not present\n  if (!eventQueues[runId]) {\n    eventQueues[runId] = [];\n  }\n  // Add event to queue\n  eventQueues[runId].push(msg);\n\n  if (msg instanceof CometChat.AIAssistantMessage) {\n    const runIdToUse = msg.getRunId?.() || msg.getMessageId?.();\n    if (runIdToUse) {\n      storeAIAssistantMessage(String(runIdToUse), msg);\n    }\n  }\n  if ((msg as any).getType?.() === CometChatUIKitConstants.streamMessageTypes.tool_call_result) {\n    lastAIToolResultMessages[runId] = msg;\n    checkQueueEmptyStatus(runId);\n  }\n  if ((msg as any).getType?.() === CometChatUIKitConstants.streamMessageTypes.tool_call_args) {\n    lastAIToolArgumentMessages[runId] = msg;\n    checkQueueEmptyStatus(runId);\n  }\n  switch (type) {\n    case CometChatUIKitConstants.streamMessageTypes.run_started: {\n      messageSubject.next({ message: msg });\n      if (listeners[runId]) {\n        listeners[runId](msg);\n      }\n      break;\n    }\n    case CometChatUIKitConstants.streamMessageTypes.tool_call_start: {\n      messageSubject.next({ message: msg });\n      if (listeners[runId]) {\n        listeners[runId](msg);\n      }\n      break;\n    }\n    case CometChatUIKitConstants.streamMessageTypes.tool_call_args:\n    case CometChatUIKitConstants.streamMessageTypes.tool_call_result:\n    case CometChatUIKitConstants.streamMessageTypes.tool_call_end: {\n      messageSubject.next({ message: msg });\n      break;\n    }\n    case CometChatUIKitConstants.streamMessageTypes.text_message_start: {\n      messageSubject.next({ message: msg });\n      break;\n    }\n    case CometChatUIKitConstants.streamMessageTypes.text_message_content: {\n      const delta = (msg as CometChat.AIAssistantContentReceivedEvent).getDelta() || '';\n      if (!delta) return;\n      const current = streamedMessages[runId] || '';\n      if (delta.startsWith(current)) {\n        streamedMessages[runId] = delta;\n      } else if (delta.length > 0) {\n        let overlapIndex = -1;\n        for (let i = Math.min(current.length, delta.length); i > 0; i--) {\n          if (current.endsWith(delta.substring(0, i))) {\n            overlapIndex = i;\n            break;\n          }\n        }\n        streamedMessages[runId] = current + delta.substring(overlapIndex === -1 ? 0 : overlapIndex);\n      }\n      // ✅ live normalization of fences\n      const normalized = fixMarkdownFences(streamedMessages[runId]);\n      messageSubject.next({\n        message: msg,\n        streamedMessages: normalized,\n      });\n      break;\n    }\n    case CometChatUIKitConstants.streamMessageTypes.text_message_end: {\n      const finalText = autoCloseFences(fixMarkdownFences(streamedMessages[runId] || ''));\n      messageSubject.next({ message: msg, streamedMessages: finalText });\n      break;\n    }\n    case CometChatUIKitConstants.streamMessageTypes.run_finished: {\n      const finalText = autoCloseFences(fixMarkdownFences(streamedMessages[runId] || ''));\n      messageSubject.next({ message: msg, streamedMessages: finalText });\n      streamingStateSubject.next(false);\n      eventQueues[runId] = eventQueues[runId].filter(e => e !== msg);\n      streamingComplete[runId] = true;\n      checkQueueEmptyStatus(runId);\n\n      if (listeners[runId]) {\n        listeners[runId](msg);\n        delete listeners[runId];\n      }\n      break;\n    }\n    default:\n      messageSubject.next({ message: msg });\n  }\n  // Error handling: if message has error property, call onError\n  if ((msg as any).error && errorListeners[runId]) {\n    errorListeners[runId]((msg as any).error);\n    delete errorListeners[runId];\n  }\n  if (type !== CometChatUIKitConstants.streamMessageTypes.run_finished) {\n    eventQueues[runId] = eventQueues[runId].filter(e => e !== msg);\n\n    checkQueueEmptyStatus(runId);\n  }\n};\n\n/**\n * Registers a queue completion callback for a runId\n * Called when run_finished is processed for that runId\n */\nexport const setQueueCompletionCallback = (runId: string, callback: QueueCompletionCallback) => {\n  queueCompletionCallbacks[runId] = callback;\n};\n\n/**\n * Removes a queue completion callback for a runId\n */\nexport const removeQueueCompletionCallback = (runId: string) => {\n  if (queueCompletionCallbacks[runId]) {\n    delete queueCompletionCallbacks[runId];\n  }\n  if (errorListeners[runId]) {\n    delete errorListeners[runId];\n  }\n};\n\n/**\n * Sets the typing speed delay for text message content chunks\n * @param delay - The delay in milliseconds between text content chunks (default: 80ms)\n */\nexport const setStreamSpeed = (delay: number) => {\n  if (delay !== streamSpeed) {\n    streamSpeed = delay;\n  }\n};\n\n/**\n * Gets the current typing speed delay for text message content chunks\n * @returns The current delay in milliseconds\n */\nexport const getStreamSpeed = (): number => {\n  return streamSpeed;\n};\n\nexport const getAIAssistantTools = (): CometChatAIAssistantTools => {\n  return toolKitActions;\n};\n\nexport const setAIAssistantTools = (actions: CometChatAIAssistantTools): void => {\n  toolKitActions = actions;\n};\n\n/**\n * Stops the streaming message session and cleans up resources\n * Unsubscribes from the message processor and resets accumulated content\n */\nexport const stopStreamingForRunId = (runId?: string) => {\n  if (subscription) {\n    subscription.unsubscribe();\n    subscription = null;\n  }\n  if (runId) {\n    delete streamedMessages[runId];\n    delete processedDeltas[runId];\n    delete queueCompletionCallbacks[runId];\n    lastAIAssistantMessages = lastAIAssistantMessages.filter(item => item.runId !== runId);\n    delete lastAIToolResultMessages[runId];\n    delete lastAIToolArgumentMessages[runId];\n    delete streamingComplete[runId];\n    delete renderingComplete[runId];\n    delete errorListeners[runId];\n  } else {\n    streamedMessages = {};\n    processedDeltas = {};\n    streamingComplete = {};\n    renderingComplete = {};\n    lastAIAssistantMessages = [];\n    lastAIToolResultMessages = {};\n    lastAIToolArgumentMessages = {};\n    for (const key in queueCompletionCallbacks) {\n      delete queueCompletionCallbacks[key];\n    }\n    for (const key in errorListeners) {\n      delete errorListeners[key];\n    }\n  }\n  streamingStateSubject.next(false);\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/CometChatMessageHelper/index.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\n\nconst wordBoundary = {\n  start: `(?:^|:|;|'|\"|,|{|}|\\\\.|\\\\s|\\\\!|\\\\?|\\\\(|\\\\)|\\\\[|\\\\]|\\\\*)`,\n  end: `(?=$|:|;|'|\"|,|{|}|\\\\.|\\\\s|\\\\!|\\\\?|\\\\(|\\\\)|\\\\[|\\\\]|\\\\*)`,\n};\n\nexport const makeExtentionCall = (\n  extentionType: string,\n  callMethod: string,\n  extentionAction: string,\n  parameters: any\n) => {\n  return CometChat.callExtension(extentionType, callMethod, extentionAction, parameters);\n};\n\nexport const messageStatus = Object.freeze({\n  inprogress: \"inprogress\",\n  success: \"success\",\n  error: \"error\",\n});\n\nexport const emailPattern =\n  wordBoundary.start + `[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,6}` + wordBoundary.end;\n\nexport const urlPattern =\n  wordBoundary.start +\n  `((https?://|www\\\\.|pic\\\\.)[-\\\\w;/?:@&=+$\\\\|\\\\_.!~*\\\\|'()\\\\[\\\\]%#,☺]+[\\\\w/#](\\\\(\\\\))?)` +\n  wordBoundary.end;\n\nexport const phoneNumPattern =\n  wordBoundary.start +\n  `(?:\\\\+?(\\\\d{1,3}))?([-. (]*(\\\\d{3})[-. )]*)?((\\\\d{3})[-. ]*(\\\\d{2,4})(?:[-.x ]*(\\\\d+))?)` +\n  wordBoundary.end;\n\nexport const ID = () => {\n  // Math.random should be unique because of its seeding algorithm.\n  // Convert it to base 36 (numbers + letters), and grab the first 9 characters\n  // after the decimal.\n  return \"_\" + Math.random().toString(36).substr(2, 9);\n};\n\nexport const getUnixTimestamp = () => {\n  return Math.round(+new Date() / 1000);\n};\n\nexport const getUnixTimestampInMilliseconds = () => {\n  return Math.round(+new Date());\n};\n\nexport const formatBytes = (bytes: number, decimals = 2) => {\n  if (bytes === 0) return \"0 Bytes\";\n\n  const k = 1024;\n  const dm = decimals < 0 ? 0 : decimals;\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"];\n\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/CometChatMessagePreview/CometChatMessagePreview.tsx",
    "content": "import React, { JSX } from \"react\";\nimport { View, Text, TouchableOpacity, StyleProp, ViewStyle, Platform, StyleSheet } from \"react-native\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Styles } from \"./style\";\nimport { getCometChatTranslation } from \"../../resources/CometChatLocalizeNew/LocalizationManager\";\nimport { Icon } from \"../../icons/Icon\";\nimport { stripMarkdown, preparePreviewText } from \"../MarkdownUtils\";\nimport { CometChatRichTextFormatter } from \"../../formatters/CometChatRichTextFormatter\";\nimport { applyMentionsFormatting } from \"../MessageUtils\";\n\nconst t = getCometChatTranslation();\n\n/** Module-level formatter instance — reused across renders (SRP, no GC churn) */\nconst previewFormatter = new CometChatRichTextFormatter();\n\n/**\n * Props for CometChatMessagePreview component\n */\ninterface CometChatMessagePreviewProps {\n  // Old interface (for backward compatibility)\n  messagePreviewTitle?: string;\n  messagePreviewSubtitle?: string;\n  closeIconURL?: any;\n  onCloseClick?: () => void;\n  \n  // New interface (message-based)\n  message?: CometChat.BaseMessage;\n  theme?: any;\n  showCloseIcon?: boolean;\n  style?: StyleProp<ViewStyle>;\n  \n  // Icon support\n  subtitleIcon?: JSX.Element;\n  \n  // Title style override\n  titleStyle?: any;\n  \n  // Deleted message indicator\n  isDeletedMessage?: boolean;\n\n  // Mentions style override for reply preview context\n  mentionsStyle?: any;\n}\n\n/**\n *\n * CometChatMessagePreview\n *\n */\nconst CometChatMessagePreview = (props: CometChatMessagePreviewProps) => {\n  const {\n    messagePreviewTitle,\n    messagePreviewSubtitle,\n    closeIconURL,\n    onCloseClick,\n    message,\n    theme: propTheme,\n    showCloseIcon = false,\n    style,\n    subtitleIcon,\n    titleStyle,\n    isDeletedMessage = false,\n    mentionsStyle,\n  } = props;\n  \n  const theme = useTheme();\n  const finalTheme = propTheme || theme;\n\n  // Helper function to get message preview title (sender name)\n  const getMessagePreviewTitle = (): string => {\n    if (messagePreviewTitle) return messagePreviewTitle;\n    \n    if (!message) return \"\";\n    \n    try {\n      if (message && typeof message.getSender === 'function') {\n        const sender = message.getSender();\n        if (sender && typeof sender.getName === 'function') {\n          return sender.getName() || \"\";\n        }\n      }\n      // Fallback for cases where getSender is not available\n      if (message && (message as any).sender && (message as any).sender.name) {\n        return (message as any).sender.name;\n      }\n    } catch (error) {\n      console.warn(\"Error getting message sender:\", error);\n    }\n    return \"\";\n  };\n\n  // Helper function to get message preview subtitle/content\n  const getMessagePreviewSubtitle = (): string | JSX.Element => {\n    if (messagePreviewSubtitle) return messagePreviewSubtitle;\n    \n    if (!message) return \"\";\n    \n\n    if (props.isDeletedMessage) {\n      return t(\"MESSAGE_IS_DELETED\") || \"Message deleted\";\n    }\n    \n    try {\n      const messageType = typeof message.getType === 'function' ? message.getType() : (message as any).type;\n      const messageCategory = typeof message.getCategory === 'function' ? message.getCategory() : (message as any).category;\n\n      if (messageCategory === CometChat.CATEGORY_MESSAGE) {\n        switch (messageType) {\n          case CometChat.MESSAGE_TYPE.TEXT:\n            const textMessage = message as CometChat.TextMessage;\n            let text = (typeof textMessage.getText === 'function' ? textMessage.getText() : (textMessage as any).text) || \"\";\n\n            // Prepare text for preview: collapse code blocks, strip block markers, flatten to single line\n            const previewResult = preparePreviewText(text);\n            const cleanText = previewResult.text;\n            // Set inline code styles for the preview context — uses theme's textPrimary\n            // which is white for outgoing (set by getReplyView) and dark for composer tray.\n            previewFormatter.setStyle({\n              inlineCodeStyle: {\n                fontSize: 14,\n                fontWeight: '400',\n                color: finalTheme?.color?.textPrimary as string || '#141414',\n              },\n              inlineCodeContainerStyle: {\n                backgroundColor: finalTheme?.color?.previewInlineCodeBackground ?? finalTheme?.color?.background3 ?? 'rgba(120, 120, 128, 0.18)',\n                borderRadius: 4,\n                borderWidth: 0.5,\n                borderColor: finalTheme?.color?.borderDefault ?? 'rgba(120, 120, 128, 0.3)',\n              },\n            });\n            const formatted = previewFormatter.getFormattedText(cleanText || null);\n\n            // Resolve subtitle content\n            let subtitleContent: string | JSX.Element;\n            if (formatted && typeof formatted !== 'string') {\n              subtitleContent = formatted;\n            } else {\n              subtitleContent = (formatted as string) || stripMarkdown(text);\n            }\n\n            // Apply mentions formatter using shared helper (DRY — same as conversation list)\n            // mentionsStyle prop is passed from the caller (e.g., getReplyView in MessageDataSource)\n            subtitleContent = applyMentionsFormatting(textMessage, subtitleContent, text, mentionsStyle);\n\n            // For code blocks (first rich block): render compact code block container\n            if (previewResult.codeBlockFirstLine !== null) {\n              isCodeBlockPreview = true;\n              codeBlockLine = previewResult.codeBlockFirstLine;\n            }\n\n            // For blockquotes: set flag for render section to add container styling\n            if (previewResult.isBlockquote) {\n              isBlockquotePreview = true;\n            }\n\n            // For list items: set flag so render shows prefix + content with ellipsis\n            if (previewResult.listPrefix) {\n              isListPreview = true;\n              listPrefix = previewResult.listPrefix;\n            }\n\n            return subtitleContent;\n          \n          case CometChat.MESSAGE_TYPE.IMAGE:\n            const imageMessage = message as CometChat.MediaMessage;\n            const imageAttachment = typeof imageMessage.getAttachment === 'function' ? imageMessage.getAttachment() : (imageMessage as any).attachment;\n            return imageAttachment?.getName?.() || imageAttachment?.name || t(\"MESSAGE_IMAGE\") || \"Image\";\n          \n          case CometChat.MESSAGE_TYPE.VIDEO:\n            const videoMessage = message as CometChat.MediaMessage;\n            const videoAttachment = typeof videoMessage.getAttachment === 'function' ? videoMessage.getAttachment() : (videoMessage as any).attachment;\n            return videoAttachment?.getName?.() || videoAttachment?.name || t(\"MESSAGE_VIDEO\") || \"Video\";\n          \n          case CometChat.MESSAGE_TYPE.AUDIO:\n            const audioMessage = message as CometChat.MediaMessage;\n            const audioAttachment = typeof audioMessage.getAttachment === 'function' ? audioMessage.getAttachment() : (audioMessage as any).attachment;\n            return audioAttachment?.getName?.() || audioAttachment?.name || t(\"MESSAGE_AUDIO\") || \"Audio\";\n          \n          case CometChat.MESSAGE_TYPE.FILE:\n            const fileMessage = message as CometChat.MediaMessage;\n            const fileAttachment = typeof fileMessage.getAttachment === 'function' ? fileMessage.getAttachment() : (fileMessage as any).attachment;\n            return fileAttachment?.getName?.() || fileAttachment?.name || t(\"MESSAGE_FILE\") || \"File\";\n          \n          default:\n            return messageType || t(\"MESSAGE\") || \"Message\";\n        }\n      } else if (messageCategory === CometChat.CATEGORY_CUSTOM) {\n        // Handle custom message types with proper formatting\n        \n        if (messageType === \"extension_sticker\") {\n          return \"Sticker\";\n        } else if (messageType === \"extension_collaborative_whiteboard\" || messageType === \"extension_whiteboard\") {\n          return \"Collaborative Whiteboard\";\n        } else if (messageType === \"extension_document\") {\n          return \"Document\";\n        } else if (messageType === \"extension_poll\") {\n          return \"Poll\";\n        } else if (messageType === \"meeting\") {\n          return \"Meeting\";\n        } else if (messageType === \"extension_location\") {\n          return \"Location\";\n        } else {\n          // Convert snake_case or camelCase to Title Case, but remove \"extension_\" prefix\n          if (messageType) {\n            let formattedType = messageType;\n            \n            // Remove \"extension_\" prefix if present\n            if (formattedType.startsWith('extension_')) {\n              formattedType = formattedType.replace('extension_', '');\n            }\n            \n            return formattedType\n              .replace(/[_-]/g, ' ')\n              .replace(/([a-z])([A-Z])/g, '$1 $2')\n              .split(' ')\n              .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n              .join(' ');\n          }\n          return t(\"CUSTOM_MESSAGE\") || \"Custom Message\";\n        }\n      } else if (messageCategory === CometChat.CATEGORY_ACTION) {\n        return t(\"ACTION_MESSAGE\") || \"Action\";\n      }\n      \n      return t(\"MESSAGE\") || \"Message\";\n    } catch (error) {\n      console.warn(\"Error getting message preview subtitle:\", error);\n      return t(\"MESSAGE\") || \"Message\";\n    }\n  };\n\n  // Helper function to get auto-generated icon for attachment types\n  const getAutoIcon = (): JSX.Element | null => {\n    if (subtitleIcon) return null; // Don't auto-generate if icon is manually provided\n    if (!message) {\n      return null;\n    }\n    \n    try {\n      const messageType = typeof message.getType === 'function' ? message.getType() : (message as any).type;\n      const messageCategory = typeof message.getCategory === 'function' ? message.getCategory() : (message as any).category;\n\n      // Use theme colors safely\n      const iconColor = finalTheme?.palette?.getAccent600?.() || \n                       finalTheme?.color?.iconSecondary || \n                       finalTheme?.colors?.accent || \n                       \"#666666\"; // fallback color\n\n      if (messageCategory === CometChat.CATEGORY_MESSAGE) {\n        switch (messageType) {\n          case CometChat.MESSAGE_TYPE.IMAGE:\n            return <Icon name=\"photo-fill\" width={16} height={16} color={iconColor} />;\n          case CometChat.MESSAGE_TYPE.VIDEO:\n            return <Icon name=\"videocam-fill\" width={16} height={16} color={iconColor} />;\n          case CometChat.MESSAGE_TYPE.AUDIO:\n            return <Icon name=\"play-circle-fill\" width={16} height={16} color={iconColor} />;\n          case CometChat.MESSAGE_TYPE.FILE:\n            return <Icon name=\"description-fill\" width={16} height={16} color={iconColor} />;\n          default:\n        }\n      } else if (messageCategory === CometChat.CATEGORY_CUSTOM) {\n        // Handle custom message types with appropriate icons\n        let iconMessageType = messageType;\n        \n        // Normalize the message type by removing \"extension_\" prefix if present\n        if (iconMessageType && iconMessageType.startsWith('extension_')) {\n          iconMessageType = iconMessageType.replace('extension_', '');\n        }\n        \n        switch (iconMessageType) {\n          case \"sticker\":\n            return <Icon name=\"sticker-fill\" width={16} height={16} color={iconColor} />;\n          case \"collaborative_whiteboard\":\n          case \"whiteboard\":\n            return <Icon name=\"collaborative-whiteboard-fill\" width={16} height={16} color={iconColor} />;\n          case \"document\":\n            return <Icon name=\"collaborative-document-fill\" width={16} height={16} color={iconColor} />;\n          case \"poll\":\n            return <Icon name=\"poll-fill\" width={16} height={16} color={iconColor} />;\n          case \"meeting\":\n            return <Icon name=\"videocam-fill\" width={16} height={16} color={iconColor} />;\n          case \"location\":\n            return <Icon name=\"location-on-fill\" width={16} height={16} color={iconColor} />;\n          default:\n            return <Icon name=\"chat-fill\" width={16} height={16} color={iconColor} />;\n        }\n      }\n    } catch (error) {\n      console.warn(\"Error getting auto icon:\", error);\n    }\n    \n    return null;\n  };\n\n  // Mutable flags set by getMessagePreviewSubtitle when block types are detected\n  let isBlockquotePreview = false;\n  let isCodeBlockPreview = false;\n  let isListPreview = false;\n  let listPrefix = '';\n  let codeBlockLine = '';\n\n  let messageText: string | JSX.Element = getMessagePreviewSubtitle();\n  let title = getMessagePreviewTitle();\n  let autoIcon = getAutoIcon();\n\n  const shouldShowClose = showCloseIcon || onCloseClick;\n  const containerStyle = style ? [Styles(finalTheme).editPreviewContainerStyle, style] : Styles(finalTheme).editPreviewContainerStyle;\n  const iconToShow = subtitleIcon || autoIcon;\n\n  // Determine if subtitle is rich (JSX) or plain string\n  const isRichSubtitle = typeof messageText !== 'string';\n  // Detect if the formatter returned a View root (block-level content that\n  // slipped through preparePreviewText). View can't nest inside Text, so we\n  // render it in a height-constrained View wrapper instead.\n  const isViewRoot = isRichSubtitle && React.isValidElement(messageText) && (messageText as React.ReactElement<any>).type === View;\n\n  return (\n    <View style={containerStyle}>\n      {/* <View style={Styles(finalTheme).leftBar} /> */}\n      <View style={Styles(finalTheme).previewHeadingStyle}>\n        <Text style={[Styles(finalTheme).previewTitleStyle, titleStyle]}>{title}</Text>\n        {shouldShowClose && (\n          <TouchableOpacity\n            style={Styles(finalTheme).previewCloseStyle}\n            onPress={onCloseClick ?? (() => {})}\n          >\n            <Icon name='close' width={16} height={16} color={finalTheme.color.textPrimary} />\n          </TouchableOpacity>\n        )}\n      </View>\n      <View style={[{ flexDirection: 'row', alignItems: 'flex-start', gap: finalTheme?.spacing?.padding?.p1 }]}>\n        {iconToShow && <View>{iconToShow}</View>}\n        {isCodeBlockPreview ? (\n          <View style={previewBlockStyles.codeBlockRow}>\n            <View style={[previewBlockStyles.codeBlockBadge, { backgroundColor: (finalTheme?.color?.previewCodeBlockBackground ?? finalTheme?.color?.background2) as string || '#FAFAFA', borderColor: (finalTheme?.color?.previewCodeBlockBorder ?? finalTheme?.color?.borderDefault) as string || '#E8E8E8' }]}>\n              <Text numberOfLines={1} ellipsizeMode='tail' style={[previewBlockStyles.codeBlockText, { color: finalTheme?.color?.textPrimary as string || '#141414' }]}>\n                {codeBlockLine + '..'}\n              </Text>\n            </View>\n          </View>\n        ) : isBlockquotePreview ? (\n          <View style={previewBlockStyles.blockquoteRow}>\n            <View style={[previewBlockStyles.blockquoteBar, { backgroundColor: finalTheme.color.primaryColor as string }]} />\n            <Text numberOfLines={1} ellipsizeMode='tail' style={[Styles(finalTheme).previewSubTitleStyle, previewBlockStyles.blockquoteText]}>\n              {messageText}\n            </Text>\n          </View>\n        ) : isListPreview ? (\n          <Text numberOfLines={1} ellipsizeMode='tail' style={[Styles(finalTheme).previewSubTitleStyle, previewBlockStyles.flexOne]}>\n            {listPrefix}{messageText}{'...'}\n          </Text>\n        ) : isViewRoot ? (\n          <View style={previewBlockStyles.viewRootContainer}>\n            {messageText}\n          </View>\n        ) : (\n          <Text numberOfLines={1} ellipsizeMode='tail' style={[Styles(finalTheme).previewSubTitleStyle, previewBlockStyles.flexOne]}>\n            {messageText}\n          </Text>\n        )}\n      </View>\n    </View>\n  );\n};\n\nexport { CometChatMessagePreview };\n\n// Static styles for message preview block-level elements.\n// Theme-dependent values (colors) are applied inline via style array merging.\nconst previewBlockStyles = StyleSheet.create({\n  codeBlockRow: {\n    flex: 1,\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  codeBlockBadge: {\n    borderRadius: 4,\n    borderWidth: 1,\n    paddingHorizontal: 8,\n    paddingVertical: 3,\n    flexShrink: 1,\n  },\n  codeBlockText: {\n    fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',\n    fontSize: 12,\n  },\n  blockquoteRow: {\n    flex: 1,\n    flexDirection: 'row',\n    alignItems: 'stretch',\n    backgroundColor: 'rgba(104, 81, 214, 0.08)',\n    borderRadius: 8,\n    minHeight: 26,\n    paddingVertical: 2,\n  },\n  blockquoteBar: {\n    width: 4,\n    borderRadius: 2,\n    marginVertical: 4,\n    marginLeft: 4,\n  },\n  blockquoteText: {\n    flex: 1,\n    marginBottom: 0,\n    paddingHorizontal: 6,\n    lineHeight: 22,\n  },\n  flexOne: {\n    flex: 1,\n  },\n  viewRootContainer: {\n    flex: 1,\n    overflow: 'hidden',\n    maxHeight: 28,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/CometChatMessagePreview/index.ts",
    "content": "import { CometChatMessagePreview } from \"./CometChatMessagePreview\";\n\nexport { CometChatMessagePreview }"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/CometChatMessagePreview/resources/index.ts",
    "content": "import Close from \"./close.png\";\n\nexport { Close };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/CometChatMessagePreview/style.ts",
    "content": "import { StyleSheet, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport const Styles = (theme: CometChatTheme) => {\n  return StyleSheet.create({\n    editPreviewContainerStyle: {\n      borderRadius: theme.spacing.radius.r2,\n      padding: theme.spacing.padding.p2,\n      marginBottom: theme.spacing.margin.m1,\n      backgroundColor: theme.color.background3,\n      borderWidth: theme.spacing.spacing.s0_5 / 2,\n      borderColor: theme.color.borderDefault,\n    },\n    previewHeadingStyle: {\n      marginBottom: 5,\n      paddingTop: 5,\n    },\n    previewTitleStyle: {\n      color: theme.color.textPrimary,\n      letterSpacing: 0.5,\n      ...theme.typography.body.regular\n    },\n\n    previewSubTitleStyle: {\n      color: theme.color.textSecondary,\n      letterSpacing: 0.5,\n      marginBottom: 5,\n      ...theme.typography.caption1.regular\n    },\n    previewCloseStyle: {\n      position: \"absolute\",\n      top: 5,\n      right: 5,\n      width: 16,\n      height: 16\n    },\n    previewCloseIconStyle: {\n      width: 16,\n      height: 16,\n      tintColor: theme.color.iconPrimary\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/CommonUtils.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { IMAGE_PREFETCH_MAX_ATTEMPTS, IMAGE_PREFETCH_TIMEOUT } from \"../constants/UIKitConstants\";\nimport { Image } from \"react-native\";\n\nexport class CommonUtils {\n  static clone<T extends any>(arg: T, seen = new WeakMap()): T {\n    if (typeof arg !== \"object\" || !arg) {\n      return arg;\n    }\n\n    // Handle circular references\n    if (seen.has(arg)) {\n      return seen.get(arg);\n    }\n\n    let res: any;\n    if (Array.isArray(arg)) {\n      res = [];\n      seen.set(arg, res);\n      for (const value of arg) {\n        res.push(CommonUtils.clone(value, seen));\n      }\n      return res as T;\n    } else {\n      res = {};\n      seen.set(arg, res);\n      const descriptor = Object.getOwnPropertyDescriptors(arg);\n\n      for (const k of Reflect.ownKeys(descriptor)) {\n        const curDescriptor = descriptor[k as any];\n        if (curDescriptor.hasOwnProperty(\"value\")) {\n          Object.defineProperty(res, k, {\n            ...curDescriptor,\n            value: CommonUtils.clone(curDescriptor[\"value\"], seen),\n          });\n        } else {\n          Object.defineProperty(res, k, curDescriptor);\n        }\n      }\n\n      Object.setPrototypeOf(res, Object.getPrototypeOf(arg));\n    }\n\n    return res as T;\n  }\n\n  static mergeObjects(obj1: object, obj2: object) {\n    // Create a new instance of the same class as obj1\n    let merged = Object.create(Object.getPrototypeOf(obj1));\n\n    // Copy properties from obj1 to the new instance\n    Object.assign(merged, obj1);\n\n    // Copy properties from obj2 to the new instance\n    Object.assign(merged, obj2);\n\n  // Preserve SDK message class identity after merge.\n    // When obj2 is a raw JSON object from the REST API history fetch,\n    // it contains flat `category` and `type` own properties (e.g. \"message\"/\"text\")\n    const protectedProps = ['category', 'type'];\n    for (const prop of protectedProps) {\n      const getterName = `get${prop.charAt(0).toUpperCase() + prop.slice(1)}`;\n      if (\n        typeof (obj1 as any)[getterName] === 'function' &&\n        typeof (obj2 as any)[getterName] !== 'function'\n      ) {\n        const correctValue = (obj1 as any)[getterName]();\n        if (correctValue !== undefined && (merged as any)[prop] !== correctValue) {\n          (merged as any)[prop] = correctValue;\n        }\n      }\n    }\n\n    return merged;\n  }\n\n  /**\n   * Merge two arrays of objects based on provided keys\n   */\n  static mergeArrays(arr1: Array<object>, arr2: Array<object>, keys: string[]) {\n    let map = new Map<string, any>();\n\n    const getKeyValues = (obj: any, keys: string[]) => {\n      const keyValues: { [key: string]: string } = {};\n      keys.forEach((key) => {\n        if (obj[key]) {\n          keyValues[key] = obj[key];\n        }\n      });\n      return keyValues;\n    };\n\n    // Add objects from arr1 to the map\n    arr1.forEach((obj: any) => {\n      const keyValues = getKeyValues(obj, keys);\n      if (Object.keys(keyValues).length === 0) {\n        // No keys present, use a fallback unique key\n        map.set(\"\" + Date.now() + Math.random(), obj);\n      } else {\n        // Store the object using the stringified key-value pairs as the key\n        const compositeKey = JSON.stringify(keyValues);\n        map.set(compositeKey, obj);\n      }\n    });\n\n    // Process arr2 and merge with arr1 if there are matching values for any key\n    arr2.forEach((obj: any) => {\n      const keyValues = getKeyValues(obj, keys);\n\n      let foundMatch = false;\n\n      // Iterate through the map to find matches\n      for (const [mapKey, mapObj] of map.entries()) {\n        const mapKeyValues = JSON.parse(mapKey);\n\n        // Check for matches with any key-value pair in the object from arr2\n        for (const keyInArr2 in keyValues) {\n          const valueInArr2 = keyValues[keyInArr2]; // Corresponding value\n\n          // Check if the current key from arr2 has a matching value in map's key values\n          if (mapKeyValues[keyInArr2] === valueInArr2) {\n            // If match is found, merge the objects and update the map\n            const mergedObj = this.mergeObjects(mapObj, obj);\n            map.set(mapKey, mergedObj);\n            foundMatch = true;\n            break; // Exit inner loop once a match is found\n          }\n        }\n\n        if (foundMatch) break; // Exit outer loop if a match was found\n      }\n\n      if (!foundMatch) {\n        // No match, add object from arr2 to the map\n        const compositeKey = JSON.stringify(\n          Object.keys(keyValues).length ? keyValues : { key: Date.now() + Math.random() }\n        );\n        map.set(compositeKey, obj);\n      }\n    });\n\n    // Return the merged array from the map values\n    return Array.from(map.values());\n  }\n\n  static getComponentIdFromMessage(message: CometChat.BaseMessage): Object {\n    let id: any = {};\n    if (message.getReceiver() instanceof CometChat.User) {\n      id[\"uid\"] = message.getSender().getUid();\n    } else if (message.getReceiver() instanceof CometChat.Group) {\n      id[\"guid\"] = (message.getReceiver() as CometChat.Group).getGuid();\n    }\n    if (message.getParentMessageId() && message.getParentMessageId() !== 0) {\n      id[\"parentMessageId\"] = message.getParentMessageId();\n    }\n    return id;\n  }\n\n  static checkIdBelongsToThisComponent(\n    id: any,\n    user: CometChat.User,\n    group: CometChat.Group,\n    parentMessageId: string | number\n  ): boolean {\n    if (id) {\n      if (id[\"parentMessageId\"] && id[\"parentMessageId\"] != parentMessageId) return false;\n      if ((id[\"uid\"] || user) && id[\"uid\"] != user?.getUid()) return false;\n      if ((id[\"guid\"] || group) && id[\"guid\"] != group?.getGuid()) return false;\n    }\n    return true;\n  }\n\n  static async prefetchThumbnail(\n    url: string,\n    attempts: number = IMAGE_PREFETCH_MAX_ATTEMPTS\n  ): Promise<boolean> {\n    if (attempts <= 0) return false;\n\n    try {\n      // Attempt to prefetch the thumbnail image\n      const result = await Image.prefetch(url);\n      return result;\n    } catch (error) {\n      // console.log(\n      //   `Failed to prefetch ${url}. Retrying in ${IMAGE_PREFETCH_TIMEOUT}ms... (${\n      //     IMAGE_PREFETCH_MAX_ATTEMPTS - attempts + 1\n      //   })`\n      // );\n\n      // Wait for IMAGE_PREFETCH_TIMEOUT before retrying\n      await new Promise<void>((resolve) => setTimeout(resolve, IMAGE_PREFETCH_TIMEOUT));\n\n\n      // Retry with reduced attempt count\n      return CommonUtils.prefetchThumbnail(url, attempts - 1);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/MarkdownUtils.ts",
    "content": "/**\n * Utility functions for markdown text processing.\n */\n\n/**\n * Strips markdown syntax characters from text, returning clean readable content.\n *\n * Handles:\n *  - Code blocks: ```code``` → code\n *  - Inline code: `code` → code\n *  - Bold: **text** → text\n *  - Underline: __text__ → text\n *  - Strikethrough: ~~text~~ → text\n *  - Italic: _text_ → text\n *  - Blockquotes: > text → text\n *  - Bullet lists: - text → text\n *  - Numbered lists: 1. text → text\n *\n * @param text - The markdown text to strip\n * @returns Clean text without markdown syntax characters\n */\nexport function stripMarkdown(text: string): string {\n  if (!text) return text;\n\n  let result = text;\n\n  // 1. Remove code block fences (``` on their own lines or inline ```)\n  result = result.replace(/```[\\s\\S]*?```/g, (match) => {\n    return match.slice(3, -3).trim();\n  });\n\n  // 1.5. Convert markdown links [text](url) → text\n  result = result.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n\n  // 2. Remove inline code backticks (normal and escaped)\n  result = result.replace(/\\\\`([^`]*?)\\\\`/g, '$1'); // escaped \\`code\\`\n  result = result.replace(/`([^`]*)`/g, '$1');       // normal `code`\n\n  // 3. Remove bold **text** (normal and escaped)\n  result = result.replace(/\\\\\\*\\\\\\*(.+?)\\\\\\*\\\\\\*/g, '$1'); // escaped \\*\\*bold\\*\\*\n  result = result.replace(/\\*\\*(.+?)\\*\\*/g, '$1');          // normal **bold**\n\n  // 4. Remove underline __text__ (must come before italic _ handling)\n  result = result.replace(/\\\\__(.+?)\\\\__/g, '$1');  // escaped\n  result = result.replace(/__(.+?)__/g, '$1');       // normal\n\n  // 5. Remove strikethrough ~~text~~\n  result = result.replace(/\\\\~~(.+?)\\\\~~/g, '$1');  // escaped\n  result = result.replace(/~~(.+?)~~/g, '$1');       // normal\n\n  // 6. Remove italic _text_\n  result = result.replace(/(?<![a-zA-Z0-9])_(.+?)_(?![a-zA-Z0-9])/g, '$1');\n\n  // 7. Remove blockquote markers at start of lines\n  result = result.replace(/^>\\s?/gm, '');\n\n  // 8. Remove bullet list markers at start of lines\n  result = result.replace(/^-\\s/gm, '');\n\n  // 9. Remove numbered list markers at start of lines\n  result = result.replace(/^\\d+\\.\\s/gm, '');\n\n  // 10. Strip HTML tags (e.g., <u>, <b>, <i>, <s>, <em>, <strong>, <del>, <br>, etc.)\n  // Preserve CometChat mention tokens <@uid:xxx> and <@all:xxx> which look like HTML tags\n  result = result.replace(/<br\\s*\\/?>/gi, '\\n');\n  result = result.replace(/<(?!@(?:uid|all):)[^>]+>/g, '');\n\n  // 11. Remove any remaining backslash escapes (e.g., \\* \\_ \\` \\~)\n  result = result.replace(/\\\\([*_`~>\\\\])/g, '$1');\n\n  return result;\n}\n\n\n/**\n * Result from preparePreviewText with metadata about detected block types.\n */\nexport interface PreviewTextResult {\n  text: string;\n  isBlockquote: boolean;\n  /** First line of a fenced code block (if the first rich block is a code block) */\n  codeBlockFirstLine: string | null;\n  /** Display prefix for list items (e.g. \"1. \" or \"- \") — kept separate so the\n   *  formatter won't re-parse the text as a block element. */\n  listPrefix: string | null;\n}\n\n/**\n * Prepare message text for compact single-line preview display.\n * Detects the FIRST block-level element and returns only its first line.\n * Priority: blockquote > code block > ordered list > bullet list > plain text\n * Everything after the first block element is discarded.\n *\n * For list items and blockquotes, the returned `text` contains ONLY the item\n * content (no `> `, `- `, or `1. ` prefix) so the formatter won't re-parse\n * it as a block element. The `listPrefix` field carries the display prefix.\n */\nexport function preparePreviewText(text: string): PreviewTextResult {\n  const lines = text.split('\\n');\n\n  for (let i = 0; i < lines.length; i++) {\n    const trimmed = lines[i].trim();\n    if (trimmed.length === 0) continue;\n\n    // Blockquote\n    if (trimmed.startsWith('> ') || trimmed.startsWith('▎ ')) {\n      return { text: trimmed.substring(2), isBlockquote: true, codeBlockFirstLine: null, listPrefix: null };\n    }\n\n    // Fenced code block\n    if (trimmed.startsWith('```')) {\n      const afterOpen = trimmed.substring(3);\n      const closeIdx = afterOpen.indexOf('```');\n      let firstLine: string;\n      if (closeIdx > 0) {\n        firstLine = afterOpen.substring(0, closeIdx).trim();\n      } else {\n        firstLine = '';\n        for (let j = i + 1; j < lines.length; j++) {\n          if (lines[j].trim().startsWith('```')) break;\n          if (lines[j].trim().length > 0) { firstLine = lines[j].trim(); break; }\n        }\n      }\n      return { text: '', isBlockquote: false, codeBlockFirstLine: firstLine, listPrefix: null };\n    }\n\n    // Ordered list — content only, prefix in listPrefix\n    const orderedMatch = trimmed.match(/^(\\d+)\\.\\s(.*)$/);\n    if (orderedMatch) {\n      return { text: orderedMatch[2].trim(), isBlockquote: false, codeBlockFirstLine: null, listPrefix: `${orderedMatch[1]}. ` };\n    }\n\n    // Bullet list — content only, prefix in listPrefix\n    if (trimmed.startsWith('- ')) {\n      return { text: trimmed.substring(2).trim(), isBlockquote: false, codeBlockFirstLine: null, listPrefix: '- ' };\n    }\n\n    // Plain text — collapse any inline code blocks\n    let result = lines[i];\n    result = result.replace(/```\\n?([\\s\\S]*?)```/g, (_m, c: string) => {\n      const fl = c.split('\\n')[0].trim();\n      return fl ? '`' + fl + '..' + '`' : '';\n    });\n    return { text: result.trim(), isBlockquote: false, codeBlockFirstLine: null, listPrefix: null };\n  }\n\n  return { text: text.trim(), isBlockquote: false, codeBlockFirstLine: null, listPrefix: null };\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/MentionUtils.ts",
    "content": "/**\n * Shared mention utility functions used by both CometChatMessageComposer\n * and CometChatSingleLineMessageComposer for cursor positioning around mentions.\n */\n\n/**\n * Check if cursor is within a mention range.\n */\nexport function isCursorWithinMentionRange(\n  mentionRanges: Map<string, any>,\n  cursorPosition: number\n): boolean {\n  for (const [range] of mentionRanges) {\n    const [start, end] = range.split('_').map(Number);\n    if (cursorPosition >= start && cursorPosition <= end) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Get the mention range boundaries if cursor is within a mention.\n * Used to snap cursor to the nearest edge (WhatsApp-style behavior).\n */\nexport function getMentionRangeAtCursor(\n  mentionRanges: Map<string, any>,\n  cursorPosition: number\n): { start: number; end: number } | null {\n  for (const [range] of mentionRanges) {\n    const [start, end] = range.split('_').map(Number);\n    if (cursorPosition >= start && cursorPosition <= end) {\n      return { start, end };\n    }\n  }\n  return null;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/MessageReceiptUtils.ts",
    "content": "import { MessageReceipt } from \"../constants/UIKitConstants\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { getModerationStatus } from \"./MessageUtils\";\n\n/**\n * Utility helper for deriving a message receipt status from a BaseMessage instance.\n */\nexport const MessageReceiptUtils = {\n  getReceiptStatus(message: CometChat.BaseMessage | undefined): MessageReceipt {\n    if (!message) return MessageReceipt.WAIT;\n\n    // Moderation status (if available)\n    const moderationStatus = getModerationStatus(message);\n    if (moderationStatus === \"disapproved\") return MessageReceipt.ERROR;\n    if (moderationStatus === \"pending\") return MessageReceipt.WAIT;\n\n    // Explicit error markers\n    const hasError = (message as any)?.error || (message as any)?.metadata?.error;\n    if (hasError) return MessageReceipt.ERROR;\n\n    // Deleted messages treated as error for receipts\n    if (message.getDeletedAt?.()) return MessageReceipt.ERROR;\n\n    // Use getter methods for receipt state (SDK objects expose these via getters)\n    if (message.getReadAt?.()) return MessageReceipt.READ;\n    if (message.getDeliveredAt?.()) return MessageReceipt.DELIVERED;\n    if (message.getSentAt?.()) return MessageReceipt.SENT;\n\n    return MessageReceipt.WAIT;\n  },\n};\n\nexport default MessageReceiptUtils;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/MessageUtils.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX } from \"react\";\nimport { ColorValue, PixelRatio, Text, View } from \"react-native\";\nimport { MessageBubbleAlignmentType } from \"../base\";\nimport {\n  CometChatCustomMessageTypes,\n  MessageCategoryConstants,\n  MentionsTargetElement,\n  MessageReceipt,\n  MessageTypeConstants,\n} from \"../constants/UIKitConstants\";\nimport { ChatConfigurator } from \"../framework\";\nimport { Icon, IconName } from \"../icons/Icon\";\nimport { CometChatMessageTemplate } from \"../modals\";\nimport { CometChatMessageBubble } from \"../views/CometChatMessageBubble\";\nimport { BubbleStyles, CometChatTheme, OutgoingBubbleStyles } from \"../../theme/type\";\nimport { CometChatUIKit } from \"../CometChatUiKit/CometChatUIKit\";\nimport { SuggestionItem } from \"../views/CometChatSuggestionList/SuggestionItem\";\nimport { CommonUtils } from \"./CommonUtils\";\nimport { deepMerge } from \"../helper/helperFunctions\";\nimport { CometChatDate } from \"../views/CometChatDate\";\nimport { CometChatAvatar, CometChatReceipt } from \"../views\";\nimport { useCometChatTranslation } from \"../resources/CometChatLocalizeNew\";\nimport { useTheme } from \"../../theme\";\n\ntype MessageViewParamsType = {\n  message: CometChat.BaseMessage;\n  templates?: CometChatMessageTemplate[];\n  alignment?: MessageBubbleAlignmentType;\n  theme: CometChatTheme;\n  isThreaded?: boolean;\n  datePattern?: (message: CometChat.BaseMessage) => string;\n  receiptsVisibility?: boolean;\n  avatarVisibility?: boolean;\n};\n\nconst getOverridenBubbleStyles = (theme: CometChatTheme) => {\n  const styleCache = new Map();\n  const outgoingBubbleStyles = theme.messageListStyles.outgoingMessageBubbleStyles;\n  const incomingBubbleStyles = theme.messageListStyles.incomingMessageBubbleStyles;\n\n  styleCache.set(MessageTypeConstants.text, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.textBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.textBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.image, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.imageBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.imageBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.file, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.fileBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.fileBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.audio, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.audioBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.audioBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.messageDeleted, {\n    incoming: deepMerge(\n      incomingBubbleStyles,\n      theme.deletedBubbleStyles ?? {},\n      incomingBubbleStyles.deletedBubbleStyles ?? {}\n    ),\n    outgoing: deepMerge(\n      outgoingBubbleStyles,\n      theme.deletedBubbleStyles ?? {},\n      outgoingBubbleStyles.deletedBubbleStyles ?? {}\n    ),\n  });\n\n  styleCache.set(MessageTypeConstants.sticker, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.stickerBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.stickerBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.document, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.collaborativeBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.collaborativeBubbleStyles ?? {}),\n  });\n\n  styleCache.set(CometChatCustomMessageTypes.meeting, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.meetCallBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.meetCallBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.whiteboard, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.collaborativeBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.collaborativeBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.video, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.videoBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.videoBubbleStyles ?? {}),\n  });\n\n  styleCache.set(MessageTypeConstants.poll, {\n    incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.pollBubbleStyles ?? {}),\n    outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.pollBubbleStyles ?? {}),\n  });\n\n  return styleCache;\n};\n\nconst getTemplatesMap = (templates: CometChatMessageTemplate[]) => {\n  let templatesMap = new Map<string, CometChatMessageTemplate>();\n  templates.forEach((template) => {\n    if (templatesMap.get(`${template.category}_${template.type}`)) return;\n    templatesMap.set(`${template.category}_${template.type}`, template);\n  });\n  return templatesMap;\n};\n\nconst MessageContentView = (props: {\n  message: CometChat.BaseMessage;\n  alignment: MessageBubbleAlignmentType;\n  theme: CometChatTheme;\n}): JSX.Element | null => {\n  const { message, alignment, theme } = props;\n\n  switch (message.getType()) {\n    case MessageTypeConstants.audio:\n      return ChatConfigurator.dataSource.getAudioMessageContentView(message, alignment, theme);\n    case MessageTypeConstants.video:\n      return ChatConfigurator.dataSource.getVideoMessageContentView(message, alignment, theme);\n    case MessageTypeConstants.file:\n      return ChatConfigurator.dataSource.getFileMessageContentView(message, alignment, theme);\n    case MessageTypeConstants.text:\n      return ChatConfigurator.dataSource.getTextMessageContentView(message, alignment, theme);\n    case MessageTypeConstants.image:\n      return ChatConfigurator.dataSource.getImageMessageContentView(message, alignment, theme);\n  }\n  return null;\n};\n\nconst getLeadingView = (\n  item: CometChat.BaseMessage,\n  theme: CometChatTheme,\n  avatarVisibility = true\n): JSX.Element | undefined => {\n  if (!avatarVisibility) return undefined;\n  let _style = getBubbleStyle(item, theme);\n  if (\n    item.getSender()?.getUid() !== CometChatUIKit.loggedInUser?.getUid() &&\n    item.getCategory() != MessageCategoryConstants.action\n  ) {\n    return (\n      <CometChatAvatar\n        image={\n          item?.getSender()?.getAvatar && item?.getSender()?.getAvatar()\n            ? { uri: item.getSender().getAvatar() }\n            : undefined\n        }\n        name={\n          item?.getSender()?.getName && item?.getSender()?.getName()\n            ? item?.getSender()?.getName()\n            : \"\"\n        }\n        style={_style.avatarStyle}\n      />\n    );\n  }\n  return undefined;\n};\n\nconst getHeaderView = (\n  item: CometChat.BaseMessage | any,\n  theme: CometChatTheme\n): JSX.Element | undefined => {\n  const _style = getBubbleStyle(item, theme);\n  if (\n    item.getSender()?.getUid() != CometChatUIKit.loggedInUser?.getUid() &&\n    ![MessageCategoryConstants.action, MessageCategoryConstants.call].includes(item.getCategory())\n  ) {\n    const senderName = (item.getSender()?.getName() || \"\").trim();\n    return (\n      <View style={{flexDirection: \"row\" }}>\n        {Boolean(senderName) && (\n          <Text style={_style.senderNameTextStyles} numberOfLines={1} ellipsizeMode={\"tail\"}>\n            {senderName}\n          </Text>\n        )}\n      </View>\n    );\n  }\n  return undefined;\n};\nconst getBubbleStyle = (item: CometChat.BaseMessage, theme: CometChatTheme): BubbleStyles => {\n  const loggedInUser = CometChatUIKit.loggedInUser!;\n  const type = (() => {\n    if (item.getDeletedAt()) {\n      return MessageTypeConstants.messageDeleted;\n    }\n    return item.getType();\n  })();\n\n  if (item.getSender().getUid() != loggedInUser.getUid()) {\n    return (\n      getOverridenBubbleStyles(theme).get(type)?.incoming ??\n      theme.messageListStyles.incomingMessageBubbleStyles\n    );\n  }\n\n  return (\n    getOverridenBubbleStyles(theme).get(type)?.outgoing ??\n    theme.messageListStyles.outgoingMessageBubbleStyles\n  );\n};\n\nconst getSentAtTimestamp = (item: any) => {\n  return item.getSentAt() ? item.getSentAt() * 1000 : Date.now();\n};\n\nconst getStatusInfoView = (\n  item:\n    | CometChat.TextMessage\n    | CometChat.MediaMessage\n    | CometChat.CustomMessage\n    | CometChat.InteractiveMessage\n    | CometChat.BaseMessage\n    | any,\n  theme: CometChatTheme,\n  receiptsVisibility: boolean = true,\n  datePattern?: (message: CometChat.BaseMessage) => string\n): JSX.Element | undefined => {\n  const loggedInUser = CometChatUIKit.loggedInUser!;\n\n  let isOutgoingMessage = item.getSender()?.getUid() == loggedInUser.getUid();\n  let _style = getBubbleStyle(item, theme);\n\n  let messageState;\n  if (item.getReadAt()) messageState = \"READ\";\n  else if (item.getDeliveredAt()) messageState = \"DELIVERED\";\n  else if (item.getSentAt()) messageState = \"SENT\";\n  else if (item?.getData()?.metaData?.error) messageState = \"ERROR\";\n  else if (isOutgoingMessage) messageState = \"WAIT\";\n  else messageState = \"ERROR\";\n\n  return (\n    <View\n      style={[\n        {\n          flexDirection: \"row\",\n          justifyContent: \"flex-end\",\n          alignSelf: \"flex-end\",\n        },\n        _style.dateReceiptContainerStyle,\n      ]}\n    >\n      <CometChatDate\n        timeStamp={(item.getDeletedAt() || item.getSentAt()) * 1000 || getSentAtTimestamp(item)}\n        pattern={\"timeFormat\"}\n        customDateString={datePattern && datePattern(item)}\n        style={_style.dateStyles}\n      />\n      {receiptsVisibility && isOutgoingMessage ? (\n        /* ToDoM Use Icon From Incoming/Outgoing bubble styles */\n        <View style={{ marginLeft: 2, alignItems: \"center\", justifyContent: \"center\" }}>\n          <CometChatReceipt\n            receipt={messageState as MessageReceipt}\n            style={{\n              sentIconStyle: _style.receiptStyles.sentIconStyle,\n              readIconStyle: _style.receiptStyles.readIconStyle,\n              waitIconStyle: _style.receiptStyles.waitIconStyle,\n              errorIconStyle: _style.receiptStyles.errorIconStyle,\n              deliveredIconStyle: _style.receiptStyles.deliveredIconStyle,\n            }}\n          />\n        </View>\n      ) : null}\n    </View>\n  );\n};\n\nexport const MessageUtils = {\n  getMessageView: (params: MessageViewParamsType) => {\n    const {\n      message,\n      templates,\n      alignment,\n      theme,\n      datePattern,\n      receiptsVisibility,\n      avatarVisibility,\n    } = params;\n    const templatesMap = getTemplatesMap(templates!);\n    const baseStyle = getBubbleStyle(message, theme);\n    \n    let hasTemplate = templatesMap.get(`${message.getCategory()}_${message.getType()}`);\n    if (templates!.length > 0) {\n      let customTemplate = templates!.find(\n        (template) =>\n          template.type == message.getType() && template.category == message.getCategory()\n      );\n      if (customTemplate) hasTemplate = customTemplate;\n    }\n\n    // Only show moderation bottom view for outgoing disapproved messages\n    const moderationStatus = getModerationStatus(message);\n    const isOutgoing = message.getSender()?.getUid() === CometChatUIKit.loggedInUser?.getUid();\n    const styleForThisMessage =\n      isOutgoing && moderationStatus === \"disapproved\"\n        ? deepMerge(baseStyle, {\n            containerStyle: {\n              borderBottomLeftRadius: 0,\n              borderBottomRightRadius: 0,\n              overflow: \"hidden\",\n            },\n          })\n        : baseStyle;\n\n    const DefaultModerationBottomView =\n      isOutgoing && moderationStatus === \"disapproved\" ? (\n        <ModerationBottomView status={moderationStatus} />\n      ) : undefined;\n\n    return (\n      <CometChatMessageBubble\n        id={`${message.getId()}`}\n        alignment={alignment}\n        ContentView={\n          hasTemplate!.ContentView!(message, alignment!) ||\n          MessageContentView({ message, alignment: alignment!, theme: theme! })\n        }\n        LeadingView={\n          message.getReceiverType() === \"group\"\n            ? getLeadingView(message, theme, avatarVisibility)\n            : undefined\n        }\n        HeaderView={\n          message.getReceiverType() === \"group\" ? getHeaderView(message, theme) : undefined\n        }\n        BottomView={\n          DefaultModerationBottomView ||\n          (hasTemplate?.BottomView && hasTemplate?.BottomView(message, alignment!))\n        }\n        StatusInfoView={\n          (hasTemplate?.StatusInfoView && hasTemplate!.StatusInfoView(message, alignment!)) ||\n          getStatusInfoView(message, theme, receiptsVisibility, datePattern)\n        }\n        style={styleForThisMessage}\n      />\n    );\n  },\n};\n\nexport const getMessagePreviewInternal = (\n  iconName: IconName,\n  text: string,\n  { iconColor, theme }: { iconColor?: ColorValue; theme?: CometChatTheme }\n) => {\n  const fontScale = PixelRatio.getFontScale();\n  const iconSize = (theme?.spacing?.spacing?.s4 ?? 16) * fontScale;\n  return (\n    <>\n      {iconName && (\n        <Icon\n          name={iconName}\n          size={iconSize}\n          color={iconColor || theme?.color?.textSecondary}\n          containerStyle={{ marginRight: theme?.spacing?.spacing?.s0_5 }}\n        />\n      )}\n      <Text\n        numberOfLines={1}\n        ellipsizeMode='tail'\n        style={{\n          color: theme?.color?.textSecondary,\n          ...theme?.typography?.body?.regular,\n          flexShrink: 2,\n        }}\n      >\n        {text}\n      </Text>\n    </>\n  );\n};\n\n/**\n * Applies mentions formatting to message content.\n * Shared helper used by CometChatConversations subtitle and CometChatMessagePreview\n * to ensure consistent @mention styling across the UI.\n *\n * @param message - The message containing mentioned users\n * @param content - The current formatted content (string or JSX)\n * @param rawText - The raw message text (for detecting @all alias tokens)\n * @param mentionsStyle - Optional style overrides for mentions\n * @returns The content with styled mentions applied\n */\nexport const applyMentionsFormatting = (\n  message: CometChat.BaseMessage,\n  content: string | JSX.Element,\n  rawText: string,\n  mentionsStyle?: any,\n): string | JSX.Element => {\n  const containsAllAlias = /<@all:(.*?)>/.test(rawText);\n  if (!message.getMentionedUsers?.().length && !containsAllAlias) {\n    return content;\n  }\n\n  try {\n    let mentionsFormatter = ChatConfigurator.getDataSource().getMentionsFormatter();\n    mentionsFormatter.setContext(\"conversation\");\n    mentionsFormatter.setLoggedInUser(CometChatUIKit.loggedInUser!);\n    mentionsFormatter.setTargetElement(MentionsTargetElement.conversation);\n    mentionsFormatter.setMessage(message);\n    if (mentionsStyle) {\n      mentionsFormatter.setMentionsStyle(mentionsStyle);\n    }\n\n    if (containsAllAlias) {\n      const match = rawText.match(/<@all:(.*?)>/);\n      const aliasLabel = match && match[1] ? match[1] : \"all\";\n      let existing = mentionsFormatter.getSuggestionItems();\n      const underlyingText = `<@all:${aliasLabel}>`;\n      const already = existing.find((it: any) => it.underlyingText === underlyingText);\n      if (!already) {\n        const aliasItem = new SuggestionItem({\n          id: aliasLabel,\n          name: aliasLabel,\n          promptText: aliasLabel,\n          trackingCharacter: \"@\",\n          underlyingText,\n          hideLeadingIcon: true,\n        });\n        mentionsFormatter.setSuggestionItems([...existing, aliasItem]);\n      }\n    }\n\n    let suggestionUsers = mentionsFormatter.getSuggestionItems();\n    mentionsFormatter.setMessage(message);\n    if (suggestionUsers.length > 0) mentionsFormatter.setSuggestionItems(suggestionUsers);\n    let _formatter = CommonUtils.clone(mentionsFormatter);\n    return _formatter.getFormattedText(content, mentionsStyle);\n  } catch (e) {\n    console.warn(\"Error applying mentions formatting:\", e);\n    return content;\n  }\n};\n\nexport const getModerationStatus = (message: CometChat.BaseMessage | any): string => {\n  if (message?.getModerationStatus) {\n    try {\n      return message.getModerationStatus();\n    } catch (e) {\n      console.log(\"🚀 ~ getModerationStatus ~ e:\", e);\n    }\n  }\n  const rawStatus =\n    message?.getData?.()?.moderation?.status ||\n    message?.getData?.()?.metaData?.moderation?.status ||\n    message?.data?.moderation?.status ||\n    message?.data?.metaData?.moderation?.status ||\n    message?.moderationStatus;\n  if (!rawStatus) return \"unmoderated\";\n  return String(rawStatus);\n};\n\nexport const ModerationBottomView = ({\n  status,\n  moderationStyle,\n}: {\n  status: string;\n  moderationStyle?: OutgoingBubbleStyles[\"moderationStyle\"];\n}) => {\n  const { t } = useCometChatTranslation();\n  const theme = useTheme();\n  if (status !== \"disapproved\") return null;\n\n  const bubblePadH =\n  theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingHorizontal ??\n  theme.spacing?.spacing?.s3 ?? 12;\n\n  const bubblePadB =\n  theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingBottom ?? 0;\n\n  const bubbleRadius =\n    theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.borderRadius ?? 8;\n\n  const themeModerationStyle =\n    theme.messageListStyles.outgoingMessageBubbleStyles.moderationStyle || {};\n\n  const effective = deepMerge(themeModerationStyle, moderationStyle || {});\n\n  return (\n    <View\n      style={[\n        {\n        marginHorizontal: -bubblePadH,\n        marginBottom: -bubblePadB,\n        borderTopLeftRadius: 0,\n        borderTopRightRadius: 0,\n        borderBottomLeftRadius: bubbleRadius,\n        borderBottomRightRadius: bubbleRadius,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n      },\n        effective?.containerStyle,\n      ]}\n    >\n      <Icon\n        name=\"message-blocked\"\n        color={effective?.iconTintColor}\n        containerStyle={{ paddingLeft: 12, paddingRight: 4, alignSelf: \"stretch\" }}\n      />\n      <Text\n        style={[\n          effective?.textStyle,\n        ]}\n      >\n        {t(\"BLOCKED_MODERATION\")}\n      </Text>\n    </View>\n  );\n};\n\nexport const MimeErrorBottomView = ({\n  moderationStyle,\n}: {\n  moderationStyle?: OutgoingBubbleStyles[\"moderationStyle\"];\n}) => {\n  const { t } = useCometChatTranslation();\n  const theme = useTheme();\n\n  const bubblePadH =\n    theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingHorizontal ??\n    theme.spacing?.spacing?.s3 ?? 12;\n\n  const bubblePadB =\n    theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingBottom ?? 0;\n\n  const bubbleRadius =\n    theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.borderRadius ?? 8;\n\n  const themeModerationStyle =\n    theme.messageListStyles.outgoingMessageBubbleStyles.moderationStyle || {};\n\n  const effective = deepMerge(themeModerationStyle, moderationStyle || {});\n\n  return (\n    <View\n      style={[\n        {\n          marginHorizontal: -bubblePadH,\n          marginBottom: -bubblePadB,\n          borderTopLeftRadius: 0,\n          borderTopRightRadius: 0,\n          borderBottomLeftRadius: bubbleRadius,\n          borderBottomRightRadius: bubbleRadius,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n        },\n        effective?.containerStyle,\n      ]}\n    >\n      <Icon\n        name=\"message-blocked\"\n        color={effective?.iconTintColor}\n        containerStyle={{ paddingLeft: 12, paddingRight: 4, alignSelf: \"stretch\" }}\n      />\n      <Text\n        style={[\n          effective?.textStyle,\n        ]}\n      >\n        {t(\"FILE_TYPE_NOT_ALLOWED\")}\n      </Text>\n    </View>\n  );\n};\n\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/NetworkUtils.tsx",
    "content": "export class CometChatNetworkUtils {\n  static async fetcher({\n    url,\n    method,\n    body,\n    headers,\n  }: {\n    url: string;\n    method: string;\n    body: any;\n    headers: any;\n  }) {\n    try {\n      const res = await fetch(url, {\n        method: method,\n        body: JSON.stringify(body),\n        headers: {\n          \"Content-Type\": \"application/json\",\n          ...headers,\n        },\n      });\n      const json = await res.json();\n      return res;\n    } catch (error) {\n      throw error;\n    }\n  }\n}\n\nexport function isHttpUrl(string) {\n  if (String(string).startsWith(\"https://\") || String(string).startsWith(\"http://\")) {\n    return true;\n  } else {\n    return false;\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/PermissionUtil.ts",
    "content": "import { Alert, Linking, NativeModules, PermissionsAndroid, Platform } from \"react-native\";\nimport { getCometChatTranslation } from \"../resources/CometChatLocalizeNew/LocalizationManager\";\nconst t = getCometChatTranslation();\nconst { FileManager } = NativeModules;\n\nlet instance: PermissionUtilIOS;\nconst permissionStatus = {\n  \"Not Determined\": 0,\n  Restricted: 1,\n  Denied: 2,\n  Authorized: 3,\n  Unknown: -1,\n} as const;\n\nconst permissionStatusAndroid = {\n  granted: 3,\n  denied: 2,\n  never_ask_again: 2,\n} as any;\n\nconst permissibleResources = [\"camera\", \"mic\"] as const;\n\nconst permissionMapAndroid: any = {\n  camera: PermissionsAndroid.PERMISSIONS.CAMERA,\n  mic: PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,\n};\n\ntype TPermissibleResources = (typeof permissibleResources)[number];\n\ntype ValueOf<T> = T[keyof T];\ntype TPermissionStatus = ValueOf<typeof permissionStatus>;\n\nconst permissionStore: any = {\n  camera: permissionStatus.Unknown,\n  mic: permissionStatus.Unknown,\n};\n\nclass PermissionUtilIOS {\n  status = permissionStatus;\n  constructor() {\n    if (instance) {\n      throw new Error(\"New instance cannot be created!!\");\n    }\n    instance = this;\n  }\n\n  async init() {\n    try {\n      if (Platform.OS === \"ios\") {\n        const res: Record<TPermissibleResources, TPermissionStatus> =\n          await FileManager.checkResourcesPermission([\"mic\", \"camera\"]);\n        for (const resourceType in res) {\n          permissionStore[resourceType as TPermissibleResources] =\n            res[resourceType as TPermissibleResources];\n        }\n        permissionStore.camera = res.camera;\n        permissionStore.mic = res.mic;\n      } else if (Platform.OS === \"android\") {\n        for (const resourceType of permissibleResources) {\n          const isGranted = await PermissionsAndroid.check(permissionMapAndroid[resourceType]);\n          permissionStore[resourceType] = isGranted\n            ? permissionStatus.Authorized\n            : permissionStatus[\"Not Determined\"];\n        }\n      }\n      return true;\n    } catch (error: any) {\n      return false;\n    }\n  }\n\n  async get(resources: TPermissibleResources[]) {\n    return resources.map((resource) => {\n      if (!permissibleResources.includes(resource)) {\n        throw new Error(\"Invalid resource type\");\n      }\n      return permissionStore[resource];\n    });\n  }\n\n  async request(resources: TPermissibleResources[]) {\n    for (const resource of resources) {\n      if (!permissibleResources.includes(resource)) {\n        throw new Error(\"Invalid resource type\");\n      }\n    }\n    if (Platform.OS === \"ios\") {\n      const res: Record<TPermissibleResources, TPermissionStatus> =\n        await FileManager.requestResourcesPermission(resources);\n      for (const resourceType in res) {\n        permissionStore[resourceType as TPermissibleResources] =\n          res[resourceType as TPermissibleResources];\n      }\n    } else if (Platform.OS === \"android\") {\n      const res: any = await PermissionsAndroid.requestMultiple(\n        resources.map((item) => permissionMapAndroid[item])\n      );\n      const permissionMapAndroidRevert: any = {};\n      for (const resource in permissionMapAndroid) {\n        permissionMapAndroidRevert[permissionMapAndroid[resource]] = resource;\n      }\n      for (const androidResource in res) {\n        permissionStore[permissionMapAndroidRevert[androidResource]] =\n          permissionStatusAndroid[res[androidResource]];\n      }\n    }\n  }\n\n  async startResourceBasedTask(resources: TPermissibleResources[]) {\n    const resourcePermStatuses = await this.get(resources);\n    let allResourcesAllowed = true;\n    for (let i = 0; i < resourcePermStatuses.length; i++) {\n      const resourcePermStatus = resourcePermStatuses[i];\n      if (\n        resourcePermStatus === this.status[\"Not Determined\"] ||\n        (Platform.OS === \"android\" && resourcePermStatuses[i] === this.status.Denied)\n      ) {\n        await this.request([resources[i]]);\n        [resourcePermStatuses[i]] = await this.get([resources[i]]);\n      }\n      if (resourcePermStatuses[i] === this.status.Denied) {\n        allResourcesAllowed = false;\n      }\n    }\n    if (allResourcesAllowed === false) {\n      Alert.alert(\"\", t(\"CAMERA_PERMISSION\"), [\n        {\n          style: \"cancel\",\n          text: t(\"CANCEL\"),\n        },\n        {\n          style: \"default\",\n          text: t(\"SETTINGS\"),\n          onPress: () => {\n            Linking.openSettings();\n          },\n        },\n      ]);\n    }\n    return allResourcesAllowed;\n  }\n}\n\nexport const permissionUtil = Object.freeze(new PermissionUtilIOS());\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/conversationUtils.ts",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { ColorValue } from \"react-native\";\nimport { IconName } from \"../icons/Icon\";\nimport { getMessagePreviewInternal } from \"./MessageUtils\";\nimport { MessageCategoryConstants } from \"../constants/UIKitConstants\";\nimport { CometChatUIKit } from \"../CometChatUiKit\";\nimport { CometChatTheme } from \"../../theme/type\";\nimport { JSX } from \"react\";\nimport { getCometChatTranslation } from \"../resources/CometChatLocalizeNew/LocalizationManager\";\n \nconst t = getCometChatTranslation();\n\nexport class CometChatConversationUtils {\n  static getLastMessage(conversation: CometChat.Conversation): CometChat.BaseMessage | undefined {\n    let msg = conversation?.getLastMessage && conversation?.getLastMessage();\n    if (!msg) {\n      return undefined;\n    }\n    switch (msg[\"category\"]) {\n      case \"message\":\n        break;\n      case \"custom\":\n        break;\n      case \"action\":\n        break;\n      case \"call\":\n        break;\n      default:\n        break;\n    }\n    return msg;\n  }\n\n  static getMessagePreview = (\n    lastMessage: CometChat.BaseMessage,\n    theme?: CometChatTheme\n  ): string | JSX.Element => {\n    const uid = CometChatUIKit.loggedInUser!.getUid();\n    if (lastMessage != undefined) {\n      if (lastMessage.getDeletedAt() !== undefined) {\n        return getMessagePreviewInternal(\"block-fill\", t(\"DELETE_MSG_TEXT\"), {theme});\n      }\n\n      if (lastMessage.getCategory() === MessageCategoryConstants.interactive) {\n        return getMessagePreviewInternal(\n          \"block-fill\",\n          t(\"NOT_SUPPORTED\") ?? \"This message type is not supported\",\n          {theme}\n        );\n      }\n\n      if (lastMessage.getCategory() == \"call\") {\n        let color: ColorValue | undefined = theme?.color?.textSecondary;\n        let text = \"Video call\";\n        let iconName: IconName = \"phone-incoming-fill\";\n        if (lastMessage.getType() == \"audio\") {\n          text = \"Voice call\";\n        }\n        if (uid === lastMessage.getSender().getUid()) {\n          iconName = \"phone-outgoing-fill\";\n        } else if ((lastMessage as CometChat.Call).getAction() === \"unanswered\") {\n          color = theme?.color?.error;\n          text = \"Missed \" + text.toLowerCase();\n        }\n\n        return getMessagePreviewInternal(iconName, text, {iconColor: color, theme});\n      }\n      let msgText = \"\";\n      if (lastMessage.getCategory() == \"message\") {\n        switch (lastMessage.getType()) {\n          case \"text\":\n            msgText = (lastMessage as CometChat.TextMessage).getText();\n            break;\n          case \"image\":\n            return getMessagePreviewInternal(\"photo-fill\", t('PHOTOS'), {theme});\n          case \"audio\":\n            return getMessagePreviewInternal(\"mic-fill\", t('MESSAGE_AUDIO'), {theme});\n          case \"video\":\n            return getMessagePreviewInternal(\"videocam-fill\", t(\"MESSAGE_VIDEO\"), {theme});\n          case \"file\":\n            return getMessagePreviewInternal(\"description-fill\", t(\"MESSAGE_FILE\"), {theme});\n        }\n      } else if (\n        lastMessage.getCategory() == (CometChat.CATEGORY_CUSTOM as CometChat.MessageCategory)\n      ) {\n        msgText = t(lastMessage.getType())\n      } else if (\n        lastMessage.getCategory() == (CometChat.CATEGORY_ACTION as CometChat.MessageCategory)\n      ) {\n        if (\n          (lastMessage as CometChat.Action)?.getAction() === CometChat.ACTION_TYPE.MESSSAGE_DELETED\n        ) {\n          return getMessagePreviewInternal(\"block-fill\", t(\"DELETE_MSG_TEXT\"), {theme});\n        }\n        msgText = (lastMessage as CometChat.Action).getMessage();\n      } else if (lastMessage.getCategory() === CometChat.CATEGORY_INTERACTIVE) {\n        msgText = \"\";\n        //todo unsupported bubble\n      } else {\n        msgText = (lastMessage[\"metadata\"] as any)?.pushNotification;\n      }\n      return msgText;\n    } else {\n      return \"\";\n    }\n  };\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/icsToJson.js",
    "content": "const NEW_LINE = /\\r\\n|\\n|\\r/;\n\nconst EVENT = 'VEVENT';\nconst EVENT_START = 'BEGIN';\nconst EVENT_END = 'END';\nconst START_DATE = 'DTSTART';\nconst END_DATE = 'DTEND';\nconst DESCRIPTION = 'DESCRIPTION';\nconst SUMMARY = 'SUMMARY';\nconst LOCATION = 'LOCATION';\nconst ALARM = 'VALARM';\nconst TZID = 'TZID';\nconst TZOFFSETFROM = 'TZOFFSETFROM';\nconst TZOFFSETTO = 'TZOFFSETTO';\n\nconst keyMap = {\n  [START_DATE]: 'startDate',\n  [END_DATE]: 'endDate',\n  [DESCRIPTION]: 'description',\n  [SUMMARY]: 'summary',\n  [LOCATION]: 'location',\n  [TZID]: 'tzid',\n  [TZOFFSETFROM]: 'tzOffsetFrom',\n  [TZOFFSETTO]: 'tzOffsetTo',\n};\n\nconst clean = (string) => unescape(string).trim();\n\nconst icsToJson = (icsData) => {\n  const array = [];\n  let currentObj = {};\n  let timeZoneObj = {};\n  let lastKey = '';\n\n  const lines = icsData.split(NEW_LINE);\n\n  let isAlarm = false;\n  for (let i = 0, iLen = lines.length; i < iLen; ++i) {\n    const line = lines[i];\n    const lineData = line.split(':');\n\n    let key = lineData[0];\n    const value = lineData[1];\n    let kyParts = [];\n    // console.log('--->>>   ',{key:key,value:value});\n    if (key.indexOf(';') !== -1) {\n      const keyParts = key.split(';');\n      key = keyParts[0];\n      kyParts = keyParts;\n      // Maybe do something with that second part later\n    }\n\n    if (lineData.length < 2) {\n      if (key.startsWith(' ') && lastKey !== undefined && lastKey.length) {\n        currentObj[lastKey] += clean(line.substr(1));\n      }\n      continue;\n    } else {\n      lastKey = keyMap[key];\n    }\n    switch (key) {\n      case EVENT_START:\n        if (value === EVENT) {\n          currentObj = { ...timeZoneObj };\n        } else if (value === ALARM) {\n          isAlarm = true;\n        }\n        break;\n      case EVENT_END:\n        isAlarm = false;\n        if (value === EVENT) {\n          timeZoneObj = {};\n          array.push(currentObj);\n        }\n        break;\n      case START_DATE:\n        // console.log({ key, value, lineData, kyParts });\n        if (kyParts.length > 1) {\n          let tzid = kyParts[1]?.split('=');\n          if (tzid.length > 1) currentObj[keyMap[TZID]] = tzid[1];\n        }\n        currentObj[keyMap[START_DATE]] = value;\n        break;\n      case END_DATE:\n        currentObj[keyMap[END_DATE]] = value;\n        break;\n      case DESCRIPTION:\n        if (!isAlarm) currentObj[keyMap[DESCRIPTION]] = clean(value);\n        break;\n      case SUMMARY:\n        currentObj[keyMap[SUMMARY]] = clean(value);\n        break;\n      case LOCATION:\n        currentObj[keyMap[LOCATION]] = clean(value);\n        break;\n      case TZID:\n        timeZoneObj[keyMap[TZID]] = value;\n        break;\n      case TZOFFSETFROM:\n        timeZoneObj[keyMap[TZOFFSETFROM]] = value;\n        break;\n      case TZOFFSETTO:\n        timeZoneObj[keyMap[TZOFFSETTO]] = value;\n        break;\n      default:\n        continue;\n    }\n  }\n\n  return array;\n};\n\nexport default icsToJson;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/utils/index.ts",
    "content": "import { CometChatSoundManager } from \"../resources/CometChatSoundManager\";\nimport { CometChatMessagePreview } from \"./CometChatMessagePreview\";\nimport { CometChatConversationUtils } from \"./conversationUtils\";\nimport { isCursorWithinMentionRange, getMentionRangeAtCursor } from \"./MentionUtils\";\nimport { stripMarkdown } from \"./MarkdownUtils\";\nexport {\n  CometChatConversationUtils,\n  CometChatMessagePreview,\n  CometChatSoundManager,\n  isCursorWithinMentionRange,\n  getMentionRangeAtCursor,\n  stripMarkdown,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAIAssistantMessageBubble/CometChatAIAssistantMessageBubble.tsx",
    "content": "import React, { useRef, useEffect, useMemo } from 'react';\nimport { View, Text, StyleSheet, Animated } from 'react-native';\nimport Markdown from 'react-native-markdown-display';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useTheme } from '../../../theme';\nimport { CometChatTheme } from '../../../theme/type';\n\nexport interface CometChatAIAssistantMessageBubbleProps {\n  message: any;\n  theme?: CometChatTheme;\n  style?: any;\n}\n\nconst getMessageText = (message: any) => {\n  if (!message) return '';\n  if (message instanceof CometChat.CustomMessage) {\n    const customData = message.getData();\n    if (customData?.text) return customData.text;\n    if (customData?.assistantMessageData?.getText) return customData.assistantMessageData.getText();\n  }\n  if (typeof message.getAssistantMessageData === 'function') {\n    const assistantData = message.getAssistantMessageData();\n    if (assistantData?.getText) return assistantData.getText();\n  }\n  if (typeof message.getText === 'function') return message.getText();\n  if (typeof message.text === 'string') return message.text;\n  return '';\n};\n\nconst CometChatAIAssistantMessageBubble: React.FC<CometChatAIAssistantMessageBubbleProps> = ({ \n  message, \n  style: bubbleStyle \n}) => {\n  const theme = useTheme();\n  const text = getMessageText(message);\n\n  // Use the bubbleStyle passed from parent, with theme fallbacks\n  const styles = StyleSheet.create({\n    container: {\n      backgroundColor: bubbleStyle?.containerStyle?.backgroundColor || 'transparent',\n      borderRadius: bubbleStyle?.containerStyle?.borderRadius || theme.spacing.radius.r3,\n      minWidth: bubbleStyle?.containerStyle?.minWidth || 90,\n      alignSelf: bubbleStyle?.containerStyle?.alignSelf || 'flex-start',\n      maxWidth: '100%',\n      ...bubbleStyle?.containerStyle,\n    },\n    textContainer: {\n      ...bubbleStyle?.textContainerStyle,\n    },\n    text: {\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      ...bubbleStyle?.textStyle,\n    },\n    placeholderText: {\n      color: bubbleStyle?.placeholderTextStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.placeholderTextStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.placeholderTextStyle?.fontSize || theme.typography.body.regular.fontSize,\n      opacity: bubbleStyle?.placeholderTextStyle?.opacity || 0.6,\n      fontStyle: 'italic',\n      ...bubbleStyle?.placeholderTextStyle,\n    },\n    copyButton: {\n      backgroundColor: bubbleStyle?.copyButtonStyle?.backgroundColor || theme.color.primary,\n      padding: bubbleStyle?.copyButtonStyle?.padding || theme.spacing.padding.p1,\n      borderRadius: bubbleStyle?.copyButtonStyle?.borderRadius || theme.spacing.radius.r1,\n      ...bubbleStyle?.copyButtonStyle,\n    },\n    errorContainer: {\n      backgroundColor: bubbleStyle?.errorContainerStyle?.backgroundColor || theme.color.error,\n      padding: bubbleStyle?.errorContainerStyle?.padding || theme.spacing.padding.p2,\n      borderRadius: bubbleStyle?.errorContainerStyle?.borderRadius || theme.spacing.radius.r2,\n      marginTop: bubbleStyle?.errorContainerStyle?.marginTop || theme.spacing.margin.m1,\n      ...bubbleStyle?.errorContainerStyle,\n    },\n    errorText: {\n      color: bubbleStyle?.errorTextStyle?.color || theme.color.background1,\n      fontFamily: bubbleStyle?.errorTextStyle?.fontFamily || theme.typography.caption1.regular.fontFamily,\n      fontSize: bubbleStyle?.errorTextStyle?.fontSize || theme.typography.caption1.regular.fontSize,\n      ...bubbleStyle?.errorTextStyle,\n    },\n  });\n\n  // Create markdown styles based on theme\n  const markdownStyles = useMemo(() => ({\n    body: {\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      ...bubbleStyle?.textStyle,\n      margin: 0,\n      padding: 0,\n      textAlignVertical: 'top',\n    },\n    text: {\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      ...(bubbleStyle?.textStyle && Object.fromEntries(\n        Object.entries(bubbleStyle.textStyle).filter(([key]) => key !== 'color')\n      )),\n    },\n    paragraph: {\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      margin: 0,\n      padding: 0,\n    },\n    code_inline: {\n      backgroundColor: theme.color.background3,\n      color: theme.color.textHighlight,\n      borderRadius: theme.spacing.radius.r1,\n      padding: theme.spacing.padding.p0_5,\n    },\n    code_block: {\n      backgroundColor: theme.color.background3,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      borderRadius: theme.spacing.radius.r2,\n      padding: theme.spacing.padding.p2,\n      fontFamily: 'monospace',\n    },\n    fence: {\n      backgroundColor: theme.color.background3,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      borderRadius: theme.spacing.radius.r2,\n      borderWidth: 1,\n      borderColor: theme.color.borderDefault,\n      padding: theme.spacing.padding.p2,\n      marginVertical: theme.spacing.margin.m4,\n      fontFamily: 'monospace',\n    },\n    table: {\n      borderWidth: 1,\n      borderColor: theme.color.borderDefault,\n      borderRadius: theme.spacing.radius.r2,\n      backgroundColor: theme.color.background2,\n      marginVertical: theme.spacing.margin.m2,\n      overflow: 'hidden' as 'hidden',\n      borderCollapse: 'separate' as 'separate',\n      borderSpacing: 0,\n    },\n    thead: {\n      backgroundColor: theme.color.background3,\n      margin: 0,\n      padding: 0,\n    },\n    tbody: {\n      backgroundColor: theme.color.background1,\n      margin: 0,\n      padding: 0,\n    },\n    th: {\n      borderRightWidth: 1,\n      borderBottomWidth: 1,\n      borderColor: theme.color.borderDefault,\n      backgroundColor: theme.color.background3,\n      padding: theme.spacing.padding.p2,\n      fontWeight: 'bold',\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      margin: 0,\n      borderTopWidth: 0,\n      borderLeftWidth: 0,\n    },\n    td: {\n      borderRightWidth: 1,\n      borderBottomWidth: 1,\n      borderColor: theme.color.borderDefault,\n      backgroundColor: theme.color.background1,\n      padding: theme.spacing.padding.p2,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      margin: 0,\n      borderTopWidth: 0,\n      borderLeftWidth: 0,\n    },\n    tr: {\n      borderBottomWidth: 0,\n      margin: 0,\n      padding: 0,\n    },\n    heading1: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.6,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m2,\n    },\n    heading2: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.4,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m2,\n    },\n    heading3: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.2,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    heading4: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.1,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    heading5: {\n      fontWeight: '700' as '700',\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    heading6: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 0.9,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    strong: {\n      fontWeight: '700' as '700',\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n    },\n    em: {\n      fontStyle: 'italic' as 'italic',\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n    },\n    blockquote: {\n      borderLeftWidth: 3,\n      borderLeftColor: theme.color.borderDefault,\n      paddingLeft: theme.spacing.padding.p2,\n      color: theme.color.textSecondary,\n    },\n    link: {\n      underlineColor: theme.color.textHighlight,\n      color: theme.color.textHighlight,\n      borderRadius: theme.spacing.radius.r1,\n      paddingHorizontal: theme.spacing.padding.p0_5,\n      paddingVertical: theme.spacing.padding.p0_5,\n      textDecorationLine: 'underline' as 'underline',\n    },\n  }), [bubbleStyle?.textStyle, theme]);\n\n  const MemoMarkdown = useMemo(() => {\n    return (\n      <View style={styles.container}>\n        <Markdown style={markdownStyles} mergeStyle={true}>\n          {text.trim()}\n        </Markdown>\n      </View>\n    )\n  }, [text, markdownStyles, styles]);\n\n  return MemoMarkdown;\n};\n\nexport default CometChatAIAssistantMessageBubble;"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAIAssistantMessageBubble/index.ts",
    "content": "import CometChatAIAssistantMessageBubble from \"./CometChatAIAssistantMessageBubble\";\n\nexport { CometChatAIAssistantMessageBubble };\nexport default CometChatAIAssistantMessageBubble;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAICard/CometChatAICard.tsx",
    "content": "// import React, { useContext } from \"react\";\n// import { ActivityIndicator, Image, Text, View } from \"react-native\";\n// import { AIBaseStyle, AIBaseStyleInterface } from \"../../../AI/AIBaseStyle\";\n// import { CometChatContext } from \"../../CometChatContext\";\n// import { ErrorMessageIcon, NoMessageIcon } from \"./resources\";\n\n// const enum States {\n//   loading = \"loading\",\n//   error = \"error\",\n//   empty = \"empty\",\n// }\n\n// export interface CometChatAICardInterface {\n//   state: \"loading\" | \"error\" | \"empty\";\n//   style: AIBaseStyleInterface;\n//   loadingIconTint?: string;\n//   errorIconTint?: string;\n//   emptyIconTint?: string;\n//   loadingIconURL?: string;\n//   errorIconURL?: string;\n//   emptyIconURL?: string;\n//   loadingStateText?: string;\n//   errorStateText?: string;\n//   emptyStateText?: string;\n// }\n\n// const CometChatAICard = (props: CometChatAICardInterface) => {\n//   const {\n//     state,\n//     style,\n//     loadingStateText,\n//     loadingIconURL,\n//     emptyIconURL,\n//     emptyStateText,\n//     errorIconURL,\n//     errorStateText,\n//   } = props;\n\n//   const { theme } = useContext(CometChatContext);\n\n//   const {\n//     emptyStateTextColor,\n//     emptyStateTextFont,\n//     emptyIconTint,\n//     errorStateTextColor,\n//     errorStateTextFont,\n//     errorIconTint,\n//     loadingStateTextColor,\n//     loadingStateTextFont,\n//     loadingIconTint,\n//   } = new AIBaseStyle({\n//     emptyIconTint: theme.palette.getAccent(),\n//     emptyStateTextColor: theme.palette.getAccent(),\n//     emptyStateTextFont: theme.typography.subtitle1,\n//     errorIconTint: theme.palette.getAccent(),\n//     errorStateTextColor: theme.palette.getAccent(),\n//     errorStateTextFont: theme.typography.subtitle1,\n//     loadingIconTint: theme.palette.getAccent(),\n//     loadingStateTextColor: theme.palette.getAccent(),\n//     loadingStateTextFont: theme.typography.subtitle1,\n//     ...style,\n//   });\n//   return (\n//     <View>\n//       {props.children ? (\n//         props.children\n//       ) : state === States.loading ? (\n//         <View style={{ flexDirection: \"row\", alignItems: \"center\", padding: 10 }}>\n//           {!loadingIconURL ? (\n//             <ActivityIndicator color={loadingIconTint} />\n//           ) : (\n//             <Image\n//               style={{ width: 25, height: 25, tintColor: loadingIconTint }}\n//               source={loadingIconURL}\n//             />\n//           )}\n//           <Text style={[loadingStateTextFont, { color: loadingStateTextColor, marginLeft: 10 }]}>\n//             {loadingStateText}\n//           </Text>\n//         </View>\n//       ) : state === States.error ? (\n//         <View style={{ flexDirection: \"row\", alignItems: \"center\", padding: 10 }}>\n//           <Image\n//             style={{ width: 25, height: 25, tintColor: errorIconTint }}\n//             source={errorIconURL || ErrorMessageIcon}\n//           />\n//           <Text style={[errorStateTextFont, { color: errorStateTextColor, marginLeft: 10 }]}>\n//             {errorStateText}\n//           </Text>\n//         </View>\n//       ) : state === States.empty ? (\n//         <View style={{ flexDirection: \"row\", alignItems: \"center\", padding: 10 }}>\n//           <Image\n//             style={{ width: 25, height: 25, tintColor: emptyIconTint }}\n//             source={emptyIconURL || NoMessageIcon}\n//           />\n//           <Text style={[emptyStateTextFont, { color: emptyStateTextColor, marginLeft: 10 }]}>\n//             {emptyStateText}\n//           </Text>\n//         </View>\n//       ) : null}\n//     </View>\n//   );\n// };\n\n// export default CometChatAICard;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAICard/resources/index.ts",
    "content": "import ErrorMessageIcon from \"./errormessage.png\";\nimport NoMessageIcon from \"./nomessage.png\";\n\nexport { ErrorMessageIcon, NoMessageIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatActionSheet/ActionItem.ts",
    "content": "import { ImageSourcePropType, ImageStyle, StyleProp, TextStyle, ViewStyle } from \"react-native\";\nimport { ActionSheetStyle } from \"../../../theme/type\";\nimport { JSX } from \"react\";\n\nexport interface ActionItemInterface {\n  id: string;\n  title?: string;\n  icon?: JSX.Element | ImageSourcePropType;\n  onPress?: Function;\n  style?: Partial<{\n    containerStyle: ViewStyle;\n    iconStyle: ImageStyle;\n    iconContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n  }>;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatActionSheet/CometChatActionSheet.tsx",
    "content": "import React, { JSX, useCallback, useMemo, useState } from \"react\";\nimport {\n  FlatList,\n  ImageSourcePropType,\n  ImageStyle,\n  Pressable,\n  Text,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { ActionSheetStyle } from \"../../../theme/type\";\nimport { Icon } from \"../../icons/Icon\";\nimport { ActionItemInterface } from \"./ActionItem\";\nimport { Hooks } from \"./hooks\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { DeepPartial } from \"../../helper/types\";\n\nconst OptionListView = ({\n  id,\n  title,\n  icon,\n  onPress,\n  style,\n}: {\n  id: string;\n  title: string;\n  icon: JSX.Element | ImageSourcePropType;\n  onPress: () => void;\n  style: Partial<{\n    containerStyle: ViewStyle;\n    iconStyle: ImageStyle;\n    iconContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n  }>;\n}) => {\n  const theme = useTheme();\n  const [isPressed, setIsPressed] = useState(false);\n\n  const renderIcon = useCallback(() => {\n    return (\n      <Icon icon={icon} imageStyle={style?.iconStyle} containerStyle={style?.iconContainerStyle} />\n    );\n  }, [icon]);\n\n  return (\n    <Pressable\n      key={id}\n      onPress={onPress}\n      onPressIn={() => setIsPressed(true)}\n      onPressOut={() => setIsPressed(false)}\n      style={[\n        style?.containerStyle,\n        isPressed && { backgroundColor: theme.color.background4 },\n      ]}\n    >\n      {renderIcon()}\n      <Text style={style?.titleStyle}>{title}</Text>\n    </Pressable>\n  );\n};\n\nexport interface CometChatActionSheetInterface {\n  actions: ActionItemInterface[];\n  style?: DeepPartial<ActionSheetStyle>;\n}\nexport const CometChatActionSheet = (props: CometChatActionSheetInterface) => {\n  const [actionList, setActionList] = React.useState<ActionItemInterface[]>(props.actions);\n  const theme = useTheme();\n\n  const actionSheetStyles = useMemo(() => {\n    return deepMerge(theme.actionSheetStyle, props.style ?? {});\n  }, [theme.actionSheetStyle, props.style]);\n\n  Hooks(props, setActionList);\n\n  const _render = ({ item }: { item: any }) => {\n    return (\n      <OptionListView\n        id={item.id}\n        {...item}\n        icon={item.icon}\n        style={deepMerge(actionSheetStyles.optionsItemStyle, item?.style ?? {})}\n      />\n    );\n  };\n\n  const getList = () => {\n    return (\n      <FlatList\n        key={\"list\"}\n        keyExtractor={(item) => item.id}\n        data={actionList}\n        numColumns={1}\n        renderItem={_render}\n        contentContainerStyle={{ paddingBottom: 24 }}\n      />\n    );\n  };\n\n  return getList();\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatActionSheet/hooks.ts",
    "content": "import React from \"react\";\nimport { ActionItemInterface } from \"./ActionItem\";\nimport { CometChatActionSheetInterface } from \"./CometChatActionSheet\";\n\nexport const Hooks = (\n  props: CometChatActionSheetInterface,\n  setActionList: React.Dispatch<React.SetStateAction<ActionItemInterface[]>>\n) => {\n  React.useEffect(() => {\n    setActionList(props.actions);\n  }, [props.actions]);\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatActionSheet/index.ts",
    "content": "export type { ActionItemInterface } from \"./ActionItem\";\nexport { CometChatActionSheet } from \"./CometChatActionSheet\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatActionSheet/resources/index.ts",
    "content": "import GRID from \"./grid.png\";\nimport LIST from \"./list.png\";\n\nexport const ICONS = {\n  GRID,\n  LIST,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatActionSheet/style.ts",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const Style = StyleSheet.create({\n  listContainer: {\n    borderRadius: 8,\n    overflow: \"hidden\"\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAudioBubble/AnimatedAudioWaves.tsx",
    "content": "import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Animated, LayoutChangeEvent, StyleProp, StyleSheet, View, ViewStyle } from \"react-native\";\n\nexport const AnimatedAudioWaves = ({\n  isAnimating = false,\n  waveStyle = {},\n  waveContainerStyle = {},\n}: {\n  isAnimating?: boolean;\n  waveStyle?: StyleProp<ViewStyle>;\n  waveContainerStyle?: StyleProp<ViewStyle>;\n}) => {\n  const animatedValues = useRef<Animated.Value[]>([]).current;\n  const delayValues = useRef<number[]>([]);\n  const [staticBarValues, setStaticBarValues] = useState<{ height: number; marginTop: number }[]>(\n    []\n  );\n  const animationStartedOnce = useRef(false);\n\n  useEffect(() => {\n    if (isAnimating === false || animatedValues.length === 0 || staticBarValues.length === 0) {\n      return () => {};\n    }\n    startAnimation();\n    return () => {\n      stopAnimation();\n    };\n  }, [isAnimating, animatedValues]);\n\n  const onLayoutHandler = useCallback(\n    (event: LayoutChangeEvent) => {\n      if (animationStartedOnce.current) {\n        return;\n      }\n      const containerWidth = event.nativeEvent.layout.width;\n      const barWidth = 2;\n      const barMargin = 2;\n      const totalBarWidth = barWidth + barMargin;\n\n      let numBars = Math.floor(containerWidth / totalBarWidth);\n      if (staticBarValues.length > numBars) {\n        numBars = staticBarValues.length;\n      }\n      animatedValues.splice(\n        0,\n        animatedValues.length,\n        ...Array(numBars)\n          .fill(0)\n          .map(() => new Animated.Value(1))\n      );\n\n      if (staticBarValues.length === 0 || animatedValues.length !== staticBarValues.length) {\n        const newStaticBarValues = Array(numBars)\n          .fill(0)\n          .map(() => ({\n            height: Math.floor(Math.random() * (20 - 4 + 1)) + 4,\n            marginTop: Math.floor(Math.random() * 5),\n          }));\n        setStaticBarValues(newStaticBarValues);\n        delayValues.current = Array(numBars)\n          .fill(0)\n          .map(() => Math.random());\n      }\n    },\n    [staticBarValues]\n  );\n\n  const startAnimation = () => {\n    for (let i = 0; i < animatedValues.length; i++) {\n      const delay = Math.floor(Math.random() * 500);\n      Animated.loop(\n        Animated.sequence([\n          Animated.timing(animatedValues[i], {\n            toValue: delayValues.current[i],\n            duration: 800,\n            delay: delay,\n            useNativeDriver: true,\n          }),\n          Animated.timing(animatedValues[i], {\n            toValue: 1,\n            duration: 400,\n            delay: delay,\n            useNativeDriver: true,\n          }),\n        ])\n      ).start();\n    }\n    animationStartedOnce.current = true;\n  };\n\n  const stopAnimation = () => {\n    animatedValues.forEach((animatedValue) => {\n      animatedValue.stopAnimation();\n    });\n  };\n\n  return (\n    <View style={[styles.waveStyleContainer, waveContainerStyle]} onLayout={onLayoutHandler}>\n      {animatedValues.map((animatedValue, index) => {\n        return (\n          <Animated.View\n            key={index}\n            style={[\n              styles.waveStyle,\n              waveStyle,\n              {\n                transform: [{ scaleY: animatedValue }],\n                height: staticBarValues[index]?.height, // Use optional chaining to avoid undefined errors\n              },\n            ]}\n          />\n        );\n      })}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  waveStyleContainer: {\n    alignItems: \"center\",\n    flexDirection: \"row\",\n    height: 30,\n    overflow: \"hidden\",\n  },\n  waveStyle: {\n    backgroundColor: \"#FFFFFF\",\n    height: 18,\n    marginHorizontal: 1,\n    width: 2,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAudioBubble/CometChatAudioBubble.tsx",
    "content": "import React, { JSX, useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n  EmitterSubscription,\n  ImageSourcePropType,\n  ImageStyle,\n  NativeEventEmitter,\n  NativeModules,\n  Platform,\n  StyleProp,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { Icon } from \"../../icons/Icon\";\nimport { AnimatedAudioWaves } from \"./AnimatedAudioWaves\";\n\nconst { SoundPlayer } = NativeModules;\nconst eventEmitter = new NativeEventEmitter(SoundPlayer);\nlet listener: EmitterSubscription;\nlet interval: ReturnType<typeof setTimeout>;;\n\nexport interface CometChatAudioBubbleInterface {\n  /**\n   * url of audio\n   */\n  audioUrl: string;\n  /**\n   * custom icon for play\n   */\n  playIcon?: ImageSourcePropType | JSX.Element;\n  /**\n   * custom icon for pause\n   */\n  pauseIcon?: ImageSourcePropType | JSX.Element;\n  /**\n   * pass function to handle custom play/pause logic.\n   * one parameters will be received audioUrl\n   */\n  onPress?: Function;\n  playViewContainerStyle?: StyleProp<ViewStyle>;\n  playIconStyle?: ImageStyle;\n  playIconContainerStyle?: StyleProp<ViewStyle>;\n  waveStyle?: StyleProp<ViewStyle>;\n  waveContainerStyle?: StyleProp<ViewStyle>;\n  playProgressTextStyle?: TextStyle;\n}\n\nexport const CometChatAudioBubble = ({\n  audioUrl,\n  onPress,\n  playIcon,\n  pauseIcon,\n  playViewContainerStyle = {},\n  playIconStyle = {},\n  playIconContainerStyle = {},\n  waveStyle = {},\n  waveContainerStyle = {},\n  playProgressTextStyle = {},\n}: CometChatAudioBubbleInterface) => {\n  const [status, setStatus] = useState<\"playing\" | \"paused\" | \"loading\" | \"\">(\"\");\n  const [duration, setDuration] = useState(0);\n  const [currentTime, setCurrentTime] = useState(0);\n\n  useEffect(() => {\n    if (audioUrl) {\n      SoundPlayer.prepareMediaPlayer(audioUrl, (s: string) => {\n        try {\n          setDuration(JSON.parse(s).duration);\n        } catch (e) {}\n      });\n    }\n\n    return () => {\n      SoundPlayer.releaseMediaPlayer();\n    };\n  }, [audioUrl]);\n\n  useEffect(() => {\n    listener = eventEmitter.addListener(\"soundPlayStatus\", (data) => {\n      if (audioUrl === data.url) {\n        setStatus(\"\");\n        \n        clearInterval(interval);\n        \n        setCurrentTime(0);\n      }\n    });\n\n    return () => {\n      listener.remove();\n        clearInterval(interval);\n    };\n  }, [audioUrl]);\n\n  const playPauseAudio = () => {\n    if (onPress) {\n      onPress(audioUrl);\n      return;\n    }\n\n    if (status === \"playing\") {\n      SoundPlayer.pause((s: string) => {\n        try {\n          const json = JSON.parse(s);\n          if (json[\"success\"] === true) {\n            setStatus(\"paused\");\n            clearInterval(interval);\n          }\n        } catch (ex) {\n          console.log(ex);\n        }\n      });\n      return;\n    }\n    if (status === \"paused\") {\n      SoundPlayer.resume();\n      if (Platform.OS == \"ios\") {\n        setStatus(\"playing\");\n      }\n      // Get total duration when audio starts playing\n      SoundPlayer.getPosition((info: string) => {\n        try {\n          if (info) {\n            if (Platform.OS == \"android\") {\n              setStatus(\"playing\");\n            }\n            setCurrentTime(JSON.parse(info).position);\n          }\n        } catch (e) {}\n      });\n\n      // Start tracking the current time\n      interval = setInterval(() => {\n        SoundPlayer.getPosition((info: string) => {\n          try {\n            if (info) {\n              setCurrentTime(JSON.parse(info).position);\n            }\n          } catch (e) {}\n        });\n      }, 500);\n      return;\n    }\n    if (audioUrl) {\n      setStatus(\"loading\");\n      SoundPlayer.play(audioUrl, (s: string) => {\n        try {\n          const json = JSON.parse(s);\n          if (json[\"success\"] == true) {\n            setStatus(\"playing\");\n            // Get total duration when audio starts playing\n            SoundPlayer.getPosition((info: string) => {\n              try {\n                if (info) {\n                  setCurrentTime(JSON.parse(info).position);\n                }\n              } catch (e) {}\n            });\n\n            // Start tracking the current time\n            interval = setInterval(() => {\n              SoundPlayer.getPosition((info: string) => {\n                try {\n                  if (info) {\n                    setCurrentTime(JSON.parse(info).position);\n                  }\n                } catch (e) {}\n              });\n            }, 500);\n          }\n        } catch (ex) {\n          console.log(ex);\n        }\n      });\n    }\n  };\n\n  const pressTime = useRef<number | null>(0);\n\n  const handleTouchStart = () => {\n    pressTime.current = Date.now();\n  };\n\n  const handleTouchEnd = () => {\n    if (pressTime.current === null && Platform.OS === \"ios\") return;\n    const endTime = Date.now();\n    const pressDuration = endTime - (pressTime.current ?? 0);\n    if (pressDuration < 500) {\n      playPauseAudio();\n    }\n  };\n\n  const onTouchMove = () => {\n    if (Platform.OS === \"ios\") {\n      pressTime.current = null;\n    }\n  };\n\n  const displayDuration = useCallback(\n    () => (\n      <Text allowFontScaling={false} style={playProgressTextStyle}>\n        {`${String(Math.floor((currentTime || 0) / 60)).padStart(2, \"0\")}:${String(\n          Math.floor((currentTime || 0) % 60)\n        ).padStart(2, \"0\")}`}\n        /\n        {`${String(Math.floor((duration || 0) / 60)).padStart(2, \"0\")}:${String(\n          Math.floor((duration || 0) % 60)\n        ).padStart(2, \"0\")}`}\n      </Text>\n    ),\n    [currentTime, duration]\n  );\n\n  return (\n    <View style={playViewContainerStyle}>\n      <View\n        style={playIconContainerStyle}\n        onTouchStart={handleTouchStart}\n        onTouchEnd={handleTouchEnd}\n        onTouchMove={onTouchMove}\n      >\n        <Icon\n          name={status === \"playing\" ? \"pause-fill\" : \"play-arrow-fill\"}\n          height={playIconStyle?.height}\n          width={playIconStyle?.width}\n          color={playIconStyle?.tintColor}\n          imageStyle={playIconStyle}\n          icon={status === \"playing\" ? pauseIcon : playIcon}\n        />\n      </View>\n\n      <View style={{ flex: 1 }}>\n        <AnimatedAudioWaves\n          waveStyle={waveStyle}\n          waveContainerStyle={waveContainerStyle}\n          isAnimating={status === \"playing\"}\n        />\n        {displayDuration()}\n      </View>\n    </View>\n  );\n};\n// CometChatAudioBubble"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAudioBubble/index.ts",
    "content": "import { CometChatAudioBubble, CometChatAudioBubbleInterface } from \"./CometChatAudioBubble\";\nimport { getAudioBubbleStyleLight, getAudioBubbleStyleDark } from \"./style\";\n\nexport {\n  getAudioBubbleStyleLight,\n  getAudioBubbleStyleDark,\n  CometChatAudioBubble\n};\n\nexport type {\n  CometChatAudioBubbleInterface\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAudioBubble/resources/index.ts",
    "content": "import defaultPauseIcon from \"./pause.png\";\nimport defaultPlayIcon from \"./play.png\";\n\nexport { defaultPauseIcon, defaultPlayIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAudioBubble/style.ts",
    "content": "import { CometChatTheme } from \"../../../theme/type\";\n\nexport const getAudioBubbleStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingAudioBubbleStyle: Partial<CometChatTheme[\"audioBubbleStyles\"]>;\n  outgoingAudioBubbleStyle: Partial<CometChatTheme[\"audioBubbleStyles\"]>;\n} => {\n  return {\n    incomingAudioBubbleStyle: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        width: 240,\n      },\n      playViewContainerStyle: {\n        width: 216,\n        height: 32,\n        gap: spacing.padding.p3,\n        flexDirection: \"row\",\n      },\n      playIconStyle: {\n        tintColor: color.sendBubbleBackground,\n        width: spacing.spacing.s7,\n        height: spacing.spacing.s7,\n      },\n      playIconContainerStyle: {\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        height: spacing.spacing.s8,\n        width: spacing.spacing.s8,\n        backgroundColor: color.primaryButtonIcon,\n        borderRadius: 1000,\n      },\n      waveStyle: {\n        backgroundColor: color.receiveBubbleIcon,\n        width: 2,\n        marginHorizontal: 1,\n        height: 18,\n      },\n      waveContainerStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        height: 30,\n        overflow: \"hidden\",\n      },\n      playProgressTextStyle: {\n        ...typography.caption2.regular,\n        color: color.neutral600,\n        lineHeight: 12,\n      },\n    },\n    outgoingAudioBubbleStyle: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        height: 74,\n        width: 240,\n      },\n      playViewContainerStyle: {\n        width: 216,\n        height: 32,\n        gap: spacing.padding.p3,\n        flexDirection: \"row\",\n      },\n      playIconStyle: {\n        tintColor: color.sendBubbleBackground,\n        width: spacing.spacing.s7,\n        height: spacing.spacing.s7,\n      },\n      playIconContainerStyle: {\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        height: spacing.spacing.s8,\n        width: spacing.spacing.s8,\n        backgroundColor: color.primaryButtonIcon,\n        borderRadius: 1000,\n      },\n      waveStyle: {\n        backgroundColor: color.primaryButtonIcon,\n        width: 2,\n        marginHorizontal: 1,\n        height: 18,\n      },\n      waveContainerStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        height: 30,\n        overflow: \"hidden\",\n      },\n      playProgressTextStyle: {\n        ...typography.caption2.regular,\n        color: color.staticWhite,\n        lineHeight: 12,\n      },\n    },\n  };\n};\n\nexport const getAudioBubbleStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingAudioBubbleStyle: Partial<CometChatTheme[\"audioBubbleStyles\"]>;\n  outgoingAudioBubbleStyle: Partial<CometChatTheme[\"audioBubbleStyles\"]>;\n} => {\n  return {\n    incomingAudioBubbleStyle: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        width: 240,\n      },\n      playViewContainerStyle: {\n        width: 216,\n        height: 32,\n        gap: spacing.padding.p3,\n        flexDirection: \"row\",\n      },\n      playIconStyle: {\n        tintColor: color.sendBubbleBackground,\n        width: spacing.spacing.s7,\n        height: spacing.spacing.s7,\n      },\n      playIconContainerStyle: {\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        height: spacing.spacing.s8,\n        width: spacing.spacing.s8,\n        backgroundColor: color.staticWhite,\n        borderRadius: 1000,\n      },\n      waveStyle: {\n        backgroundColor: color.receiveBubbleIcon,\n        width: 2,\n        marginHorizontal: 1,\n        height: 18,\n      },\n      waveContainerStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        height: 30,\n        overflow: \"hidden\",\n      },\n      playProgressTextStyle: {\n        ...typography.caption2.regular,\n        color: color.neutral600,\n        lineHeight: 12,\n      },\n    },\n    outgoingAudioBubbleStyle: {\n      containerStyle: {\n        padding: spacing.padding.p3,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        height: 74,\n        width: 240,\n      },\n      playViewContainerStyle: {\n        width: 216,\n        height: 32,\n        gap: spacing.padding.p3,\n        flexDirection: \"row\",\n      },\n      playIconStyle: {\n        tintColor: color.sendBubbleBackground,\n        width: spacing.spacing.s7,\n        height: spacing.spacing.s7,\n      },\n      playIconContainerStyle: {\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        height: spacing.spacing.s8,\n        width: spacing.spacing.s8,\n        backgroundColor: color.primaryButtonIcon,\n        borderRadius: 1000,\n      },\n      waveStyle: {\n        backgroundColor: color.primaryButtonIcon,\n        width: 2,\n        marginHorizontal: 1,\n        height: 18,\n      },\n      waveContainerStyle: {\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        height: 30,\n        overflow: \"hidden\",\n      },\n      playProgressTextStyle: {\n        ...typography.caption2.regular,\n        color: color.staticWhite,\n        lineHeight: 12,\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAvatar/CometChatAvatar.tsx",
    "content": "import React, { useCallback, useMemo } from \"react\";\nimport { Image, ImageSourcePropType, Text, View } from \"react-native\";\nimport { useCompTheme, useTheme } from \"../../../theme/hook\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\n\n/**\n * Properties for the CometChatAvatar component.\n */\ninterface CometChatAvatarProps {\n  /**\n   * The image source for the avatar.\n   * Can be a remote URI string, an object with a URI property, or a local require() number.\n   */\n  image?: ImageSourcePropType;\n  /**\n   * The name to display if no valid image is provided.\n   */\n  name: string;\n  /**\n   * Custom style for the avatar component, overriding theme styles.\n   */\n  style?: CometChatTheme[\"avatarStyle\"];\n}\n\n/**\n * A functional component that renders a user's avatar.\n *\n * Props for the avatar.\n * The rendered avatar view.\n */\nexport const CometChatAvatar = (props: CometChatAvatarProps) => {\n  const theme = useTheme();\n  const compTheme = useCompTheme();\n\n  const { image, name, style = {} } = props;\n\n  // Merges theme styles with component styles and custom styles.\n  const avatarStyle = useMemo(() => {\n    return deepMerge(theme.avatarStyle, compTheme.avatarStyle ?? {}, style);\n  }, [theme.avatarStyle, style, compTheme.avatarStyle]);\n\n  // Returns an Image view if a valid image is provided, otherwise a text view with initials.\n  const getImageView = useCallback(() => {\n    const imageSource = typeof image === \"string\" ? { uri: image } : image;\n    if (\n      (typeof imageSource === \"object\" &&\n        \"uri\" in imageSource &&\n        typeof imageSource.uri === \"string\") ||\n      typeof imageSource === \"number\"\n    ) {\n      return <Image source={imageSource} style={[avatarStyle.imageStyle]} />;\n    }\n    const initials = [...(name || \"\")].slice(0, 2).join(\"\").toUpperCase();\n    \n    return (\n      <Text \n        style={[avatarStyle.textStyle]}\n        adjustsFontSizeToFit={true}\n        numberOfLines={1}\n      >\n        {initials}\n      </Text>\n    );\n  }, [image, name, avatarStyle.imageStyle, avatarStyle.textStyle]);\n\n  return <View style={[avatarStyle.containerStyle]}>{getImageView()}</View>;\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAvatar/index.ts",
    "content": "export { CometChatAvatar } from \"./CometChatAvatar\";\nexport { getAvatarStyle } from \"./styles\";\nexport type {AvatarStyle} from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatAvatar/styles.ts",
    "content": "import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport type AvatarStyle = {\n  containerStyle: ViewStyle;\n  textStyle: TextStyle;\n  imageStyle: ImageStyle;\n};\n\nexport const getAvatarStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): AvatarStyle => {\n  return StyleSheet.create({\n    containerStyle: {\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      backgroundColor: color.extendedPrimary500,\n      height: spacing.spacing.s12,\n      width: spacing.spacing.s12,\n      borderRadius: spacing.radius.max,\n    },\n    imageStyle: {\n      height: \"100%\",\n      width: \"100%\",\n      borderRadius: spacing.radius.max,\n    },\n    textStyle: {\n      textAlign: \"center\",\n      textAlignVertical: \"center\",\n      fontSize: typography.heading2.bold.fontSize,\n      color: color.primaryButtonIcon,\n      fontFamily: typography.fontFamily,\n      ...typography.heading2.bold,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatBadge/CometChatBadge.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { Text, View, Dimensions } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { BadgeStyle } from \"./styles\";\n\n/**\n * Props for the CometChatBadge component.\n */\nexport interface CometChatBadgeProps {\n  /**\n   * The count to display on the badge.\n   * If the count exceeds 999, it will be shown as \"999+\".\n   */\n  count: number | string;\n  /**\n   * Custom style for the badge.\n   */\n  style?: Partial<BadgeStyle>;\n}\n\n/**\n * CometChatBadge component renders the unread message count as a badge.\n *\n * This component returns a badge with custom styling that displays the number of unread messages.\n *\n * Props for the component.\n * The rendered badge element or null if count is 0.\n */\nexport const CometChatBadge = (props: CometChatBadgeProps) => {\n  const { style = {}, count = 0 } = props;\n  const theme = useTheme();\n\n  // Merge the theme's badge style with any custom styles provided.\n  const badgeStyle = useMemo(() => {\n    return deepMerge(theme.badgeStyle, style ?? {});\n  }, [theme.badgeStyle, style]);\n\n  // Convert count to text, formatting as \"999+\" if count is greater than 999.\n  const countText: string = useMemo(() => {\n    if (Number.isInteger(count)) {\n      if ((count as number) === 0) return \"\";\n      if ((count as number) > 999) return \"999+\";\n      return (count as number).toString();\n    }\n    return count.toString();\n  }, [count]);\n\n  // If count is zero or an empty string, do not render the badge.\n  if (countText.length === 0) return null;\n\n  const fontScale = Dimensions.get('window').fontScale;\n  const badgeSize = 25 * fontScale;\n\n  return (\n    <View style={[badgeStyle.containerStyle, { width: badgeSize, height: badgeSize }]}>\n      <Text style={[badgeStyle.textStyle]} numberOfLines={1} adjustsFontSizeToFit={true}>{countText}</Text>\n    </View>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatBadge/index.ts",
    "content": "export { CometChatBadge } from \"./CometChatBadge\";\nexport type { CometChatBadgeProps } from \"./CometChatBadge\";\nexport { getBadgeStyle } from \"./styles\";\nexport type { BadgeStyle } from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatBadge/styles.ts",
    "content": "import { StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport type BadgeStyle = {\n  containerStyle?: ViewStyle;\n  textStyle?: TextStyle;\n};\n\nexport const getBadgeStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): BadgeStyle => {\n  return StyleSheet.create({\n    containerStyle: {\n      backgroundColor: color.primary,\n      borderRadius: spacing.radius.max,\n      minWidth: spacing.spacing.s5,\n      maxWidth: spacing.spacing.s12,\n      height: spacing.spacing.s5,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      paddingHorizontal: spacing.spacing.s1,\n      paddingVertical: spacing.spacing.s0_5,\n    },\n    textStyle: {\n      color: color.primaryButtonIcon,\n      ...typography.caption1.regular,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatBottomSheet/CometChatBottomSheet.tsx",
    "content": "import React, {\n  forwardRef,\n  useEffect,\n  useImperativeHandle,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  Animated,\n  BackHandler,\n  Dimensions,\n  Modal,\n  View,\n  Easing,\n  DimensionValue,\n  KeyboardAvoidingView,\n  Platform,\n  ScrollView,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\n\n/**\n * Props for the CometChatBottomSheet component.\n */\nexport interface CometChatBottomSheetInterface {\n  /**\n   * Content to be rendered inside the bottom sheet.\n   */\n  children?: any;\n  /**\n   * Determines whether the bottom sheet is open.\n   */\n  isOpen?: boolean;\n  /**\n   * Callback function invoked when the bottom sheet is opened.\n   */\n  onOpen?: Function;\n  /**\n   * Callback function invoked when the bottom sheet is requested to close.\n   */\n  onClose?: Function;\n  /**\n   * iOS specific callback invoked when the modal is dismissed.\n   */\n  onDismiss?: () => void; //iOS\n  /**\n   * Custom styles for the bottom sheet.\n   */\n  style?: {\n    shadowColor?: string;\n    backgroundColor?: string;\n    lineColor?: string;\n    lineHeight?: number;\n    paddingHorizontal?: number;\n    borderRadius?: number;\n    paddingBottom?: number;\n    maxHeight?: DimensionValue;\n    minHeight?: DimensionValue;\n  };\n  /**\n   * Enables scrolling for the bottom sheet content.\n   */\n  scrollEnabled?: boolean;\n  /**\n   * When rendering a FlatList, pass this as true to enable {height: 100%}.\n   */\n  doNotOccupyEntireHeight?: boolean;\n}\n\n/**\n * CometChatBottomSheet is a component that renders a modal bottom sheet.\n * It provides a customizable bottom sheet with overlay fade effect and keyboard handling.\n *\n * @param {CometChatBottomSheetInterface} props - Props for the bottom sheet component.\n * @param {React.Ref<any>} ref - Reference object to expose component methods.\n * @returns {JSX.Element} The rendered modal bottom sheet.\n */\nconst CometChatBottomSheet = forwardRef(\n  (props: CometChatBottomSheetInterface, ref) => {\n    const theme = useTheme();\n    const {\n      style = {\n        maxHeight: Dimensions.get(\"screen\").height * 0.9,\n        minHeight: 50,\n      },\n      children = <View />,\n      isOpen = true,\n      onClose = null,\n      scrollEnabled = false,\n      doNotOccupyEntireHeight = false,\n      onDismiss,\n    } = props;\n\n    // All hooks called unconditionally at top level\n    const overlayAnim = useRef(new Animated.Value(0)).current;\n    const [isModalOpen, setIsModalOpen] = useState(isOpen);\n\n    useEffect(() => {\n      setIsModalOpen(isOpen);\n    }, [isOpen]);\n\n    // Fade in/out the overlay when isOpen changes\n    useEffect(() => {\n      if (isOpen) {\n        // Fade from 0 -> 0.3 over 100ms\n        Animated.timing(overlayAnim, {\n          toValue: 0.3,\n          duration: 100,\n          easing: Easing.ease,\n          useNativeDriver: false,\n        }).start();\n      } else {\n        // Immediately reset to 0 if closed\n        overlayAnim.setValue(0);\n      }\n    }, [isOpen, overlayAnim]);\n\n    /**\n     * Toggles the bottom sheet panel by invoking the onClose callback.\n     */\n    const togglePanel = () => {\n      onClose && onClose();\n    };\n\n    /**\n     * Handles the hardware back button press.\n     * Closes the bottom sheet if it is open.\n     *\n     * @returns True if the back press is handled.\n     */\n    const onBackPress = () => {\n      if (isOpen) {\n        togglePanel();\n        return true; // Prevent default back behavior\n      }\n      return false;\n    };\n\n    useEffect(() => {\n      const backHandler = BackHandler.addEventListener(\n        \"hardwareBackPress\",\n        onBackPress\n      );\n\n      return () => {\n        backHandler.remove();\n      };\n    }, [onBackPress]);\n\n    useImperativeHandle(ref, () => {\n      return {\n        togglePanel,\n      };\n    });\n\n    return (\n      <Modal\n        animationType=\"fade\"\n        transparent={true}\n        hardwareAccelerated={true}\n        visible={isModalOpen}\n        onRequestClose={togglePanel}\n        onDismiss={onDismiss}\n        supportedOrientations={[\"portrait\", \"landscape\"]}\n      >\n        {/* Animated overlay that fades in from transparent to a semi-transparent black */}\n        <Animated.View\n          style={{\n            flex: 1,\n            backgroundColor: overlayAnim.interpolate({\n              inputRange: [0, 0.2],\n              outputRange: [\"rgba(0,0,0,0)\", \"rgba(0,0,0,0.35)\"],\n            }),\n          }}\n          onStartShouldSetResponder={() => {\n            togglePanel();\n            return true;\n          }}\n        />\n\n        {/* Bottom sheet content */}\n        <KeyboardAvoidingView\n          behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n          enabled={Platform.OS === \"ios\"}\n          style={{\n            backgroundColor: theme.color.background1,\n            paddingTop: 25,\n            position: \"absolute\",\n            bottom: 0,\n            left: 0,\n            right: 0,\n            borderTopLeftRadius: 24,\n            borderTopRightRadius: 24,\n            shadowOffset: { width: 0, height: 6 },\n            shadowOpacity: 0.37,\n            shadowRadius: 7.49,\n            elevation: 12,\n            paddingHorizontal: 5,\n            maxHeight: style.maxHeight,\n            minHeight: style.minHeight,\n            ...(doNotOccupyEntireHeight ? {} : { height: \"100%\" }),\n          }}\n        >\n          {scrollEnabled ? (\n            <ScrollView\n              scrollEnabled={scrollEnabled}\n              keyboardShouldPersistTaps=\"always\"\n              style={{ flex: 1 }}\n            >\n              {typeof children === \"function\" ? children() : children}\n            </ScrollView>\n          ) : typeof children === \"function\" ? (\n            children()\n          ) : (\n            children\n          )}\n        </KeyboardAvoidingView>\n      </Modal>\n    );\n  }\n);\n\nexport { CometChatBottomSheet };"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatBottomSheet/index.ts",
    "content": "import { CometChatBottomSheet, CometChatBottomSheetInterface } from \"./CometChatBottomSheet\";\n\nexport { CometChatBottomSheet };\nexport type {CometChatBottomSheetInterface};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatConfirmDialog/CometChatConfirmDialog.tsx",
    "content": "import React, { JSX } from \"react\";\nimport {\n  ImageSourcePropType,\n  ImageStyle,\n  Modal,\n  Pressable,\n  StyleProp,\n  StyleSheet,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\n\n/**\n * Props for the CometChatConfirmDialog component.\n */\nexport interface CometChatConfirmDialogInterface {\n  /**\n   * Callback invoked when the confirm action is triggered.\n   */\n  onConfirm?: () => void;\n  /**\n   * Callback invoked when the cancel action is triggered.\n   */\n  onCancel?: () => void;\n  /**\n   * Callback invoked when the dialog is dismissed (iOS specific).\n   */\n  onDismiss?: () => void;\n  /**\n   * Flag to determine if the dialog is visible.\n   */\n  isOpen?: boolean;\n  /**\n   * Custom style for the container view.\n   */\n  containerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Icon to be displayed. Can be an image source or a JSX element.\n   */\n  icon?: ImageSourcePropType | JSX.Element;\n  /**\n   * Custom style for the icon image.\n   */\n  iconImageStyle?: StyleProp<ImageStyle>;\n  /**\n   * Custom style for the icon container.\n   */\n  iconContainerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the title text.\n   */\n  titleTextStyle?: StyleProp<TextStyle>;\n  /**\n   * Custom style for the message text.\n   */\n  messageTextStyle?: StyleProp<TextStyle>;\n  /**\n   * Custom style for the cancel button container.\n   */\n  cancelButtonStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the confirm button container.\n   */\n  confirmButtonStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the cancel button text.\n   */\n  cancelButtonTextStyle?: StyleProp<TextStyle>;\n  /**\n   * Custom style for the confirm button text.\n   */\n  confirmButtonTextStyle?: StyleProp<TextStyle>;\n  /**\n   * The title text of the dialog.\n   */\n  titleText?: string;\n  /**\n   * The message text of the dialog.\n   */\n  messageText?: string;\n  /**\n   * The text to display on the cancel button.\n   */\n  cancelButtonText?: string;\n  /**\n   * The text to display on the confirm button.\n   */\n  confirmButtonText?: string;\n}\n\n/**\n * CometChatConfirmDialog is a modal dialog component that displays a confirmation prompt.\n * It is typically used to confirm actions such as deletion.\n *\n * Props for the confirm dialog component.\n * The rendered confirmation dialog.\n */\nexport const CometChatConfirmDialog = (props: CometChatConfirmDialogInterface) => {\n  const {\n    onConfirm,\n    onCancel,\n    onDismiss = () => null,\n    isOpen,\n    icon,\n    containerStyle = {},\n    iconImageStyle = {},\n    iconContainerStyle = {},\n    titleTextStyle = {},\n    messageTextStyle = {},\n    cancelButtonStyle = {},\n    confirmButtonStyle = {},\n    cancelButtonTextStyle = {},\n    confirmButtonTextStyle = {},\n    titleText = \"\",\n    messageText = \"\",\n    cancelButtonText = \"Cancel\",\n    confirmButtonText = \"Delete\",\n  } = props;\n\n  const theme = useTheme();\n\n  return (\n    <Modal\n      animationType=\"fade\"\n      transparent={true}\n      visible={isOpen}\n      onRequestClose={onCancel}\n      onDismiss={onDismiss}\n    >\n      <Pressable onPress={onCancel} style={styles.wrapper}>\n        <View style={[theme.confirmDialogStyles.containerStyle, containerStyle]}>\n          {icon && (\n            <View style={[theme.confirmDialogStyles.iconContainerStyle, iconContainerStyle]}>\n              <Icon\n                icon={icon}\n                imageStyle={[theme.confirmDialogStyles.iconImageStyle, iconImageStyle]}\n              />\n            </View>\n          )}\n          {titleText && (\n            <Text style={[theme.confirmDialogStyles.titleTextStyle, titleTextStyle]}>\n              {titleText}\n            </Text>\n          )}\n          {messageText && (\n            <Text style={[theme.confirmDialogStyles.messageTextStyle, messageTextStyle]}>\n              {messageText}\n            </Text>\n          )}\n          <View style={styles.buttonContainer}>\n            <TouchableOpacity\n              style={[theme.confirmDialogStyles.cancelButtonStyle, cancelButtonStyle]}\n              onPress={(e) => {\n                e.stopPropagation();\n                onCancel?.();\n              }}\n            >\n              <Text numberOfLines={1} style={[theme.confirmDialogStyles.cancelButtonTextStyle, cancelButtonTextStyle]}>\n                {cancelButtonText}\n              </Text>\n            </TouchableOpacity>\n            <TouchableOpacity\n              style={[theme.confirmDialogStyles.confirmButtonStyle, confirmButtonStyle]}\n              onPress={(e) => {\n                e.stopPropagation();\n                onConfirm?.();\n              }}\n            >\n              <Text numberOfLines={1} style={[theme.confirmDialogStyles.confirmButtonTextStyle, confirmButtonTextStyle]}>\n                {confirmButtonText}\n              </Text>\n            </TouchableOpacity>\n          </View>\n        </View>\n      </Pressable>\n    </Modal>\n  );\n};\n\nconst styles = StyleSheet.create({\n  wrapper: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    backgroundColor: \"#141414CC\",\n    paddingHorizontal: 20,\n  },\n  buttonContainer: {\n    flexDirection: \"row\",\n    width: \"100%\",\n    gap: 8,\n  },\n});"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatConfirmDialog/index.ts",
    "content": "import { CometChatConfirmDialog, CometChatConfirmDialogInterface } from \"./CometChatConfirmDialog\";\nexport { CometChatConfirmDialog };\nexport type { CometChatConfirmDialogInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatConfirmDialog/style.ts",
    "content": "import { ImageSourcePropType, ImageStyle, StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { JSX } from \"react\";\n\nexport type ConfirmDialogStyle = {\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  messageTextStyle: TextStyle;\n  icon?: ImageSourcePropType | JSX.Element;\n  iconContainerStyle: ViewStyle;\n  iconImageStyle: ImageStyle;\n  cancelButtonStyle: ViewStyle;\n  confirmButtonStyle: ViewStyle;\n  cancelButtonTextStyle: TextStyle;\n  confirmButtonTextStyle: TextStyle;\n};\n\nexport const getConfirmDialogStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ConfirmDialogStyle => {\n  return StyleSheet.create({\n    containerStyle: {\n      backgroundColor: color.background1,\n      width: \"100%\",\n      padding: 24,\n      borderRadius: 16,\n      alignItems: \"center\",\n    },\n    titleTextStyle: {\n      color: color.textPrimary,\n      marginBottom: spacing.margin.m2,\n      textAlign: \"center\",\n      ...typography.heading2.medium,\n    },\n    messageTextStyle: {\n      color: color.textSecondary,\n      marginBottom: 24,\n      textAlign: \"center\",\n      ...typography.body.regular,\n    },\n    iconContainerStyle: {\n      backgroundColor: color.background2,\n      borderRadius: spacing.radius.max,\n      width: spacing.spacing.s20,\n      height: spacing.spacing.s20,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      marginBottom: spacing.spacing.s3,\n    },\n    iconImageStyle: {\n      height: spacing.spacing.s20,\n      width: spacing.spacing.s20,\n      tintColor: color.iconPrimary,\n    },\n    cancelButtonStyle: {\n      flex: 1,\n      borderRadius: spacing.radius.r2,\n      height: 45,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      borderWidth: 1,\n      borderColor: color.borderDark,\n    },\n    cancelButtonTextStyle: {\n      color: color.textPrimary,\n      ...typography.button.medium,\n      paddingHorizontal: 6,\n      textAlign: \"center\",\n    },\n    confirmButtonStyle: {\n      flex: 1,\n      backgroundColor: color.error,\n      borderRadius: spacing.radius.r2,\n      height: 45,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    confirmButtonTextStyle: {\n      color: color.primaryButtonIcon,\n      paddingHorizontal: 6,\n      textAlign: \"center\",\n      ...typography.button.medium,\n    },\n  });\n};\n\nexport const getConfirmDialogStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ConfirmDialogStyle => {\n  return <Omit<ConfirmDialogStyle, \"icon\">>(\n    deepMerge(getConfirmDialogStyleLight(color, spacing, typography), {\n      containerStyle: {\n        backgroundColor: color.background2,\n      },\n      titleTextStyle: {\n        color: color.textPrimary,\n      },\n      messageTextStyle: {\n        color: color.textSecondary,\n      },\n      iconImageStyle: {\n        tintColor: color.iconPrimary,\n      },\n      iconContainerStyle: {\n        backgroundColor: color.background1,\n      },\n      cancelButtonStyle: {\n        borderColor: color.borderDark,\n      },\n      cancelButtonTextStyle: {\n        color: color.textPrimary,\n      },\n      confirmButtonStyle: {\n        backgroundColor: color.error,\n      },\n      confirmButtonTextStyle: {\n        color: color.primaryButtonIcon,\n      },\n    })\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDate/CometChatDate.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { Text, View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { useCompTheme } from \"../../../theme/hook\";\nimport { LocalizedDateHelper } from \"./../../helper/LocalizedDateHelper\";\nimport { useLocalizedDate } from \"./../../helper/useLocalizedDateHook\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { ValueOf } from \"../../helper/types\";\nimport { DateStyle } from \"./styles\";\n\n/**\n * Props for the CometChatDate component.\n */\nexport interface CometChatDateInterface {\n  /**\n  * Unix epoch time to be formatted and displayed.\n  */\n  timeStamp?: number;\n  /**\n * Pattern for formatting the date.\n * One of the following values:\n * - timeFormat: \"hh:mm a\".\n * - dayDateFormat: Today, Yesterday, week-day or \"d MMM, yyyy\".\n * - dayWeekDayDateTimeFormat: Today, Yesterday, week-day or \"dd/mm/yyyy\".\n */\n  pattern?: ValueOf<typeof LocalizedDateHelper.patterns>;\n  /**\n   * A custom date string to override the formatted date.\n   */\n  customDateString?: string;\n  /**\n   * Custom styles for the date component.\n   */\n  style?: DateStyle;\n}\n\n/**\n * CometChatDate is a component for displaying a formatted date/time.\n *\n * If a customDateString is provided, it will be displayed instead of the formatted date.\n * Otherwise, the component formats the provided timeStamp using the given pattern.\n *\n *  - Props for the component.\n *  The rendered date view.\n */\nexport const CometChatDate = (props: CometChatDateInterface) => {\n  const { timeStamp, pattern, customDateString, style = {} } = props;\n  const theme = useTheme();\n  const compTheme = useCompTheme();\n  const { formatDate } = useLocalizedDate();\n  if (!timeStamp) return null;\n\n  // Merge theme date styles with component date styles and any custom style overrides.\n  const dateStyles = useMemo(() => {\n    return deepMerge(theme.dateStyles, compTheme.dateStyles ?? {}, style);\n  }, [theme.dateStyles, style, compTheme.dateStyles]);\n\n  return (\n    <View style={[dateStyles.containerStyle]}>\n      <Text style={[dateStyles.textStyle]} numberOfLines={1}>\n        {timeStamp\n        ? customDateString\n         ? customDateString\n          : pattern && formatDate(timeStamp, pattern)\n        : \"\"}\n      </Text>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDate/index.ts",
    "content": "export { CometChatDate } from \"./CometChatDate\";\nexport type {CometChatDateInterface} from \"./CometChatDate\";\nexport { getDateStyleLight, getDateStyleDark } from \"./styles\";\nexport type { DateStyle } from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDate/styles.ts",
    "content": "import { StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\n\nexport type DateStyle = {\n  containerStyle?: ViewStyle;\n  textStyle?: TextStyle;\n};\n\nexport const getDateStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DateStyle => {\n  return StyleSheet.create({\n    containerStyle: {},\n    textStyle: {\n      color: color.textSecondary,\n      ...typography.caption1.regular,\n    },\n  });\n};\n\nexport const getDateStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DateStyle => {\n  return StyleSheet.create(\n    deepMerge(getDateStyleLight(color, spacing, typography), {\n      textStyle: {\n        color: color.textSecondary,\n      },\n    })\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDateSeperator/CometChatDateSeparator.tsx",
    "content": "import React from \"react\";\nimport { Text, View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { LocalizedDateHelper } from \"../../helper/LocalizedDateHelper\";\nimport { ValueOf } from \"../../helper/types\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { useLocalizedDate } from \"./../../helper/useLocalizedDateHook\";\n\n/**\n * Props for the CometChatDateSeparator component.\n */\nexport interface CometChatDateSeparatorInterface {\n  /**\n   * Unix epoch time used to display the date.\n   */\n  timeStamp: number;\n  /**\n   * Pattern for formatting the date.\n   * One of the following values:\n   * - timeFormat: \"hh:mm a\"\n   * - dayDateFormat: Today, Yesterday, or \"d MMM, yyyy\"\n   * - dayWeekDayDateTimeFormat: Today (time), weekday, Yesterday, or \"dd/mm/yyyy\"\n   */\n  pattern: ValueOf<typeof LocalizedDateHelper.patterns>;\n  /**\n   * Custom string to be displayed instead of the formatted date.\n   */\n  customDateString?: string;\n  /**\n   * Custom styles for the date separator component.\n   */\n  style?: CometChatTheme[\"dateSeparatorStyles\"];\n}\n\n/**\n * CometChatDateSeparator is a component that displays a formatted date/time separator\n * between messages. If a custom date string is provided, it will be displayed instead.\n *  \n *  - Props for the component.\n *  The rendered date separator view.\n */\nexport const CometChatDateSeparator = (props: CometChatDateSeparatorInterface) => {\n  const { timeStamp, pattern, customDateString, style } = props;\n  const theme = useTheme();\n  const { formatDate } = useLocalizedDate();\n\n  return (\n    <View style={[theme.dateSeparatorStyles.containerStyle, style?.containerStyle]}>\n      <Text style={[theme.dateSeparatorStyles.textStyle, style?.textStyle]}>\n        {customDateString\n          ? customDateString\n          : formatDate(timeStamp, pattern)}\n      </Text>\n    </View>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDateSeperator/index.ts",
    "content": "export { CometChatDateSeparator } from \"./CometChatDateSeparator\";\nexport {\n  getDateSeparatorStyleLight,\n  getDateSeparatorStyleDark,\n} from \"./styles\";\nexport type { CometChatDateSeparatorInterface } from \"./CometChatDateSeparator\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDateSeperator/styles.ts",
    "content": "import { StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport type DateSeparatorStyle = {\n  containerStyle: ViewStyle;\n  textStyle: TextStyle;\n};\n\nexport const getDateSeparatorStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DateSeparatorStyle => {\n  return StyleSheet.create({\n    containerStyle: {\n      paddingHorizontal: spacing.padding.p2,\n      paddingVertical: spacing.padding.p1,\n      borderWidth: 1,\n      //borderColor: color.borderLight,\n      borderRadius: spacing.radius.r1,\n      alignSelf: \"center\",\n      // TODO: Test this in IOS\n      // shadowColor: \"#101828\",\n      // shadowOffset: { width: 0, height: 1 },\n      // shadowOpacity: 0.05, // 13 in hex (#0D) is 0.05 in decimal\n      // shadowRadius: 2,\n      // elevation: 2, // Only for Android\n    },\n    textStyle: {\n      color: color.textPrimary,\n      ...typography.caption1.medium,\n    },\n  });\n};\n\nexport const getDateSeparatorStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DateSeparatorStyle => {\n  return StyleSheet.create(\n    deepMerge(getDateSeparatorStyleLight(color, spacing, typography), {\n      containerStyle: {\n        borderColor: color.borderDark,\n      },\n      textStyle: {\n        color: color.textPrimary,\n      },\n    })\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDeletedBubble/CometChatDeletedBubble.tsx",
    "content": "import React from \"react\";\nimport { View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CometChatTextBubbleText } from \"../CometChatTextBubble/CometChatTextBubble\";\nimport { DeletedBubbleStyle } from \"./styles\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatDeletedBubble component.\n */\nexport interface CometChatDeletedBubbleInterface {\n  /**\n   * Text to be displayed in the deleted message bubble.\n   */\n  text?: string;\n  /**\n   * Custom styles for the deleted bubble.\n   */\n  style?: Partial<DeletedBubbleStyle>;\n}\n\n/**\n * CometChatDeletedBubble is a component that displays a deleted message bubble.\n * It shows an icon along with the provided text.\n *\n * - Props for the component.\n *  The rendered deleted message bubble.\n */\nexport const CometChatDeletedBubble = (props: CometChatDeletedBubbleInterface) => {\n  const theme = useTheme();\n  const {t}=useCometChatTranslation()\n  const { text = t(\"DELETE_MSG_TEXT\"), style = {} } = props;\n\n  return (\n    <View\n      style={{\n        flexDirection: \"row\",\n        gap: theme.spacing.spacing.s1,\n        alignItems: \"center\",\n      }}\n    >\n      <Icon\n        name=\"block\"\n        icon={style?.icon}\n        height={style?.iconStyle?.height ?? 16}\n        width={style?.iconStyle?.width ?? 16}\n        color={style?.iconStyle?.tintColor}\n        imageStyle={style?.iconStyle}\n        containerStyle={style?.iconContainerStyle}\n      />\n      <CometChatTextBubbleText text={text} textStyle={[style?.textStyle]} />\n    </View>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDeletedBubble/index.ts",
    "content": "export { CometChatDeletedBubble } from \"./CometChatDeletedBubble\";\nexport type { CometChatDeletedBubbleInterface } from \"./CometChatDeletedBubble\";\n\nexport { getDeletedBubbleStyle } from \"./styles\";\nexport type { DeletedBubbleStyle } from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatDeletedBubble/styles.ts",
    "content": "import { ImageSourcePropType, ImageStyle, StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { JSX } from \"react\";\n\nexport type DeletedBubbleStyle = {\n  containerStyle?: ViewStyle;\n  textStyle?: TextStyle;\n  icon?: JSX.Element | ImageSourcePropType;\n  iconContainerStyle?: ViewStyle;\n  iconStyle?: ImageStyle;\n};\n\nexport const getDeletedBubbleStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: DeletedBubbleStyle;\n  outgoingBubbleStyle: DeletedBubbleStyle;\n} => {\n  return {\n    incomingBubbleStyle: {\n      containerStyle: {},\n      textStyle: {\n        color: color.receiveBubbleTimestamp,\n        ...typography.body.regular,\n      },\n      iconStyle: {\n        tintColor: color.receiveBubbleTimestamp\n      }\n    },\n    outgoingBubbleStyle: {\n      containerStyle: {},\n      textStyle: {\n        color: color.sendBubbleText,\n        ...typography.body.regular,\n      },\n      iconStyle: {\n        tintColor: color.sendBubbleText\n      }\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/CometChatEmojiKeyboard.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n  FlatList,\n  Platform,\n  StyleProp,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\nimport { Emojis } from \"./emojis\";\nimport Styles from \"./style\";\nimport { SafeAreaView, useSafeAreaInsets } from \"react-native-safe-area-context\";\n\n/**\n * Type definitions for the emoji items and categories.\n */\ntype EmojiItem = {\n  char: string;\n  keywords: string[];\n};\n\ntype Section = {\n  id: string;\n  title: string;\n  data: EmojiItem[];\n};\n\ntype IconName =\n  | \"smiley-emoji\"\n  | \"activity-emoji\"\n  | \"animals-nature-emoji\"\n  | \"flags-emoji\"\n  | \"food-drink-emoji\"\n  | \"objects-emoji\"\n  | \"symbols-emoji\"\n  | \"travel-emoji\";\n\n/**\n * Mapping from category names to icon names.\n * Adjust these icons as per the assets available in your project.\n */\nconst categoryIconMapping: Record<string, IconName> = {\n  Smileys: \"smiley-emoji\",\n  Activity: \"activity-emoji\",\n  Animals: \"animals-nature-emoji\",\n  Flags: \"flags-emoji\",\n  Food: \"food-drink-emoji\",\n  Objects: \"objects-emoji\",\n  Symbols: \"symbols-emoji\",\n  Travel: \"travel-emoji\",\n} as const;\n\n/**\n * The number of columns in the emoji grid.\n * Adjust this value based on available space and desired layout.\n */\nconst NUM_COLUMNS = 8;\n\n/**\n * Props for the category list component.\n */\ninterface CategoryListProps {\n  onCategorySelected: (id: string) => void;\n  activeCategory: string;\n  categoryIconTint?: string;\n  selectedCategoryIconTint?: string;\n}\n\n/**\n * A memoized component displaying the category tabs (icons) at the bottom.\n */\nconst CategoryList: React.FC<CategoryListProps> = React.memo(\n  ({ onCategorySelected, activeCategory, categoryIconTint, selectedCategoryIconTint }) => {\n    const theme = useTheme();\n    const styles = useMemo(() => Styles(theme), [theme]);\n\n    return (\n      <View style={styles.categoryListContainer}>\n        {Emojis.map((category: any) => {\n          // Each category object is expected to have a key and associated info.\n          const key: string = Object.keys(category)[0];\n          const emojiCategory = category[key];\n\n          // Determine the appropriate icon for the category.\n          const iconName = categoryIconMapping[emojiCategory.symbol] || \"smiley-emoji\";\n          const isActive = activeCategory === emojiCategory.id;\n\n          return (\n            <TouchableOpacity\n              key={emojiCategory.id}\n              onPress={() => onCategorySelected(emojiCategory.id)}\n              accessibilityLabel={`Category ${emojiCategory.name}`}\n              accessibilityRole='button'\n            >\n              <View style={[styles.iconContainer, isActive && styles.activeIcon]}>\n                <Icon\n                  name={iconName}\n                  size={24}\n                  color={\n                    isActive\n                      ? selectedCategoryIconTint || theme.color.iconHighlight\n                      : categoryIconTint || theme.color.iconSecondary\n                  }\n                />\n              </View>\n            </TouchableOpacity>\n          );\n        })}\n      </View>\n    );\n  }\n);\n\n/**\n * Props for the main CometChatEmojiKeyboard component.\n */\ninterface CometChatEmojiKeyboardProps {\n  /** Callback when an emoji is clicked. */\n  onClick?: (emoji: string) => void;\n  /** Style overrides for customizing the appearance of the emoji keyboard. */\n  style?: {\n    borderRadius?: number;\n    categoryBackground?: string;\n    categoryIconTint?: string;\n    selectedCategoryIconTint?: string;\n    sectionHeaderColor?: string;\n    sectionHeaderFont?: StyleProp<TextStyle>;\n    backgroundColor?: string;\n  };\n}\n\n/**\n * The main emoji keyboard component. It displays a header, a grid of emojis for the active category,\n * and category tabs at the bottom for switching between categories.\n */\nconst CometChatEmojiKeyboard: React.FC<CometChatEmojiKeyboardProps> = ({\n  onClick,\n  style: {\n    borderRadius,\n    categoryBackground,\n    categoryIconTint,\n    selectedCategoryIconTint,\n    sectionHeaderColor,\n    sectionHeaderFont,\n    backgroundColor,\n  } = {},\n}) => {\n  const theme = useTheme();\n  const styles = Styles(theme);\n  const insets = useSafeAreaInsets();\n\n  /**\n   * ITEM_HEIGHT is assumed to be the height of one row or the uniform height per row.\n   * If this assumption changes, `getItemLayout` might need to be removed or adjusted.\n   */\n  const ITEM_HEIGHT = 40;\n\n  /**\n   * getItemLayout helps to optimize the FlatList performance by skipping layout calculations\n   * for off-screen items. This works best when item sizes are fixed and known in advance.\n   * Note: With multiple columns, ensure that ITEM_HEIGHT matches the row height.\n   */\n  const getItemLayout = useCallback(\n    (_: any, index: number) => ({\n      length: ITEM_HEIGHT,\n      offset: ITEM_HEIGHT * index,\n      index,\n    }),\n    [ITEM_HEIGHT]\n  );\n\n  // State for the currently active category and header title.\n  const [activeCategory, setActiveCategory] = useState<string | null>(null);\n  const [headerTitle, setHeaderTitle] = useState(\"Emojis\");\n\n  // A ref to the FlatList for programmatic scrolling.\n  const flatListRef = useRef<FlatList<EmojiItem>>(null);\n\n  /**\n   * Handle emoji press events by calling the provided `onClick` callback.\n   */\n  const handleEmojiPress = useCallback(\n    (emoji: string) => {\n      if (onClick) {\n        onClick(emoji);\n      }\n    },\n    [onClick]\n  );\n\n  /**\n   * Prepare sections of emojis. Each category is transformed into a Section object\n   * containing the category id, title, and array of emojis.\n   *\n   * Memoized to prevent re-computation on every render unless `Emojis` changes.\n   */\n  const sections: Section[] = useMemo(() => {\n    return Emojis.map((category: any) => {\n      const key: string = Object.keys(category)[0];\n      const { id, name, emojis } = category[key];\n      const emojiArray: EmojiItem[] = Object.values(emojis);\n\n      return {\n        id,\n        title: name,\n        data: emojiArray,\n      };\n    });\n  }, []);\n\n  /**\n   * Set the initial active category and header title once sections are computed.\n   */\n  useEffect(() => {\n    if (sections.length > 0) {\n      setActiveCategory(sections[0].id);\n      setHeaderTitle(sections[0].title);\n    }\n  }, [sections]);\n\n  /**\n   * Handle category selection from the bottom tabs.\n   * Updates the active category and resets the FlatList scroll to the top.\n   */\n  const handleCategorySelect = useCallback(\n    (id: string) => {\n      if (id !== activeCategory) {\n        const category = sections.find((section) => section.id === id);\n        if (category) {\n          setActiveCategory(id);\n          setHeaderTitle(category.title);\n          if (flatListRef.current) {\n            flatListRef.current.scrollToOffset({ offset: 0, animated: false });\n          }\n        }\n      }\n    },\n    [sections, activeCategory]\n  );\n\n  /**\n   * Render a single emoji item in the FlatList.\n   * The callback is memoized to prevent unnecessary re-renders.\n   */\n  const renderEmojiItem = useCallback(\n    ({ item }: { item: EmojiItem }) => (\n      <TouchableOpacity\n        onPress={() => handleEmojiPress(item.char)}\n        style={styles.emojiItem}\n        accessibilityLabel={`Emoji ${item.char}`}\n        accessibilityRole='button'\n      >\n        <Text style={[styles.emojiText, { color: theme.color.primary }]}>{item.char}</Text>\n      </TouchableOpacity>\n    ),\n    [handleEmojiPress, styles.emojiItem, styles.emojiText, theme.color.primary]\n  );\n\n  /**\n   * Render the FlatList of emojis for the currently active category.\n   * If no category is active, return null.\n   */\n  const renderActiveCategory = () => {\n    const category = sections.find((section) => section.id === activeCategory);\n    if (!category) return null;\n\n    return (\n      <FlatList<EmojiItem>\n        ref={flatListRef}\n        data={category.data}\n        getItemLayout={getItemLayout}\n        keyExtractor={(item) => item.char}\n        renderItem={renderEmojiItem}\n        numColumns={NUM_COLUMNS}\n        contentContainerStyle={styles.flatListContent}\n        showsVerticalScrollIndicator={false}\n        style={styles.flatList}\n        initialNumToRender={80}\n        removeClippedSubviews={true}\n      />\n    );\n  };\n\n  return (\n    <SafeAreaView\n      edges={[\"top\"]}\n      style={[\n        styles.emojiKeyboardContainer,\n        {\n          borderRadius: borderRadius || 0,\n          backgroundColor: backgroundColor || theme.color.background1,\n          flexDirection: \"column\",\n        },\n      ]}\n    >\n      {/* Fixed Header displaying the active category title */}\n      <View style={styles.fixedHeader}>\n        <Text\n          style={[\n            theme.typography.heading4.regular,\n            sectionHeaderFont,\n            { color: sectionHeaderColor || theme.color.textTertiary },\n          ]}\n        >\n          {headerTitle}\n        </Text>\n      </View>\n\n      {/* Emoji Grid for the active category */}\n      <View style={styles.emojiGridContainer}>{renderActiveCategory()}</View>\n\n      {/* Bottom Category Tabs */}\n      <View\n        style={[\n          styles.categoryContainer,\n          {\n            backgroundColor: categoryBackground || theme.color.background1,\n            paddingBottom: Platform.OS === \"ios\" ? insets.bottom : 0,\n          },\n        ]}\n      >\n        <CategoryList\n          onCategorySelected={handleCategorySelect}\n          activeCategory={activeCategory || \"\"}\n          categoryIconTint={categoryIconTint}\n          selectedCategoryIconTint={selectedCategoryIconTint}\n        />\n      </View>\n    </SafeAreaView>\n  );\n};\n\nexport { CometChatEmojiKeyboard };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/Emoji.ts",
    "content": "/**\n * @class CometChatEmoji\n * @description CometChatEmoji class is used for defining the emoji.\n *\n * @param {String} char\n * @param {Array} keywords\n */\nclass CometChatEmoji {\n  char = \"\";\n  keywords = [];\n  constructor({ char, keywords }) {\n    this.char = char;\n    this.keywords = keywords;\n  }\n}\n\nexport { CometChatEmoji };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/EmojiCategory.ts",
    "content": "/**\n * @class CometChatEmojiCategory\n * @description CometChatEmojiCategory class is used for defining the category.\n * @param {String} id\n * @param {String} name\n * @param {String} symbol\n * @param {Object} emojis\n */\nclass CometChatEmojiCategory {\n  id = \"\";\n  name = \"\";\n  symbol = \"\";\n  emojis = {};\n  constructor({ id, name, emojis, symbol }) {\n    this.id = id;\n    this.name = name;\n    this.emojis = emojis;\n    this.symbol = symbol;\n  }\n}\n\nexport { CometChatEmojiCategory };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/emojis.ts",
    "content": "import { getCometChatTranslation } from \"../../resources/CometChatLocalizeNew/LocalizationManager\";\nconst t = getCometChatTranslation();\n\nexport const Emojis = [\n  {\n    people: {\n      id: \"people\",\n      name: t(\"SMILEY_PEOPLE\"),\n      symbol: \"Smileys\",\n      emojis: {\n        grinning: {\n          keywords: [\"face\", \"smile\", \"happy\", \"joy\", \":D\", \"grin\"],\n          char: \"😀\",\n        },\n        grimacing: {\n          keywords: [\"face\", \"grimace\", \"teeth\"],\n          char: \"😬\",\n        },\n        grin: {\n          keywords: [\"face\", \"happy\", \"smile\", \"joy\", \"kawaii\"],\n          char: \"😁\",\n        },\n        joy: {\n          keywords: [\"face\", \"cry\", \"tears\", \"weep\", \"happy\", \"happytears\", \"haha\"],\n          char: \"😂\",\n        },\n        rofl: {\n          keywords: [\"face\", \"rolling\", \"floor\", \"laughing\", \"lol\", \"haha\"],\n          char: \"🤣\",\n        },\n        partying: {\n          keywords: [\"face\", \"celebration\", \"woohoo\"],\n          char: \"🥳\",\n        },\n        smiley: {\n          keywords: [\"face\", \"happy\", \"joy\", \"haha\", \":D\", \":)\", \"smile\", \"funny\"],\n          char: \"😃\",\n        },\n        smile: {\n          keywords: [\"face\", \"happy\", \"joy\", \"funny\", \"haha\", \"laugh\", \"like\", \":D\", \":)\"],\n          char: \"😄\",\n        },\n        sweat_smile: {\n          keywords: [\"face\", \"hot\", \"happy\", \"laugh\", \"sweat\", \"smile\", \"relief\"],\n          char: \"😅\",\n        },\n        laughing: {\n          keywords: [\"happy\", \"joy\", \"lol\", \"satisfied\", \"haha\", \"face\", \"glad\", \"XD\", \"laugh\"],\n          char: \"😆\",\n        },\n        innocent: {\n          keywords: [\"face\", \"angel\", \"heaven\", \"halo\"],\n          char: \"😇\",\n        },\n        wink: {\n          keywords: [\"face\", \"happy\", \"mischievous\", \"secret\", \";)\", \"smile\", \"eye\"],\n          char: \"😉\",\n        },\n        blush: {\n          keywords: [\"face\", \"smile\", \"happy\", \"flushed\", \"crush\", \"embarrassed\", \"shy\", \"joy\"],\n          char: \"😊\",\n        },\n        slightly_smiling_face: {\n          keywords: [\"face\", \"smile\"],\n          char: \"🙂\",\n        },\n        upside_down_face: {\n          keywords: [\"face\", \"flipped\", \"silly\", \"smile\"],\n          char: \"🙃\",\n        },\n        relaxed: {\n          keywords: [\"face\", \"blush\", \"massage\", \"happiness\"],\n          char: \"☺️\",\n        },\n        yum: {\n          keywords: [\n            \"happy\",\n            \"joy\",\n            \"tongue\",\n            \"smile\",\n            \"face\",\n            \"silly\",\n            \"yummy\",\n            \"nom\",\n            \"delicious\",\n            \"savouring\",\n          ],\n          char: \"😋\",\n        },\n        relieved: {\n          keywords: [\"face\", \"relaxed\", \"phew\", \"massage\", \"happiness\"],\n          char: \"😌\",\n        },\n        heart_eyes: {\n          keywords: [\n            \"face\",\n            \"love\",\n            \"like\",\n            \"affection\",\n            \"valentines\",\n            \"infatuation\",\n            \"crush\",\n            \"heart\",\n          ],\n          char: \"😍\",\n        },\n        smiling_face_with_three_hearts: {\n          keywords: [\n            \"face\",\n            \"love\",\n            \"like\",\n            \"affection\",\n            \"valentines\",\n            \"infatuation\",\n            \"crush\",\n            \"hearts\",\n            \"adore\",\n          ],\n          char: \"🥰\",\n        },\n        kissing_heart: {\n          keywords: [\"face\", \"love\", \"like\", \"affection\", \"valentines\", \"infatuation\", \"kiss\"],\n          char: \"😘\",\n        },\n        kissing: {\n          keywords: [\"love\", \"like\", \"face\", \"3\", \"valentines\", \"infatuation\", \"kiss\"],\n          char: \"😗\",\n        },\n        kissing_smiling_eyes: {\n          keywords: [\"face\", \"affection\", \"valentines\", \"infatuation\", \"kiss\"],\n          char: \"😙\",\n        },\n        kissing_closed_eyes: {\n          keywords: [\"face\", \"love\", \"like\", \"affection\", \"valentines\", \"infatuation\", \"kiss\"],\n          char: \"😚\",\n        },\n        stuck_out_tongue_winking_eye: {\n          keywords: [\n            \"face\",\n            \"prank\",\n            \"childish\",\n            \"playful\",\n            \"mischievous\",\n            \"smile\",\n            \"wink\",\n            \"tongue\",\n          ],\n          char: \"😜\",\n        },\n        zany: {\n          keywords: [\"face\", \"goofy\", \"crazy\"],\n          char: \"🤪\",\n        },\n        raised_eyebrow: {\n          keywords: [\"face\", \"distrust\", \"scepticism\", \"disapproval\", \"disbelief\", \"surprise\"],\n          char: \"🤨\",\n        },\n        monocle: {\n          keywords: [\"face\", \"stuffy\", \"wealthy\"],\n          char: \"🧐\",\n        },\n        stuck_out_tongue_closed_eyes: {\n          keywords: [\"face\", \"prank\", \"playful\", \"mischievous\", \"smile\", \"tongue\"],\n          char: \"😝\",\n        },\n        stuck_out_tongue: {\n          keywords: [\"face\", \"prank\", \"childish\", \"playful\", \"mischievous\", \"smile\", \"tongue\"],\n          char: \"😛\",\n        },\n        money_mouth_face: {\n          keywords: [\"face\", \"rich\", \"dollar\", \"money\"],\n          char: \"🤑\",\n        },\n        nerd_face: {\n          keywords: [\"face\", \"nerdy\", \"geek\", \"dork\"],\n          char: \"🤓\",\n        },\n        sunglasses: {\n          keywords: [\"face\", \"cool\", \"smile\", \"summer\", \"beach\", \"sunglass\"],\n          char: \"😎\",\n        },\n        star_struck: {\n          keywords: [\"face\", \"smile\", \"starry\", \"eyes\", \"grinning\"],\n          char: \"🤩\",\n        },\n        clown_face: {\n          keywords: [\"face\"],\n          char: \"🤡\",\n        },\n        cowboy_hat_face: {\n          keywords: [\"face\", \"cowgirl\", \"hat\"],\n          char: \"🤠\",\n        },\n        hugs: {\n          keywords: [\"face\", \"smile\", \"hug\"],\n          char: \"🤗\",\n        },\n        smirk: {\n          keywords: [\"face\", \"smile\", \"mean\", \"prank\", \"smug\", \"sarcasm\"],\n          char: \"😏\",\n        },\n        no_mouth: {\n          keywords: [\"face\", \"hellokitty\"],\n          char: \"😶\",\n        },\n        neutral_face: {\n          keywords: [\"indifference\", \"meh\", \":|\", \"neutral\"],\n          char: \"😐\",\n        },\n        expressionless: {\n          keywords: [\"face\", \"indifferent\", \"-_-\", \"meh\", \"deadpan\"],\n          char: \"😑\",\n        },\n        unamused: {\n          keywords: [\n            \"indifference\",\n            \"bored\",\n            \"straight face\",\n            \"serious\",\n            \"sarcasm\",\n            \"unimpressed\",\n            \"skeptical\",\n            \"dubious\",\n            \"side_eye\",\n          ],\n          char: \"😒\",\n        },\n        roll_eyes: {\n          keywords: [\"face\", \"eyeroll\", \"frustrated\"],\n          char: \"🙄\",\n        },\n        thinking: {\n          keywords: [\"face\", \"hmmm\", \"think\", \"consider\"],\n          char: \"🤔\",\n        },\n        lying_face: {\n          keywords: [\"face\", \"lie\", \"pinocchio\"],\n          char: \"🤥\",\n        },\n        hand_over_mouth: {\n          keywords: [\"face\", \"whoops\", \"shock\", \"surprise\"],\n          char: \"🤭\",\n        },\n        shushing: {\n          keywords: [\"face\", \"quiet\", \"shhh\"],\n          char: \"🤫\",\n        },\n        symbols_over_mouth: {\n          keywords: [\"face\", \"swearing\", \"cursing\", \"cussing\", \"profanity\", \"expletive\"],\n          char: \"🤬\",\n        },\n        exploding_head: {\n          keywords: [\"face\", \"shocked\", \"mind\", \"blown\"],\n          char: \"🤯\",\n        },\n        flushed: {\n          keywords: [\"face\", \"blush\", \"shy\", \"flattered\"],\n          char: \"😳\",\n        },\n        disappointed: {\n          keywords: [\"face\", \"sad\", \"upset\", \"depressed\", \":(\"],\n          char: \"😞\",\n        },\n        worried: {\n          keywords: [\"face\", \"concern\", \"nervous\", \":(\"],\n          char: \"😟\",\n        },\n        angry: {\n          keywords: [\"mad\", \"face\", \"annoyed\", \"frustrated\"],\n          char: \"😠\",\n        },\n        rage: {\n          keywords: [\"angry\", \"mad\", \"hate\", \"despise\"],\n          char: \"😡\",\n        },\n        pensive: {\n          keywords: [\"face\", \"sad\", \"depressed\", \"upset\"],\n          char: \"😔\",\n        },\n        confused: {\n          keywords: [\"face\", \"indifference\", \"huh\", \"weird\", \"hmmm\", \":/\"],\n          char: \"😕\",\n        },\n        slightly_frowning_face: {\n          keywords: [\"face\", \"frowning\", \"disappointed\", \"sad\", \"upset\"],\n          char: \"🙁\",\n        },\n        frowning_face: {\n          keywords: [\"face\", \"sad\", \"upset\", \"frown\"],\n          char: \"☹\",\n        },\n        persevere: {\n          keywords: [\"face\", \"sick\", \"no\", \"upset\", \"oops\"],\n          char: \"😣\",\n        },\n        confounded: {\n          keywords: [\"face\", \"confused\", \"sick\", \"unwell\", \"oops\", \":S\"],\n          char: \"😖\",\n        },\n        tired_face: {\n          keywords: [\"sick\", \"whine\", \"upset\", \"frustrated\"],\n          char: \"😫\",\n        },\n        weary: {\n          keywords: [\"face\", \"tired\", \"sleepy\", \"sad\", \"frustrated\", \"upset\"],\n          char: \"😩\",\n        },\n        pleading: {\n          keywords: [\"face\", \"begging\", \"mercy\"],\n          char: \"🥺\",\n        },\n        triumph: {\n          keywords: [\"face\", \"gas\", \"phew\", \"proud\", \"pride\"],\n          char: \"😤\",\n        },\n        open_mouth: {\n          keywords: [\"face\", \"surprise\", \"impressed\", \"wow\", \"whoa\", \":O\"],\n          char: \"😮\",\n        },\n        scream: {\n          keywords: [\"face\", \"munch\", \"scared\", \"omg\"],\n          char: \"😱\",\n        },\n        fearful: {\n          keywords: [\"face\", \"scared\", \"terrified\", \"nervous\", \"oops\", \"huh\"],\n          char: \"😨\",\n        },\n        cold_sweat: {\n          keywords: [\"face\", \"nervous\", \"sweat\"],\n          char: \"😰\",\n        },\n        hushed: {\n          keywords: [\"face\", \"woo\", \"shh\"],\n          char: \"😯\",\n        },\n        frowning: {\n          keywords: [\"face\", \"aw\", \"what\"],\n          char: \"😦\",\n        },\n        anguished: {\n          keywords: [\"face\", \"stunned\", \"nervous\"],\n          char: \"😧\",\n        },\n        cry: {\n          keywords: [\"face\", \"tears\", \"sad\", \"depressed\", \"upset\", \":'(\"],\n          char: \"😢\",\n        },\n        disappointed_relieved: {\n          keywords: [\"face\", \"phew\", \"sweat\", \"nervous\"],\n          char: \"😥\",\n        },\n        drooling_face: {\n          keywords: [\"face\"],\n          char: \"🤤\",\n        },\n        sleepy: {\n          keywords: [\"face\", \"tired\", \"rest\", \"nap\"],\n          char: \"😪\",\n        },\n        sweat: {\n          keywords: [\"face\", \"hot\", \"sad\", \"tired\", \"exercise\"],\n          char: \"😓\",\n        },\n        hot: {\n          keywords: [\"face\", \"feverish\", \"heat\", \"red\", \"sweating\"],\n          char: \"🥵\",\n        },\n        cold: {\n          keywords: [\"face\", \"blue\", \"freezing\", \"frozen\", \"frostbite\", \"icicles\"],\n          char: \"🥶\",\n        },\n        sob: {\n          keywords: [\"face\", \"cry\", \"tears\", \"sad\", \"upset\", \"depressed\"],\n          char: \"😭\",\n        },\n        dizzy_face: {\n          keywords: [\"spent\", \"unconscious\", \"xox\", \"dizzy\"],\n          char: \"😵\",\n        },\n        astonished: {\n          keywords: [\"face\", \"xox\", \"surprised\", \"poisoned\"],\n          char: \"😲\",\n        },\n        zipper_mouth_face: {\n          keywords: [\"face\", \"sealed\", \"zipper\", \"secret\"],\n          char: \"🤐\",\n        },\n        nauseated_face: {\n          keywords: [\"face\", \"vomit\", \"gross\", \"green\", \"sick\", \"throw up\", \"ill\"],\n          char: \"🤢\",\n        },\n        sneezing_face: {\n          keywords: [\"face\", \"gesundheit\", \"sneeze\", \"sick\", \"allergy\"],\n          char: \"🤧\",\n        },\n        vomiting: {\n          keywords: [\"face\", \"sick\"],\n          char: \"🤮\",\n        },\n        mask: {\n          keywords: [\"face\", \"sick\", \"ill\", \"disease\"],\n          char: \"😷\",\n        },\n        face_with_thermometer: {\n          keywords: [\"sick\", \"temperature\", \"thermometer\", \"cold\", \"fever\"],\n          char: \"🤒\",\n        },\n        face_with_head_bandage: {\n          keywords: [\"injured\", \"clumsy\", \"bandage\", \"hurt\"],\n          char: \"🤕\",\n        },\n        woozy: {\n          keywords: [\"face\", \"dizzy\", \"intoxicated\", \"tipsy\", \"wavy\"],\n          char: \"🥴\",\n        },\n        sleeping: {\n          keywords: [\"face\", \"tired\", \"sleepy\", \"night\", \"zzz\"],\n          char: \"😴\",\n        },\n        zzz: {\n          keywords: [\"sleepy\", \"tired\", \"dream\"],\n          char: \"💤\",\n        },\n        poop: {\n          keywords: [\"hankey\", \"shitface\", \"fail\", \"turd\", \"shit\"],\n          char: \"💩\",\n        },\n        smiling_imp: {\n          keywords: [\"devil\", \"horns\"],\n          char: \"😈\",\n        },\n        imp: {\n          keywords: [\"devil\", \"angry\", \"horns\"],\n          char: \"👿\",\n        },\n        japanese_ogre: {\n          keywords: [\n            \"monster\",\n            \"red\",\n            \"mask\",\n            \"halloween\",\n            \"scary\",\n            \"creepy\",\n            \"devil\",\n            \"demon\",\n            \"japanese\",\n            \"ogre\",\n          ],\n          char: \"👹\",\n        },\n        japanese_goblin: {\n          keywords: [\"red\", \"evil\", \"mask\", \"monster\", \"scary\", \"creepy\", \"japanese\", \"goblin\"],\n          char: \"👺\",\n        },\n        skull: {\n          keywords: [\"dead\", \"skeleton\", \"creepy\", \"death\"],\n          char: \"💀\",\n        },\n        ghost: {\n          keywords: [\"halloween\", \"spooky\", \"scary\"],\n          char: \"👻\",\n        },\n        alien: {\n          keywords: [\"UFO\", \"paul\", \"weird\", \"outer_space\"],\n          char: \"👽\",\n        },\n        robot: {\n          keywords: [\"computer\", \"machine\", \"bot\"],\n          char: \"🤖\",\n        },\n        smiley_cat: {\n          keywords: [\"animal\", \"cats\", \"happy\", \"smile\"],\n          char: \"😺\",\n        },\n        smile_cat: {\n          keywords: [\"animal\", \"cats\", \"smile\"],\n          char: \"😸\",\n        },\n        joy_cat: {\n          keywords: [\"animal\", \"cats\", \"haha\", \"happy\", \"tears\"],\n          char: \"😹\",\n        },\n        heart_eyes_cat: {\n          keywords: [\"animal\", \"love\", \"like\", \"affection\", \"cats\", \"valentines\", \"heart\"],\n          char: \"😻\",\n        },\n        smirk_cat: {\n          keywords: [\"animal\", \"cats\", \"smirk\"],\n          char: \"😼\",\n        },\n        kissing_cat: {\n          keywords: [\"animal\", \"cats\", \"kiss\"],\n          char: \"😽\",\n        },\n        scream_cat: {\n          keywords: [\"animal\", \"cats\", \"munch\", \"scared\", \"scream\"],\n          char: \"🙀\",\n        },\n        crying_cat_face: {\n          keywords: [\"animal\", \"tears\", \"weep\", \"sad\", \"cats\", \"upset\", \"cry\"],\n          char: \"😿\",\n        },\n        pouting_cat: {\n          keywords: [\"animal\", \"cats\"],\n          char: \"😾\",\n        },\n        palms_up: {\n          keywords: [\"hands\", \"gesture\", \"cupped\", \"prayer\"],\n          char: \"🤲\",\n          fitzpatrick_scale: true,\n        },\n        raised_hands: {\n          keywords: [\"gesture\", \"hooray\", \"yea\", \"celebration\", \"hands\"],\n          char: \"🙌\",\n          fitzpatrick_scale: true,\n        },\n        clap: {\n          keywords: [\"hands\", \"praise\", \"applause\", \"congrats\", \"yay\"],\n          char: \"👏\",\n          fitzpatrick_scale: true,\n        },\n        wave: {\n          keywords: [\"hands\", \"gesture\", \"goodbye\", \"solong\", \"farewell\", \"hello\", \"hi\", \"palm\"],\n          char: \"👋\",\n          fitzpatrick_scale: true,\n        },\n        call_me_hand: {\n          keywords: [\"hands\", \"gesture\"],\n          char: \"🤙\",\n          fitzpatrick_scale: true,\n        },\n        \"+1\": {\n          keywords: [\n            \"thumbsup\",\n            \"yes\",\n            \"awesome\",\n            \"good\",\n            \"agree\",\n            \"accept\",\n            \"cool\",\n            \"hand\",\n            \"like\",\n          ],\n          char: \"👍\",\n          fitzpatrick_scale: true,\n        },\n        \"-1\": {\n          keywords: [\"thumbsdown\", \"no\", \"dislike\", \"hand\"],\n          char: \"👎\",\n          fitzpatrick_scale: true,\n        },\n        facepunch: {\n          keywords: [\"angry\", \"violence\", \"fist\", \"hit\", \"attack\", \"hand\"],\n          char: \"👊\",\n          fitzpatrick_scale: true,\n        },\n        fist: {\n          keywords: [\"fingers\", \"hand\", \"grasp\"],\n          char: \"✊\",\n          fitzpatrick_scale: true,\n        },\n        fist_left: {\n          keywords: [\"hand\", \"fistbump\"],\n          char: \"🤛\",\n          fitzpatrick_scale: true,\n        },\n        fist_right: {\n          keywords: [\"hand\", \"fistbump\"],\n          char: \"🤜\",\n          fitzpatrick_scale: true,\n        },\n        v: {\n          keywords: [\"fingers\", \"ohyeah\", \"hand\", \"peace\", \"victory\", \"two\"],\n          char: \"✌\",\n          fitzpatrick_scale: true,\n        },\n        ok_hand: {\n          keywords: [\"fingers\", \"limbs\", \"perfect\", \"ok\", \"okay\"],\n          char: \"👌\",\n          fitzpatrick_scale: true,\n        },\n        raised_hand: {\n          keywords: [\"fingers\", \"stop\", \"highfive\", \"palm\", \"ban\"],\n          char: \"✋\",\n          fitzpatrick_scale: true,\n        },\n        raised_back_of_hand: {\n          keywords: [\"fingers\", \"raised\", \"backhand\"],\n          char: \"🤚\",\n          fitzpatrick_scale: true,\n        },\n        open_hands: {\n          keywords: [\"fingers\", \"butterfly\", \"hands\", \"open\"],\n          char: \"👐\",\n          fitzpatrick_scale: true,\n        },\n        muscle: {\n          keywords: [\"arm\", \"flex\", \"hand\", \"summer\", \"strong\", \"biceps\"],\n          char: \"💪\",\n          fitzpatrick_scale: true,\n        },\n        pray: {\n          keywords: [\"please\", \"hope\", \"wish\", \"namaste\", \"highfive\"],\n          char: \"🙏\",\n          fitzpatrick_scale: true,\n        },\n        foot: {\n          keywords: [\"kick\", \"stomp\"],\n          char: \"🦶\",\n          fitzpatrick_scale: true,\n        },\n        leg: {\n          keywords: [\"kick\", \"limb\"],\n          char: \"🦵\",\n          fitzpatrick_scale: true,\n        },\n        handshake: {\n          keywords: [\"agreement\", \"shake\"],\n          char: \"🤝\",\n        },\n        point_up: {\n          keywords: [\"hand\", \"fingers\", \"direction\", \"up\"],\n          char: \"☝\",\n          fitzpatrick_scale: true,\n        },\n        point_up_2: {\n          keywords: [\"fingers\", \"hand\", \"direction\", \"up\"],\n          char: \"👆\",\n          fitzpatrick_scale: true,\n        },\n        point_down: {\n          keywords: [\"fingers\", \"hand\", \"direction\", \"down\"],\n          char: \"👇\",\n          fitzpatrick_scale: true,\n        },\n        point_left: {\n          keywords: [\"direction\", \"fingers\", \"hand\", \"left\"],\n          char: \"👈\",\n          fitzpatrick_scale: true,\n        },\n        point_right: {\n          keywords: [\"fingers\", \"hand\", \"direction\", \"right\"],\n          char: \"👉\",\n          fitzpatrick_scale: true,\n        },\n        fu: {\n          keywords: [\"hand\", \"fingers\", \"rude\", \"middle\", \"flipping\"],\n          char: \"🖕\",\n          fitzpatrick_scale: true,\n        },\n        raised_hand_with_fingers_splayed: {\n          keywords: [\"hand\", \"fingers\", \"palm\"],\n          char: \"🖐\",\n          fitzpatrick_scale: true,\n        },\n        love_you: {\n          keywords: [\"hand\", \"fingers\", \"gesture\"],\n          char: \"🤟\",\n          fitzpatrick_scale: true,\n        },\n        metal: {\n          keywords: [\"hand\", \"fingers\", \"evil_eye\", \"sign_of_horns\", \"rock_on\"],\n          char: \"🤘\",\n          fitzpatrick_scale: true,\n        },\n        crossed_fingers: {\n          keywords: [\"good\", \"lucky\"],\n          char: \"🤞\",\n          fitzpatrick_scale: true,\n        },\n        vulcan_salute: {\n          keywords: [\"hand\", \"fingers\", \"spock\", \"star trek\"],\n          char: \"🖖\",\n          fitzpatrick_scale: true,\n        },\n        writing_hand: {\n          keywords: [\"lower_left_ballpoint_pen\", \"stationery\", \"write\", \"compose\"],\n          char: \"✍\",\n          fitzpatrick_scale: true,\n        },\n        selfie: {\n          keywords: [\"camera\", \"phone\"],\n          char: \"🤳\",\n          fitzpatrick_scale: true,\n        },\n        nail_care: {\n          keywords: [\"beauty\", \"manicure\", \"finger\", \"fashion\", \"nail\"],\n          char: \"💅\",\n          fitzpatrick_scale: true,\n        },\n        lips: {\n          keywords: [\"mouth\", \"kiss\"],\n          char: \"👄\",\n        },\n        tooth: {\n          keywords: [\"teeth\", \"dentist\"],\n          char: \"🦷\",\n        },\n        tongue: {\n          keywords: [\"mouth\", \"playful\"],\n          char: \"👅\",\n        },\n        ear: {\n          keywords: [\"face\", \"hear\", \"sound\", \"listen\"],\n          char: \"👂\",\n          fitzpatrick_scale: true,\n        },\n        nose: {\n          keywords: [\"smell\", \"sniff\"],\n          char: \"👃\",\n          fitzpatrick_scale: true,\n        },\n        eye: {\n          keywords: [\"face\", \"look\", \"see\", \"watch\", \"stare\"],\n          char: \"👁\",\n        },\n        eyes: {\n          keywords: [\"look\", \"watch\", \"stalk\", \"peek\", \"see\"],\n          char: \"👀\",\n        },\n        brain: {\n          keywords: [\"smart\", \"intelligent\"],\n          char: \"🧠\",\n        },\n        bust_in_silhouette: {\n          keywords: [\"user\", \"person\", \"human\"],\n          char: \"👤\",\n        },\n        busts_in_silhouette: {\n          keywords: [\"user\", \"person\", \"human\", \"group\", \"team\"],\n          char: \"👥\",\n        },\n        speaking_head: {\n          keywords: [\"user\", \"person\", \"human\", \"sing\", \"say\", \"talk\"],\n          char: \"🗣\",\n        },\n        baby: {\n          keywords: [\"child\", \"boy\", \"girl\", \"toddler\"],\n          char: \"👶\",\n          fitzpatrick_scale: true,\n        },\n        child: {\n          keywords: [\"gender-neutral\", \"young\"],\n          char: \"🧒\",\n          fitzpatrick_scale: true,\n        },\n        boy: {\n          keywords: [\"man\", \"male\", \"guy\", \"teenager\"],\n          char: \"👦\",\n          fitzpatrick_scale: true,\n        },\n        girl: {\n          keywords: [\"female\", \"woman\", \"teenager\"],\n          char: \"👧\",\n          fitzpatrick_scale: true,\n        },\n        adult: {\n          keywords: [\"gender-neutral\", \"person\"],\n          char: \"🧑\",\n          fitzpatrick_scale: true,\n        },\n        man: {\n          keywords: [\"mustache\", \"father\", \"dad\", \"guy\", \"classy\", \"sir\", \"moustache\"],\n          char: \"👨\",\n          fitzpatrick_scale: true,\n        },\n        woman: {\n          keywords: [\"female\", \"girls\", \"lady\"],\n          char: \"👩\",\n          fitzpatrick_scale: true,\n        },\n        blonde_woman: {\n          keywords: [\"woman\", \"female\", \"girl\", \"blonde\", \"person\"],\n          char: \"👱‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        blonde_man: {\n          keywords: [\"man\", \"male\", \"boy\", \"blonde\", \"guy\", \"person\"],\n          char: \"👱\",\n          fitzpatrick_scale: true,\n        },\n        bearded_person: {\n          keywords: [\"person\", \"bewhiskered\"],\n          char: \"🧔\",\n          fitzpatrick_scale: true,\n        },\n        older_adult: {\n          keywords: [\"human\", \"elder\", \"senior\", \"gender-neutral\"],\n          char: \"🧓\",\n          fitzpatrick_scale: true,\n        },\n        older_man: {\n          keywords: [\"human\", \"male\", \"men\", \"old\", \"elder\", \"senior\"],\n          char: \"👴\",\n          fitzpatrick_scale: true,\n        },\n        older_woman: {\n          keywords: [\"human\", \"female\", \"women\", \"lady\", \"old\", \"elder\", \"senior\"],\n          char: \"👵\",\n          fitzpatrick_scale: true,\n        },\n        man_with_gua_pi_mao: {\n          keywords: [\"male\", \"boy\", \"chinese\"],\n          char: \"👲\",\n          fitzpatrick_scale: true,\n        },\n        woman_with_headscarf: {\n          keywords: [\"female\", \"hijab\", \"mantilla\", \"tichel\"],\n          char: \"🧕\",\n          fitzpatrick_scale: true,\n        },\n        woman_with_turban: {\n          keywords: [\"female\", \"indian\", \"hinduism\", \"arabs\", \"woman\"],\n          char: \"👳‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_with_turban: {\n          keywords: [\"male\", \"indian\", \"hinduism\", \"arabs\"],\n          char: \"👳\",\n          fitzpatrick_scale: true,\n        },\n        policewoman: {\n          keywords: [\"woman\", \"police\", \"law\", \"legal\", \"enforcement\", \"arrest\", \"911\", \"female\"],\n          char: \"👮‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        policeman: {\n          keywords: [\"man\", \"police\", \"law\", \"legal\", \"enforcement\", \"arrest\", \"911\"],\n          char: \"👮\",\n          fitzpatrick_scale: true,\n        },\n        construction_worker_woman: {\n          keywords: [\"female\", \"human\", \"wip\", \"build\", \"construction\", \"worker\", \"labor\", \"woman\"],\n          char: \"👷‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        construction_worker_man: {\n          keywords: [\"male\", \"human\", \"wip\", \"guy\", \"build\", \"construction\", \"worker\", \"labor\"],\n          char: \"👷\",\n          fitzpatrick_scale: true,\n        },\n        guardswoman: {\n          keywords: [\"uk\", \"gb\", \"british\", \"female\", \"royal\", \"woman\"],\n          char: \"💂‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        guardsman: {\n          keywords: [\"uk\", \"gb\", \"british\", \"male\", \"guy\", \"royal\"],\n          char: \"💂\",\n          fitzpatrick_scale: true,\n        },\n        female_detective: {\n          keywords: [\"human\", \"spy\", \"detective\", \"female\", \"woman\"],\n          char: \"🕵️‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        male_detective: {\n          keywords: [\"human\", \"spy\", \"detective\"],\n          char: \"🕵\",\n          fitzpatrick_scale: true,\n        },\n        woman_health_worker: {\n          keywords: [\"doctor\", \"nurse\", \"therapist\", \"healthcare\", \"woman\", \"human\"],\n          char: \"👩‍⚕️\",\n          fitzpatrick_scale: true,\n        },\n        man_health_worker: {\n          keywords: [\"doctor\", \"nurse\", \"therapist\", \"healthcare\", \"man\", \"human\"],\n          char: \"👨‍⚕️\",\n          fitzpatrick_scale: true,\n        },\n        woman_farmer: {\n          keywords: [\"rancher\", \"gardener\", \"woman\", \"human\"],\n          char: \"👩‍🌾\",\n          fitzpatrick_scale: true,\n        },\n        man_farmer: {\n          keywords: [\"rancher\", \"gardener\", \"man\", \"human\"],\n          char: \"👨‍🌾\",\n          fitzpatrick_scale: true,\n        },\n        woman_cook: {\n          keywords: [\"chef\", \"woman\", \"human\"],\n          char: \"👩‍🍳\",\n          fitzpatrick_scale: true,\n        },\n        man_cook: {\n          keywords: [\"chef\", \"man\", \"human\"],\n          char: \"👨‍🍳\",\n          fitzpatrick_scale: true,\n        },\n        woman_student: {\n          keywords: [\"graduate\", \"woman\", \"human\"],\n          char: \"👩‍🎓\",\n          fitzpatrick_scale: true,\n        },\n        man_student: {\n          keywords: [\"graduate\", \"man\", \"human\"],\n          char: \"👨‍🎓\",\n          fitzpatrick_scale: true,\n        },\n        woman_singer: {\n          keywords: [\"rockstar\", \"entertainer\", \"woman\", \"human\"],\n          char: \"👩‍🎤\",\n          fitzpatrick_scale: true,\n        },\n        man_singer: {\n          keywords: [\"rockstar\", \"entertainer\", \"man\", \"human\"],\n          char: \"👨‍🎤\",\n          fitzpatrick_scale: true,\n        },\n        woman_teacher: {\n          keywords: [\"instructor\", \"professor\", \"woman\", \"human\"],\n          char: \"👩‍🏫\",\n          fitzpatrick_scale: true,\n        },\n        man_teacher: {\n          keywords: [\"instructor\", \"professor\", \"man\", \"human\"],\n          char: \"👨‍🏫\",\n          fitzpatrick_scale: true,\n        },\n        woman_factory_worker: {\n          keywords: [\"assembly\", \"industrial\", \"woman\", \"human\"],\n          char: \"👩‍🏭\",\n          fitzpatrick_scale: true,\n        },\n        man_factory_worker: {\n          keywords: [\"assembly\", \"industrial\", \"man\", \"human\"],\n          char: \"👨‍🏭\",\n          fitzpatrick_scale: true,\n        },\n        woman_technologist: {\n          keywords: [\n            \"coder\",\n            \"developer\",\n            \"engineer\",\n            \"programmer\",\n            \"software\",\n            \"woman\",\n            \"human\",\n            \"laptop\",\n            \"computer\",\n          ],\n          char: \"👩‍💻\",\n          fitzpatrick_scale: true,\n        },\n        man_technologist: {\n          keywords: [\n            \"coder\",\n            \"developer\",\n            \"engineer\",\n            \"programmer\",\n            \"software\",\n            \"man\",\n            \"human\",\n            \"laptop\",\n            \"computer\",\n          ],\n          char: \"👨‍💻\",\n          fitzpatrick_scale: true,\n        },\n        woman_office_worker: {\n          keywords: [\"business\", \"manager\", \"woman\", \"human\"],\n          char: \"👩‍💼\",\n          fitzpatrick_scale: true,\n        },\n        man_office_worker: {\n          keywords: [\"business\", \"manager\", \"man\", \"human\"],\n          char: \"👨‍💼\",\n          fitzpatrick_scale: true,\n        },\n        woman_mechanic: {\n          keywords: [\"plumber\", \"woman\", \"human\", \"wrench\"],\n          char: \"👩‍🔧\",\n          fitzpatrick_scale: true,\n        },\n        man_mechanic: {\n          keywords: [\"plumber\", \"man\", \"human\", \"wrench\"],\n          char: \"👨‍🔧\",\n          fitzpatrick_scale: true,\n        },\n        woman_scientist: {\n          keywords: [\"biologist\", \"chemist\", \"engineer\", \"physicist\", \"woman\", \"human\"],\n          char: \"👩‍🔬\",\n          fitzpatrick_scale: true,\n        },\n        man_scientist: {\n          keywords: [\"biologist\", \"chemist\", \"engineer\", \"physicist\", \"man\", \"human\"],\n          char: \"👨‍🔬\",\n          fitzpatrick_scale: true,\n        },\n        woman_artist: {\n          keywords: [\"painter\", \"woman\", \"human\"],\n          char: \"👩‍🎨\",\n          fitzpatrick_scale: true,\n        },\n        man_artist: {\n          keywords: [\"painter\", \"man\", \"human\"],\n          char: \"👨‍🎨\",\n          fitzpatrick_scale: true,\n        },\n        woman_firefighter: {\n          keywords: [\"fireman\", \"woman\", \"human\"],\n          char: \"👩‍🚒\",\n          fitzpatrick_scale: true,\n        },\n        man_firefighter: {\n          keywords: [\"fireman\", \"man\", \"human\"],\n          char: \"👨‍🚒\",\n          fitzpatrick_scale: true,\n        },\n        woman_pilot: {\n          keywords: [\"aviator\", \"plane\", \"woman\", \"human\"],\n          char: \"👩‍✈️\",\n          fitzpatrick_scale: true,\n        },\n        man_pilot: {\n          keywords: [\"aviator\", \"plane\", \"man\", \"human\"],\n          char: \"👨‍✈️\",\n          fitzpatrick_scale: true,\n        },\n        woman_astronaut: {\n          keywords: [\"space\", \"rocket\", \"woman\", \"human\"],\n          char: \"👩‍🚀\",\n          fitzpatrick_scale: true,\n        },\n        man_astronaut: {\n          keywords: [\"space\", \"rocket\", \"man\", \"human\"],\n          char: \"👨‍🚀\",\n          fitzpatrick_scale: true,\n        },\n        woman_judge: {\n          keywords: [\"justice\", \"court\", \"woman\", \"human\"],\n          char: \"👩‍⚖️\",\n          fitzpatrick_scale: true,\n        },\n        man_judge: {\n          keywords: [\"justice\", \"court\", \"man\", \"human\"],\n          char: \"👨‍⚖️\",\n          fitzpatrick_scale: true,\n        },\n        woman_superhero: {\n          keywords: [\"woman\", \"female\", \"good\", \"heroine\", \"superpowers\"],\n          char: \"🦸‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_superhero: {\n          keywords: [\"man\", \"male\", \"good\", \"hero\", \"superpowers\"],\n          char: \"🦸‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_supervillain: {\n          keywords: [\"woman\", \"female\", \"evil\", \"bad\", \"criminal\", \"heroine\", \"superpowers\"],\n          char: \"🦹‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_supervillain: {\n          keywords: [\"man\", \"male\", \"evil\", \"bad\", \"criminal\", \"hero\", \"superpowers\"],\n          char: \"🦹‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        mrs_claus: {\n          keywords: [\"woman\", \"female\", \"xmas\", \"mother christmas\"],\n          char: \"🤶\",\n          fitzpatrick_scale: true,\n        },\n        santa: {\n          keywords: [\"festival\", \"man\", \"male\", \"xmas\", \"father christmas\"],\n          char: \"🎅\",\n          fitzpatrick_scale: true,\n        },\n        sorceress: {\n          keywords: [\"woman\", \"female\", \"mage\", \"witch\"],\n          char: \"🧙‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        wizard: {\n          keywords: [\"man\", \"male\", \"mage\", \"sorcerer\"],\n          char: \"🧙‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_elf: {\n          keywords: [\"woman\", \"female\"],\n          char: \"🧝‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_elf: {\n          keywords: [\"man\", \"male\"],\n          char: \"🧝‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_vampire: {\n          keywords: [\"woman\", \"female\"],\n          char: \"🧛‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_vampire: {\n          keywords: [\"man\", \"male\", \"dracula\"],\n          char: \"🧛‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_zombie: {\n          keywords: [\"woman\", \"female\", \"undead\", \"walking dead\"],\n          char: \"🧟‍♀️\",\n        },\n        man_zombie: {\n          keywords: [\"man\", \"male\", \"dracula\", \"undead\", \"walking dead\"],\n          char: \"🧟‍♂️\",\n        },\n        woman_genie: {\n          keywords: [\"woman\", \"female\"],\n          char: \"🧞‍♀️\",\n        },\n        man_genie: {\n          keywords: [\"man\", \"male\"],\n          char: \"🧞‍♂️\",\n        },\n        mermaid: {\n          keywords: [\"woman\", \"female\", \"merwoman\", \"ariel\"],\n          char: \"🧜‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        merman: {\n          keywords: [\"man\", \"male\", \"triton\"],\n          char: \"🧜‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_fairy: {\n          keywords: [\"woman\", \"female\"],\n          char: \"🧚‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_fairy: {\n          keywords: [\"man\", \"male\"],\n          char: \"🧚‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        angel: {\n          keywords: [\"heaven\", \"wings\", \"halo\"],\n          char: \"👼\",\n          fitzpatrick_scale: true,\n        },\n        pregnant_woman: {\n          keywords: [\"baby\"],\n          char: \"🤰\",\n          fitzpatrick_scale: true,\n        },\n        breastfeeding: {\n          keywords: [\"nursing\", \"baby\"],\n          char: \"🤱\",\n          fitzpatrick_scale: true,\n        },\n        princess: {\n          keywords: [\"girl\", \"woman\", \"female\", \"blond\", \"crown\", \"royal\", \"queen\"],\n          char: \"👸\",\n          fitzpatrick_scale: true,\n        },\n        prince: {\n          keywords: [\"boy\", \"man\", \"male\", \"crown\", \"royal\", \"king\"],\n          char: \"🤴\",\n          fitzpatrick_scale: true,\n        },\n        bride_with_veil: {\n          keywords: [\"couple\", \"marriage\", \"wedding\", \"woman\", \"bride\"],\n          char: \"👰\",\n          fitzpatrick_scale: true,\n        },\n        man_in_tuxedo: {\n          keywords: [\"couple\", \"marriage\", \"wedding\", \"groom\"],\n          char: \"🤵\",\n          fitzpatrick_scale: true,\n        },\n        running_woman: {\n          keywords: [\"woman\", \"walking\", \"exercise\", \"race\", \"running\", \"female\"],\n          char: \"🏃‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        running_man: {\n          keywords: [\"man\", \"walking\", \"exercise\", \"race\", \"running\"],\n          char: \"🏃\",\n          fitzpatrick_scale: true,\n        },\n        walking_woman: {\n          keywords: [\"human\", \"feet\", \"steps\", \"woman\", \"female\"],\n          char: \"🚶‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        walking_man: {\n          keywords: [\"human\", \"feet\", \"steps\"],\n          char: \"🚶\",\n          fitzpatrick_scale: true,\n        },\n        dancer: {\n          keywords: [\"female\", \"girl\", \"woman\", \"fun\"],\n          char: \"💃\",\n          fitzpatrick_scale: true,\n        },\n        man_dancing: {\n          keywords: [\"male\", \"boy\", \"fun\", \"dancer\"],\n          char: \"🕺\",\n          fitzpatrick_scale: true,\n        },\n        dancing_women: {\n          keywords: [\"female\", \"bunny\", \"women\", \"girls\"],\n          char: \"👯\",\n        },\n        dancing_men: {\n          keywords: [\"male\", \"bunny\", \"men\", \"boys\"],\n          char: \"👯‍♂️\",\n        },\n        couple: {\n          keywords: [\n            \"pair\",\n            \"people\",\n            \"human\",\n            \"love\",\n            \"date\",\n            \"dating\",\n            \"like\",\n            \"affection\",\n            \"valentines\",\n            \"marriage\",\n          ],\n          char: \"👫\",\n        },\n        two_men_holding_hands: {\n          keywords: [\"pair\", \"couple\", \"love\", \"like\", \"bromance\", \"friendship\", \"people\", \"human\"],\n          char: \"👬\",\n        },\n        two_women_holding_hands: {\n          keywords: [\"pair\", \"friendship\", \"couple\", \"love\", \"like\", \"female\", \"people\", \"human\"],\n          char: \"👭\",\n        },\n        bowing_woman: {\n          keywords: [\"woman\", \"female\", \"girl\"],\n          char: \"🙇‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        bowing_man: {\n          keywords: [\"man\", \"male\", \"boy\"],\n          char: \"🙇\",\n          fitzpatrick_scale: true,\n        },\n        man_facepalming: {\n          keywords: [\"man\", \"male\", \"boy\", \"disbelief\"],\n          char: \"🤦‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_facepalming: {\n          keywords: [\"woman\", \"female\", \"girl\", \"disbelief\"],\n          char: \"🤦‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        woman_shrugging: {\n          keywords: [\"woman\", \"female\", \"girl\", \"confused\", \"indifferent\", \"doubt\"],\n          char: \"🤷\",\n          fitzpatrick_scale: true,\n        },\n        man_shrugging: {\n          keywords: [\"man\", \"male\", \"boy\", \"confused\", \"indifferent\", \"doubt\"],\n          char: \"🤷‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        tipping_hand_woman: {\n          keywords: [\"female\", \"girl\", \"woman\", \"human\", \"information\"],\n          char: \"💁\",\n          fitzpatrick_scale: true,\n        },\n        tipping_hand_man: {\n          keywords: [\"male\", \"boy\", \"man\", \"human\", \"information\"],\n          char: \"💁‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        no_good_woman: {\n          keywords: [\"female\", \"girl\", \"woman\", \"nope\"],\n          char: \"🙅\",\n          fitzpatrick_scale: true,\n        },\n        no_good_man: {\n          keywords: [\"male\", \"boy\", \"man\", \"nope\"],\n          char: \"🙅‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        ok_woman: {\n          keywords: [\"women\", \"girl\", \"female\", \"pink\", \"human\", \"woman\"],\n          char: \"🙆\",\n          fitzpatrick_scale: true,\n        },\n        ok_man: {\n          keywords: [\"men\", \"boy\", \"male\", \"blue\", \"human\", \"man\"],\n          char: \"🙆‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        raising_hand_woman: {\n          keywords: [\"female\", \"girl\", \"woman\"],\n          char: \"🙋\",\n          fitzpatrick_scale: true,\n        },\n        raising_hand_man: {\n          keywords: [\"male\", \"boy\", \"man\"],\n          char: \"🙋‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        pouting_woman: {\n          keywords: [\"female\", \"girl\", \"woman\"],\n          char: \"🙎\",\n          fitzpatrick_scale: true,\n        },\n        pouting_man: {\n          keywords: [\"male\", \"boy\", \"man\"],\n          char: \"🙎‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        frowning_woman: {\n          keywords: [\"female\", \"girl\", \"woman\", \"sad\", \"depressed\", \"discouraged\", \"unhappy\"],\n          char: \"🙍\",\n          fitzpatrick_scale: true,\n        },\n        frowning_man: {\n          keywords: [\"male\", \"boy\", \"man\", \"sad\", \"depressed\", \"discouraged\", \"unhappy\"],\n          char: \"🙍‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        haircut_woman: {\n          keywords: [\"female\", \"girl\", \"woman\"],\n          char: \"💇\",\n          fitzpatrick_scale: true,\n        },\n        haircut_man: {\n          keywords: [\"male\", \"boy\", \"man\"],\n          char: \"💇‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        massage_woman: {\n          keywords: [\"female\", \"girl\", \"woman\", \"head\"],\n          char: \"💆\",\n          fitzpatrick_scale: true,\n        },\n        massage_man: {\n          keywords: [\"male\", \"boy\", \"man\", \"head\"],\n          char: \"💆‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_in_steamy_room: {\n          keywords: [\"female\", \"woman\", \"spa\", \"steamroom\", \"sauna\"],\n          char: \"🧖‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_in_steamy_room: {\n          keywords: [\"male\", \"man\", \"spa\", \"steamroom\", \"sauna\"],\n          char: \"🧖‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        couple_with_heart_woman_man: {\n          keywords: [\n            \"pair\",\n            \"love\",\n            \"like\",\n            \"affection\",\n            \"human\",\n            \"dating\",\n            \"valentines\",\n            \"marriage\",\n          ],\n          char: \"💑\",\n        },\n        couple_with_heart_woman_woman: {\n          keywords: [\n            \"pair\",\n            \"love\",\n            \"like\",\n            \"affection\",\n            \"human\",\n            \"dating\",\n            \"valentines\",\n            \"marriage\",\n          ],\n          char: \"👩‍❤️‍👩\",\n        },\n        couple_with_heart_man_man: {\n          keywords: [\n            \"pair\",\n            \"love\",\n            \"like\",\n            \"affection\",\n            \"human\",\n            \"dating\",\n            \"valentines\",\n            \"marriage\",\n          ],\n          char: \"👨‍❤️‍👨\",\n        },\n        couplekiss_man_woman: {\n          keywords: [\"pair\", \"valentines\", \"love\", \"like\", \"dating\", \"marriage\"],\n          char: \"💏\",\n        },\n        couplekiss_woman_woman: {\n          keywords: [\"pair\", \"valentines\", \"love\", \"like\", \"dating\", \"marriage\"],\n          char: \"👩‍❤️‍💋‍👩\",\n        },\n        couplekiss_man_man: {\n          keywords: [\"pair\", \"valentines\", \"love\", \"like\", \"dating\", \"marriage\"],\n          char: \"👨‍❤️‍💋‍👨\",\n        },\n        family_man_woman_boy: {\n          keywords: [\n            \"home\",\n            \"parents\",\n            \"child\",\n            \"mom\",\n            \"dad\",\n            \"father\",\n            \"mother\",\n            \"people\",\n            \"human\",\n          ],\n          char: \"👪\",\n        },\n        family_man_woman_girl: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"child\"],\n          char: \"👨‍👩‍👧\",\n        },\n        family_man_woman_girl_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👩‍👧‍👦\",\n        },\n        family_man_woman_boy_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👩‍👦‍👦\",\n        },\n        family_man_woman_girl_girl: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👩‍👧‍👧\",\n        },\n        family_woman_woman_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👩‍👦\",\n        },\n        family_woman_woman_girl: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👩‍👧\",\n        },\n        family_woman_woman_girl_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👩‍👧‍👦\",\n        },\n        family_woman_woman_boy_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👩‍👦‍👦\",\n        },\n        family_woman_woman_girl_girl: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👩‍👧‍👧\",\n        },\n        family_man_man_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👨‍👦\",\n        },\n        family_man_man_girl: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👨‍👧\",\n        },\n        family_man_man_girl_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👨‍👧‍👦\",\n        },\n        family_man_man_boy_boy: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👨‍👦‍👦\",\n        },\n        family_man_man_girl_girl: {\n          keywords: [\"home\", \"parents\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👨‍👧‍👧\",\n        },\n        family_woman_boy: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"child\"],\n          char: \"👩‍👦\",\n        },\n        family_woman_girl: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"child\"],\n          char: \"👩‍👧\",\n        },\n        family_woman_girl_boy: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👧‍👦\",\n        },\n        family_woman_boy_boy: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👦‍👦\",\n        },\n        family_woman_girl_girl: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"children\"],\n          char: \"👩‍👧‍👧\",\n        },\n        family_man_boy: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"child\"],\n          char: \"👨‍👦\",\n        },\n        family_man_girl: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"child\"],\n          char: \"👨‍👧\",\n        },\n        family_man_girl_boy: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👧‍👦\",\n        },\n        family_man_boy_boy: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👦‍👦\",\n        },\n        family_man_girl_girl: {\n          keywords: [\"home\", \"parent\", \"people\", \"human\", \"children\"],\n          char: \"👨‍👧‍👧\",\n        },\n        yarn: {\n          keywords: [\"ball\", \"crochet\", \"knit\"],\n          char: \"🧶\",\n        },\n        thread: {\n          keywords: [\"needle\", \"sewing\", \"spool\", \"string\"],\n          char: \"🧵\",\n        },\n        coat: {\n          keywords: [\"jacket\"],\n          char: \"🧥\",\n        },\n        labcoat: {\n          keywords: [\"doctor\", \"experiment\", \"scientist\", \"chemist\"],\n          char: \"🥼\",\n        },\n        womans_clothes: {\n          keywords: [\"fashion\", \"shopping_bags\", \"female\"],\n          char: \"👚\",\n        },\n        tshirt: {\n          keywords: [\"fashion\", \"cloth\", \"casual\", \"shirt\", \"tee\"],\n          char: \"👕\",\n        },\n        jeans: {\n          keywords: [\"fashion\", \"shopping\"],\n          char: \"👖\",\n        },\n        necktie: {\n          keywords: [\"shirt\", \"suitup\", \"formal\", \"fashion\", \"cloth\", \"business\"],\n          char: \"👔\",\n        },\n        dress: {\n          keywords: [\"clothes\", \"fashion\", \"shopping\"],\n          char: \"👗\",\n        },\n        bikini: {\n          keywords: [\"swimming\", \"female\", \"woman\", \"girl\", \"fashion\", \"beach\", \"summer\"],\n          char: \"👙\",\n        },\n        kimono: {\n          keywords: [\"dress\", \"fashion\", \"women\", \"female\", \"japanese\"],\n          char: \"👘\",\n        },\n        lipstick: {\n          keywords: [\"female\", \"girl\", \"fashion\", \"woman\"],\n          char: \"💄\",\n        },\n        kiss: {\n          keywords: [\"face\", \"lips\", \"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💋\",\n        },\n        footprints: {\n          keywords: [\"feet\", \"tracking\", \"walking\", \"beach\"],\n          char: \"👣\",\n        },\n        flat_shoe: {\n          keywords: [\"ballet\", \"slip-on\", \"slipper\"],\n          char: \"🥿\",\n        },\n        high_heel: {\n          keywords: [\"fashion\", \"shoes\", \"female\", \"pumps\", \"stiletto\"],\n          char: \"👠\",\n        },\n        sandal: {\n          keywords: [\"shoes\", \"fashion\", \"flip flops\"],\n          char: \"👡\",\n        },\n        boot: {\n          keywords: [\"shoes\", \"fashion\"],\n          char: \"👢\",\n        },\n        mans_shoe: {\n          keywords: [\"fashion\", \"male\"],\n          char: \"👞\",\n        },\n        athletic_shoe: {\n          keywords: [\"shoes\", \"sports\", \"sneakers\"],\n          char: \"👟\",\n        },\n        hiking_boot: {\n          keywords: [\"backpacking\", \"camping\", \"hiking\"],\n          char: \"🥾\",\n        },\n        socks: {\n          keywords: [\"stockings\", \"clothes\"],\n          char: \"🧦\",\n        },\n        gloves: {\n          keywords: [\"hands\", \"winter\", \"clothes\"],\n          char: \"🧤\",\n        },\n        scarf: {\n          keywords: [\"neck\", \"winter\", \"clothes\"],\n          char: \"🧣\",\n        },\n        womans_hat: {\n          keywords: [\"fashion\", \"accessories\", \"female\", \"lady\", \"spring\"],\n          char: \"👒\",\n        },\n        tophat: {\n          keywords: [\"magic\", \"gentleman\", \"classy\", \"circus\"],\n          char: \"🎩\",\n        },\n        billed_hat: {\n          keywords: [\"cap\", \"baseball\"],\n          char: \"🧢\",\n        },\n        rescue_worker_helmet: {\n          keywords: [\"construction\", \"build\"],\n          char: \"⛑\",\n        },\n        mortar_board: {\n          keywords: [\n            \"school\",\n            \"college\",\n            \"degree\",\n            \"university\",\n            \"graduation\",\n            \"cap\",\n            \"hat\",\n            \"legal\",\n            \"learn\",\n            \"education\",\n          ],\n          char: \"🎓\",\n        },\n        crown: {\n          keywords: [\"king\", \"kod\", \"leader\", \"royalty\", \"lord\"],\n          char: \"👑\",\n        },\n        school_satchel: {\n          keywords: [\"student\", \"education\", \"bag\", \"backpack\"],\n          char: \"🎒\",\n        },\n        luggage: {\n          keywords: [\"packing\", \"travel\"],\n          char: \"🧳\",\n        },\n        pouch: {\n          keywords: [\"bag\", \"accessories\", \"shopping\"],\n          char: \"👝\",\n        },\n        purse: {\n          keywords: [\"fashion\", \"accessories\", \"money\", \"sales\", \"shopping\"],\n          char: \"👛\",\n        },\n        handbag: {\n          keywords: [\"fashion\", \"accessory\", \"accessories\", \"shopping\"],\n          char: \"👜\",\n        },\n        briefcase: {\n          keywords: [\"business\", \"documents\", \"work\", \"law\", \"legal\", \"job\", \"career\"],\n          char: \"💼\",\n        },\n        eyeglasses: {\n          keywords: [\"fashion\", \"accessories\", \"eyesight\", \"nerdy\", \"dork\", \"geek\"],\n          char: \"👓\",\n        },\n        dark_sunglasses: {\n          keywords: [\"face\", \"cool\", \"accessories\"],\n          char: \"🕶\",\n        },\n        goggles: {\n          keywords: [\"eyes\", \"protection\", \"safety\"],\n          char: \"🥽\",\n        },\n        ring: {\n          keywords: [\n            \"wedding\",\n            \"propose\",\n            \"marriage\",\n            \"valentines\",\n            \"diamond\",\n            \"fashion\",\n            \"jewelry\",\n            \"gem\",\n            \"engagement\",\n          ],\n          char: \"💍\",\n        },\n        closed_umbrella: {\n          keywords: [\"weather\", \"rain\", \"drizzle\"],\n          char: \"🌂\",\n        },\n      },\n    },\n  },\n\n  {\n    animals: {\n      id: \"animals_and_nature\",\n      name: t(\"ANIMALES_NATURE\"),\n      symbol: \"Animals\",\n      emojis: {\n        dog: {\n          keywords: [\"animal\", \"friend\", \"nature\", \"woof\", \"puppy\", \"pet\", \"faithful\"],\n          char: \"🐶\",\n        },\n        cat: {\n          keywords: [\"animal\", \"meow\", \"nature\", \"pet\", \"kitten\"],\n          char: \"🐱\",\n        },\n        mouse: {\n          keywords: [\"animal\", \"nature\", \"cheese_wedge\", \"rodent\"],\n          char: \"🐭\",\n        },\n        hamster: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐹\",\n        },\n        rabbit: {\n          keywords: [\"animal\", \"nature\", \"pet\", \"spring\", \"magic\", \"bunny\"],\n          char: \"🐰\",\n        },\n        fox_face: {\n          keywords: [\"animal\", \"nature\", \"face\"],\n          char: \"🦊\",\n        },\n        bear: {\n          keywords: [\"animal\", \"nature\", \"wild\"],\n          char: \"🐻\",\n        },\n        panda_face: {\n          keywords: [\"animal\", \"nature\", \"panda\"],\n          char: \"🐼\",\n        },\n        koala: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐨\",\n        },\n        tiger: {\n          keywords: [\"animal\", \"cat\", \"danger\", \"wild\", \"nature\", \"roar\"],\n          char: \"🐯\",\n        },\n        lion: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🦁\",\n        },\n        cow: {\n          keywords: [\"beef\", \"ox\", \"animal\", \"nature\", \"moo\", \"milk\"],\n          char: \"🐮\",\n        },\n        pig: {\n          keywords: [\"animal\", \"oink\", \"nature\"],\n          char: \"🐷\",\n        },\n        pig_nose: {\n          keywords: [\"animal\", \"oink\"],\n          char: \"🐽\",\n        },\n        frog: {\n          keywords: [\"animal\", \"nature\", \"croak\", \"toad\"],\n          char: \"🐸\",\n        },\n        squid: {\n          keywords: [\"animal\", \"nature\", \"ocean\", \"sea\"],\n          char: \"🦑\",\n        },\n        octopus: {\n          keywords: [\"animal\", \"creature\", \"ocean\", \"sea\", \"nature\", \"beach\"],\n          char: \"🐙\",\n        },\n        shrimp: {\n          keywords: [\"animal\", \"ocean\", \"nature\", \"seafood\"],\n          char: \"🦐\",\n        },\n        monkey_face: {\n          keywords: [\"animal\", \"nature\", \"circus\"],\n          char: \"🐵\",\n        },\n        gorilla: {\n          keywords: [\"animal\", \"nature\", \"circus\"],\n          char: \"🦍\",\n        },\n        see_no_evil: {\n          keywords: [\"monkey\", \"animal\", \"nature\", \"haha\"],\n          char: \"🙈\",\n        },\n        hear_no_evil: {\n          keywords: [\"animal\", \"monkey\", \"nature\"],\n          char: \"🙉\",\n        },\n        speak_no_evil: {\n          keywords: [\"monkey\", \"animal\", \"nature\", \"omg\"],\n          char: \"🙊\",\n        },\n        monkey: {\n          keywords: [\"animal\", \"nature\", \"banana\", \"circus\"],\n          char: \"🐒\",\n        },\n        chicken: {\n          keywords: [\"animal\", \"cluck\", \"nature\", \"bird\"],\n          char: \"🐔\",\n        },\n        penguin: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐧\",\n        },\n        bird: {\n          keywords: [\"animal\", \"nature\", \"fly\", \"tweet\", \"spring\"],\n          char: \"🐦\",\n        },\n        baby_chick: {\n          keywords: [\"animal\", \"chicken\", \"bird\"],\n          char: \"🐤\",\n        },\n        hatching_chick: {\n          keywords: [\"animal\", \"chicken\", \"egg\", \"born\", \"baby\", \"bird\"],\n          char: \"🐣\",\n        },\n        hatched_chick: {\n          keywords: [\"animal\", \"chicken\", \"baby\", \"bird\"],\n          char: \"🐥\",\n        },\n        duck: {\n          keywords: [\"animal\", \"nature\", \"bird\", \"mallard\"],\n          char: \"🦆\",\n        },\n        eagle: {\n          keywords: [\"animal\", \"nature\", \"bird\"],\n          char: \"🦅\",\n        },\n        owl: {\n          keywords: [\"animal\", \"nature\", \"bird\", \"hoot\"],\n          char: \"🦉\",\n        },\n        bat: {\n          keywords: [\"animal\", \"nature\", \"blind\", \"vampire\"],\n          char: \"🦇\",\n        },\n        wolf: {\n          keywords: [\"animal\", \"nature\", \"wild\"],\n          char: \"🐺\",\n        },\n        boar: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐗\",\n        },\n        horse: {\n          keywords: [\"animal\", \"brown\", \"nature\"],\n          char: \"🐴\",\n        },\n        unicorn: {\n          keywords: [\"animal\", \"nature\", \"mystical\"],\n          char: \"🦄\",\n        },\n        honeybee: {\n          keywords: [\"animal\", \"insect\", \"nature\", \"bug\", \"spring\", \"honey\"],\n          char: \"🐝\",\n        },\n        bug: {\n          keywords: [\"animal\", \"insect\", \"nature\", \"worm\"],\n          char: \"🐛\",\n        },\n        butterfly: {\n          keywords: [\"animal\", \"insect\", \"nature\", \"caterpillar\"],\n          char: \"🦋\",\n        },\n        snail: {\n          keywords: [\"slow\", \"animal\", \"shell\"],\n          char: \"🐌\",\n        },\n        beetle: {\n          keywords: [\"animal\", \"insect\", \"nature\", \"ladybug\"],\n          char: \"🐞\",\n        },\n        ant: {\n          keywords: [\"animal\", \"insect\", \"nature\", \"bug\"],\n          char: \"🐜\",\n        },\n        grasshopper: {\n          keywords: [\"animal\", \"cricket\", \"chirp\"],\n          char: \"🦗\",\n        },\n        spider: {\n          keywords: [\"animal\", \"arachnid\"],\n          char: \"🕷\",\n        },\n        scorpion: {\n          keywords: [\"animal\", \"arachnid\"],\n          char: \"🦂\",\n        },\n        crab: {\n          keywords: [\"animal\", \"crustacean\"],\n          char: \"🦀\",\n        },\n        snake: {\n          keywords: [\"animal\", \"evil\", \"nature\", \"hiss\", \"python\"],\n          char: \"🐍\",\n        },\n        lizard: {\n          keywords: [\"animal\", \"nature\", \"reptile\"],\n          char: \"🦎\",\n        },\n        \"t-rex\": {\n          keywords: [\"animal\", \"nature\", \"dinosaur\", \"tyrannosaurus\", \"extinct\"],\n          char: \"🦖\",\n        },\n        sauropod: {\n          keywords: [\n            \"animal\",\n            \"nature\",\n            \"dinosaur\",\n            \"brachiosaurus\",\n            \"brontosaurus\",\n            \"diplodocus\",\n            \"extinct\",\n          ],\n          char: \"🦕\",\n        },\n        turtle: {\n          keywords: [\"animal\", \"slow\", \"nature\", \"tortoise\"],\n          char: \"🐢\",\n        },\n        tropical_fish: {\n          keywords: [\"animal\", \"swim\", \"ocean\", \"beach\", \"nemo\"],\n          char: \"🐠\",\n        },\n        fish: {\n          keywords: [\"animal\", \"food\", \"nature\"],\n          char: \"🐟\",\n        },\n        blowfish: {\n          keywords: [\"animal\", \"nature\", \"food\", \"sea\", \"ocean\"],\n          char: \"🐡\",\n        },\n        dolphin: {\n          keywords: [\"animal\", \"nature\", \"fish\", \"sea\", \"ocean\", \"flipper\", \"fins\", \"beach\"],\n          char: \"🐬\",\n        },\n        shark: {\n          keywords: [\"animal\", \"nature\", \"fish\", \"sea\", \"ocean\", \"jaws\", \"fins\", \"beach\"],\n          char: \"🦈\",\n        },\n        whale: {\n          keywords: [\"animal\", \"nature\", \"sea\", \"ocean\"],\n          char: \"🐳\",\n        },\n        whale2: {\n          keywords: [\"animal\", \"nature\", \"sea\", \"ocean\"],\n          char: \"🐋\",\n        },\n        crocodile: {\n          keywords: [\"animal\", \"nature\", \"reptile\", \"lizard\", \"alligator\"],\n          char: \"🐊\",\n        },\n        leopard: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐆\",\n        },\n        zebra: {\n          keywords: [\"animal\", \"nature\", \"stripes\", \"safari\"],\n          char: \"🦓\",\n        },\n        tiger2: {\n          keywords: [\"animal\", \"nature\", \"roar\"],\n          char: \"🐅\",\n        },\n        water_buffalo: {\n          keywords: [\"animal\", \"nature\", \"ox\", \"cow\"],\n          char: \"🐃\",\n        },\n        ox: {\n          keywords: [\"animal\", \"cow\", \"beef\"],\n          char: \"🐂\",\n        },\n        cow2: {\n          keywords: [\"beef\", \"ox\", \"animal\", \"nature\", \"moo\", \"milk\"],\n          char: \"🐄\",\n        },\n        deer: {\n          keywords: [\"animal\", \"nature\", \"horns\", \"venison\"],\n          char: \"🦌\",\n        },\n        dromedary_camel: {\n          keywords: [\"animal\", \"hot\", \"desert\", \"hump\"],\n          char: \"🐪\",\n        },\n        camel: {\n          keywords: [\"animal\", \"nature\", \"hot\", \"desert\", \"hump\"],\n          char: \"🐫\",\n        },\n        giraffe: {\n          keywords: [\"animal\", \"nature\", \"spots\", \"safari\"],\n          char: \"🦒\",\n        },\n        elephant: {\n          keywords: [\"animal\", \"nature\", \"nose\", \"th\", \"circus\"],\n          char: \"🐘\",\n        },\n        rhinoceros: {\n          keywords: [\"animal\", \"nature\", \"horn\"],\n          char: \"🦏\",\n        },\n        goat: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐐\",\n        },\n        ram: {\n          keywords: [\"animal\", \"sheep\", \"nature\"],\n          char: \"🐏\",\n        },\n        sheep: {\n          keywords: [\"animal\", \"nature\", \"wool\", \"shipit\"],\n          char: \"🐑\",\n        },\n        racehorse: {\n          keywords: [\"animal\", \"gamble\", \"luck\"],\n          char: \"🐎\",\n        },\n        pig2: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🐖\",\n        },\n        rat: {\n          keywords: [\"animal\", \"mouse\", \"rodent\"],\n          char: \"🐀\",\n        },\n        mouse2: {\n          keywords: [\"animal\", \"nature\", \"rodent\"],\n          char: \"🐁\",\n        },\n        rooster: {\n          keywords: [\"animal\", \"nature\", \"chicken\"],\n          char: \"🐓\",\n        },\n        turkey: {\n          keywords: [\"animal\", \"bird\"],\n          char: \"🦃\",\n        },\n        dove: {\n          keywords: [\"animal\", \"bird\"],\n          char: \"🕊\",\n        },\n        dog2: {\n          keywords: [\"animal\", \"nature\", \"friend\", \"doge\", \"pet\", \"faithful\"],\n          char: \"🐕\",\n        },\n        poodle: {\n          keywords: [\"dog\", \"animal\", \"101\", \"nature\", \"pet\"],\n          char: \"🐩\",\n        },\n        cat2: {\n          keywords: [\"animal\", \"meow\", \"pet\", \"cats\"],\n          char: \"🐈\",\n        },\n        rabbit2: {\n          keywords: [\"animal\", \"nature\", \"pet\", \"magic\", \"spring\"],\n          char: \"🐇\",\n        },\n        chipmunk: {\n          keywords: [\"animal\", \"nature\", \"rodent\", \"squirrel\"],\n          char: \"🐿\",\n        },\n        hedgehog: {\n          keywords: [\"animal\", \"nature\", \"spiny\"],\n          char: \"🦔\",\n        },\n        raccoon: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🦝\",\n        },\n        llama: {\n          keywords: [\"animal\", \"nature\", \"alpaca\"],\n          char: \"🦙\",\n        },\n        hippopotamus: {\n          keywords: [\"animal\", \"nature\"],\n          char: \"🦛\",\n        },\n        kangaroo: {\n          keywords: [\"animal\", \"nature\", \"australia\", \"joey\", \"hop\", \"marsupial\"],\n          char: \"🦘\",\n        },\n        badger: {\n          keywords: [\"animal\", \"nature\", \"honey\"],\n          char: \"🦡\",\n        },\n        swan: {\n          keywords: [\"animal\", \"nature\", \"bird\"],\n          char: \"🦢\",\n        },\n        peacock: {\n          keywords: [\"animal\", \"nature\", \"peahen\", \"bird\"],\n          char: \"🦚\",\n        },\n        parrot: {\n          keywords: [\"animal\", \"nature\", \"bird\", \"pirate\", \"talk\"],\n          char: \"🦜\",\n        },\n        lobster: {\n          keywords: [\"animal\", \"nature\", \"bisque\", \"claws\", \"seafood\"],\n          char: \"🦞\",\n        },\n        mosquito: {\n          keywords: [\"animal\", \"nature\", \"insect\", \"malaria\"],\n          char: \"🦟\",\n        },\n        paw_prints: {\n          keywords: [\"animal\", \"tracking\", \"footprints\", \"dog\", \"cat\", \"pet\", \"feet\"],\n          char: \"🐾\",\n        },\n        dragon: {\n          keywords: [\"animal\", \"myth\", \"nature\", \"chinese\", \"green\"],\n          char: \"🐉\",\n        },\n        dragon_face: {\n          keywords: [\"animal\", \"myth\", \"nature\", \"chinese\", \"green\"],\n          char: \"🐲\",\n        },\n        cactus: {\n          keywords: [\"vegetable\", \"plant\", \"nature\"],\n          char: \"🌵\",\n        },\n        christmas_tree: {\n          keywords: [\"festival\", \"vacation\", \"december\", \"xmas\", \"celebration\"],\n          char: \"🎄\",\n        },\n        evergreen_tree: {\n          keywords: [\"plant\", \"nature\"],\n          char: \"🌲\",\n        },\n        deciduous_tree: {\n          keywords: [\"plant\", \"nature\"],\n          char: \"🌳\",\n        },\n        palm_tree: {\n          keywords: [\"plant\", \"vegetable\", \"nature\", \"summer\", \"beach\", \"mojito\", \"tropical\"],\n          char: \"🌴\",\n        },\n        seedling: {\n          keywords: [\"plant\", \"nature\", \"grass\", \"lawn\", \"spring\"],\n          char: \"🌱\",\n        },\n        herb: {\n          keywords: [\"vegetable\", \"plant\", \"medicine\", \"weed\", \"grass\", \"lawn\"],\n          char: \"🌿\",\n        },\n        shamrock: {\n          keywords: [\"vegetable\", \"plant\", \"nature\", \"irish\", \"clover\"],\n          char: \"☘\",\n        },\n        four_leaf_clover: {\n          keywords: [\"vegetable\", \"plant\", \"nature\", \"lucky\", \"irish\"],\n          char: \"🍀\",\n        },\n        bamboo: {\n          keywords: [\"plant\", \"nature\", \"vegetable\", \"panda\", \"pine_decoration\"],\n          char: \"🎍\",\n        },\n        tanabata_tree: {\n          keywords: [\"plant\", \"nature\", \"branch\", \"summer\"],\n          char: \"🎋\",\n        },\n        leaves: {\n          keywords: [\"nature\", \"plant\", \"tree\", \"vegetable\", \"grass\", \"lawn\", \"spring\"],\n          char: \"🍃\",\n        },\n        fallen_leaf: {\n          keywords: [\"nature\", \"plant\", \"vegetable\", \"leaves\"],\n          char: \"🍂\",\n        },\n        maple_leaf: {\n          keywords: [\"nature\", \"plant\", \"vegetable\", \"ca\", \"fall\"],\n          char: \"🍁\",\n        },\n        ear_of_rice: {\n          keywords: [\"nature\", \"plant\"],\n          char: \"🌾\",\n        },\n        hibiscus: {\n          keywords: [\"plant\", \"vegetable\", \"flowers\", \"beach\"],\n          char: \"🌺\",\n        },\n        sunflower: {\n          keywords: [\"nature\", \"plant\", \"fall\"],\n          char: \"🌻\",\n        },\n        rose: {\n          keywords: [\"flowers\", \"valentines\", \"love\", \"spring\"],\n          char: \"🌹\",\n        },\n        wilted_flower: {\n          keywords: [\"plant\", \"nature\", \"flower\"],\n          char: \"🥀\",\n        },\n        tulip: {\n          keywords: [\"flowers\", \"plant\", \"nature\", \"summer\", \"spring\"],\n          char: \"🌷\",\n        },\n        blossom: {\n          keywords: [\"nature\", \"flowers\", \"yellow\"],\n          char: \"🌼\",\n        },\n        cherry_blossom: {\n          keywords: [\"nature\", \"plant\", \"spring\", \"flower\"],\n          char: \"🌸\",\n        },\n        bouquet: {\n          keywords: [\"flowers\", \"nature\", \"spring\"],\n          char: \"💐\",\n        },\n        mushroom: {\n          keywords: [\"plant\", \"vegetable\"],\n          char: \"🍄\",\n        },\n        chestnut: {\n          keywords: [\"food\", \"squirrel\"],\n          char: \"🌰\",\n        },\n        jack_o_lantern: {\n          keywords: [\"halloween\", \"light\", \"pumpkin\", \"creepy\", \"fall\"],\n          char: \"🎃\",\n        },\n        shell: {\n          keywords: [\"nature\", \"sea\", \"beach\"],\n          char: \"🐚\",\n        },\n        spider_web: {\n          keywords: [\"animal\", \"insect\", \"arachnid\", \"silk\"],\n          char: \"🕸\",\n        },\n        earth_americas: {\n          keywords: [\"globe\", \"world\", \"USA\", \"international\"],\n          char: \"🌎\",\n        },\n        earth_africa: {\n          keywords: [\"globe\", \"world\", \"international\"],\n          char: \"🌍\",\n        },\n        earth_asia: {\n          keywords: [\"globe\", \"world\", \"east\", \"international\"],\n          char: \"🌏\",\n        },\n        full_moon: {\n          keywords: [\n            \"nature\",\n            \"yellow\",\n            \"twilight\",\n            \"planet\",\n            \"space\",\n            \"night\",\n            \"evening\",\n            \"sleep\",\n          ],\n          char: \"🌕\",\n        },\n        waning_gibbous_moon: {\n          keywords: [\n            \"nature\",\n            \"twilight\",\n            \"planet\",\n            \"space\",\n            \"night\",\n            \"evening\",\n            \"sleep\",\n            \"waxing_gibbous_moon\",\n          ],\n          char: \"🌖\",\n        },\n        last_quarter_moon: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌗\",\n        },\n        waning_crescent_moon: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌘\",\n        },\n        new_moon: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌑\",\n        },\n        waxing_crescent_moon: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌒\",\n        },\n        first_quarter_moon: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌓\",\n        },\n        waxing_gibbous_moon: {\n          keywords: [\n            \"nature\",\n            \"night\",\n            \"sky\",\n            \"gray\",\n            \"twilight\",\n            \"planet\",\n            \"space\",\n            \"evening\",\n            \"sleep\",\n          ],\n          char: \"🌔\",\n        },\n        new_moon_with_face: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌚\",\n        },\n        full_moon_with_face: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌝\",\n        },\n        first_quarter_moon_with_face: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌛\",\n        },\n        last_quarter_moon_with_face: {\n          keywords: [\"nature\", \"twilight\", \"planet\", \"space\", \"night\", \"evening\", \"sleep\"],\n          char: \"🌜\",\n        },\n        sun_with_face: {\n          keywords: [\"nature\", \"morning\", \"sky\"],\n          char: \"🌞\",\n        },\n        crescent_moon: {\n          keywords: [\"night\", \"sleep\", \"sky\", \"evening\", \"magic\"],\n          char: \"🌙\",\n        },\n        star: {\n          keywords: [\"night\", \"yellow\"],\n          char: \"⭐\",\n        },\n        star2: {\n          keywords: [\"night\", \"sparkle\", \"awesome\", \"good\", \"magic\"],\n          char: \"🌟\",\n        },\n        dizzy: {\n          keywords: [\"star\", \"sparkle\", \"shoot\", \"magic\"],\n          char: \"💫\",\n        },\n        sparkles: {\n          keywords: [\"stars\", \"shine\", \"shiny\", \"cool\", \"awesome\", \"good\", \"magic\"],\n          char: \"✨\",\n        },\n        comet: {\n          keywords: [\"space\"],\n          char: \"☄\",\n        },\n        sunny: {\n          keywords: [\"weather\", \"nature\", \"brightness\", \"summer\", \"beach\", \"spring\"],\n          char: \"☀️\",\n        },\n        sun_behind_small_cloud: {\n          keywords: [\"weather\"],\n          char: \"🌤\",\n        },\n        partly_sunny: {\n          keywords: [\"weather\", \"nature\", \"cloudy\", \"morning\", \"fall\", \"spring\"],\n          char: \"⛅\",\n        },\n        sun_behind_large_cloud: {\n          keywords: [\"weather\"],\n          char: \"🌥\",\n        },\n        sun_behind_rain_cloud: {\n          keywords: [\"weather\"],\n          char: \"🌦\",\n        },\n        cloud: {\n          keywords: [\"weather\", \"sky\"],\n          char: \"☁️\",\n        },\n        cloud_with_rain: {\n          keywords: [\"weather\"],\n          char: \"🌧\",\n        },\n        cloud_with_lightning_and_rain: {\n          keywords: [\"weather\", \"lightning\"],\n          char: \"⛈\",\n        },\n        cloud_with_lightning: {\n          keywords: [\"weather\", \"thunder\"],\n          char: \"🌩\",\n        },\n        zap: {\n          keywords: [\"thunder\", \"weather\", \"lightning bolt\", \"fast\"],\n          char: \"⚡\",\n        },\n        fire: {\n          keywords: [\"hot\", \"cook\", \"flame\"],\n          char: \"🔥\",\n        },\n        boom: {\n          keywords: [\"bomb\", \"explode\", \"explosion\", \"collision\", \"blown\"],\n          char: \"💥\",\n        },\n        snowflake: {\n          keywords: [\"winter\", \"season\", \"cold\", \"weather\", \"christmas\", \"xmas\"],\n          char: \"❄️\",\n        },\n        cloud_with_snow: {\n          keywords: [\"weather\"],\n          char: \"🌨\",\n        },\n        snowman: {\n          keywords: [\n            \"winter\",\n            \"season\",\n            \"cold\",\n            \"weather\",\n            \"christmas\",\n            \"xmas\",\n            \"frozen\",\n            \"without_snow\",\n          ],\n          char: \"⛄\",\n        },\n        snowman_with_snow: {\n          keywords: [\"winter\", \"season\", \"cold\", \"weather\", \"christmas\", \"xmas\", \"frozen\"],\n          char: \"☃\",\n        },\n        wind_face: {\n          keywords: [\"gust\", \"air\"],\n          char: \"🌬\",\n        },\n        dash: {\n          keywords: [\"wind\", \"air\", \"fast\", \"shoo\", \"fart\", \"smoke\", \"puff\"],\n          char: \"💨\",\n        },\n        tornado: {\n          keywords: [\"weather\", \"cyclone\", \"twister\"],\n          char: \"🌪\",\n        },\n        fog: {\n          keywords: [\"weather\"],\n          char: \"🌫\",\n        },\n        open_umbrella: {\n          keywords: [\"weather\", \"spring\"],\n          char: \"☂\",\n        },\n        umbrella: {\n          keywords: [\"rainy\", \"weather\", \"spring\"],\n          char: \"☔\",\n        },\n        droplet: {\n          keywords: [\"water\", \"drip\", \"faucet\", \"spring\"],\n          char: \"💧\",\n        },\n        sweat_drops: {\n          keywords: [\"water\", \"drip\", \"oops\"],\n          char: \"💦\",\n        },\n        ocean: {\n          keywords: [\"sea\", \"water\", \"wave\", \"nature\", \"tsunami\", \"disaster\"],\n          char: \"🌊\",\n        },\n      },\n    },\n  },\n\n  {\n    food: {\n      id: \"food_and_drink\",\n      name: t(\"FOOD_DRINK\"),\n      symbol: \"Food\",\n      emojis: {\n        green_apple: {\n          keywords: [\"fruit\", \"nature\"],\n          char: \"🍏\",\n        },\n        apple: {\n          keywords: [\"fruit\", \"mac\", \"school\"],\n          char: \"🍎\",\n        },\n        pear: {\n          keywords: [\"fruit\", \"nature\", \"food\"],\n          char: \"🍐\",\n        },\n        tangerine: {\n          keywords: [\"food\", \"fruit\", \"nature\", \"orange\"],\n          char: \"🍊\",\n        },\n        lemon: {\n          keywords: [\"fruit\", \"nature\"],\n          char: \"🍋\",\n        },\n        banana: {\n          keywords: [\"fruit\", \"food\", \"monkey\"],\n          char: \"🍌\",\n        },\n        watermelon: {\n          keywords: [\"fruit\", \"food\", \"picnic\", \"summer\"],\n          char: \"🍉\",\n        },\n        grapes: {\n          keywords: [\"fruit\", \"food\", \"wine\"],\n          char: \"🍇\",\n        },\n        strawberry: {\n          keywords: [\"fruit\", \"food\", \"nature\"],\n          char: \"🍓\",\n        },\n        melon: {\n          keywords: [\"fruit\", \"nature\", \"food\"],\n          char: \"🍈\",\n        },\n        cherries: {\n          keywords: [\"food\", \"fruit\"],\n          char: \"🍒\",\n        },\n        peach: {\n          keywords: [\"fruit\", \"nature\", \"food\"],\n          char: \"🍑\",\n        },\n        pineapple: {\n          keywords: [\"fruit\", \"nature\", \"food\"],\n          char: \"🍍\",\n        },\n        coconut: {\n          keywords: [\"fruit\", \"nature\", \"food\", \"palm\"],\n          char: \"🥥\",\n        },\n        kiwi_fruit: {\n          keywords: [\"fruit\", \"food\"],\n          char: \"🥝\",\n        },\n        mango: {\n          keywords: [\"fruit\", \"food\", \"tropical\"],\n          char: \"🥭\",\n        },\n        avocado: {\n          keywords: [\"fruit\", \"food\"],\n          char: \"🥑\",\n        },\n        broccoli: {\n          keywords: [\"fruit\", \"food\", \"vegetable\"],\n          char: \"🥦\",\n        },\n        tomato: {\n          keywords: [\"fruit\", \"vegetable\", \"nature\", \"food\"],\n          char: \"🍅\",\n        },\n        eggplant: {\n          keywords: [\"vegetable\", \"nature\", \"food\", \"aubergine\"],\n          char: \"🍆\",\n        },\n        cucumber: {\n          keywords: [\"fruit\", \"food\", \"pickle\"],\n          char: \"🥒\",\n        },\n        carrot: {\n          keywords: [\"vegetable\", \"food\", \"orange\"],\n          char: \"🥕\",\n        },\n        hot_pepper: {\n          keywords: [\"food\", \"spicy\", \"chilli\", \"chili\"],\n          char: \"🌶\",\n        },\n        potato: {\n          keywords: [\"food\", \"tuber\", \"vegatable\", \"starch\"],\n          char: \"🥔\",\n        },\n        corn: {\n          keywords: [\"food\", \"vegetable\", \"plant\"],\n          char: \"🌽\",\n        },\n        leafy_greens: {\n          keywords: [\"food\", \"vegetable\", \"plant\", \"bok choy\", \"cabbage\", \"kale\", \"lettuce\"],\n          char: \"🥬\",\n        },\n        sweet_potato: {\n          keywords: [\"food\", \"nature\"],\n          char: \"🍠\",\n        },\n        peanuts: {\n          keywords: [\"food\", \"nut\"],\n          char: \"🥜\",\n        },\n        honey_pot: {\n          keywords: [\"bees\", \"sweet\", \"kitchen\"],\n          char: \"🍯\",\n        },\n        croissant: {\n          keywords: [\"food\", \"bread\", \"french\"],\n          char: \"🥐\",\n        },\n        bread: {\n          keywords: [\"food\", \"wheat\", \"breakfast\", \"toast\"],\n          char: \"🍞\",\n        },\n        baguette_bread: {\n          keywords: [\"food\", \"bread\", \"french\"],\n          char: \"🥖\",\n        },\n        bagel: {\n          keywords: [\"food\", \"bread\", \"bakery\", \"schmear\"],\n          char: \"🥯\",\n        },\n        pretzel: {\n          keywords: [\"food\", \"bread\", \"twisted\"],\n          char: \"🥨\",\n        },\n        cheese: {\n          keywords: [\"food\", \"chadder\"],\n          char: \"🧀\",\n        },\n        egg: {\n          keywords: [\"food\", \"chicken\", \"breakfast\"],\n          char: \"🥚\",\n        },\n        bacon: {\n          keywords: [\"food\", \"breakfast\", \"pork\", \"pig\", \"meat\"],\n          char: \"🥓\",\n        },\n        steak: {\n          keywords: [\"food\", \"cow\", \"meat\", \"cut\", \"chop\", \"lambchop\", \"porkchop\"],\n          char: \"🥩\",\n        },\n        pancakes: {\n          keywords: [\"food\", \"breakfast\", \"flapjacks\", \"hotcakes\"],\n          char: \"🥞\",\n        },\n        poultry_leg: {\n          keywords: [\"food\", \"meat\", \"drumstick\", \"bird\", \"chicken\", \"turkey\"],\n          char: \"🍗\",\n        },\n        meat_on_bone: {\n          keywords: [\"good\", \"food\", \"drumstick\"],\n          char: \"🍖\",\n        },\n        bone: {\n          keywords: [\"skeleton\"],\n          char: \"🦴\",\n        },\n        fried_shrimp: {\n          keywords: [\"food\", \"animal\", \"appetizer\", \"summer\"],\n          char: \"🍤\",\n        },\n        fried_egg: {\n          keywords: [\"food\", \"breakfast\", \"kitchen\", \"egg\"],\n          char: \"🍳\",\n        },\n        hamburger: {\n          keywords: [\"meat\", \"fast food\", \"beef\", \"cheeseburger\", \"mcdonalds\", \"burger king\"],\n          char: \"🍔\",\n        },\n        fries: {\n          keywords: [\"chips\", \"snack\", \"fast food\"],\n          char: \"🍟\",\n        },\n        stuffed_flatbread: {\n          keywords: [\"food\", \"flatbread\", \"stuffed\", \"gyro\"],\n          char: \"🥙\",\n        },\n        hotdog: {\n          keywords: [\"food\", \"frankfurter\"],\n          char: \"🌭\",\n        },\n        pizza: {\n          keywords: [\"food\", \"party\"],\n          char: \"🍕\",\n        },\n        sandwich: {\n          keywords: [\"food\", \"lunch\", \"bread\"],\n          char: \"🥪\",\n        },\n        canned_food: {\n          keywords: [\"food\", \"soup\"],\n          char: \"🥫\",\n        },\n        spaghetti: {\n          keywords: [\"food\", \"italian\", \"noodle\"],\n          char: \"🍝\",\n        },\n        taco: {\n          keywords: [\"food\", \"mexican\"],\n          char: \"🌮\",\n        },\n        burrito: {\n          keywords: [\"food\", \"mexican\"],\n          char: \"🌯\",\n        },\n        green_salad: {\n          keywords: [\"food\", \"healthy\", \"lettuce\"],\n          char: \"🥗\",\n        },\n        shallow_pan_of_food: {\n          keywords: [\"food\", \"cooking\", \"casserole\", \"paella\"],\n          char: \"🥘\",\n        },\n        ramen: {\n          keywords: [\"food\", \"japanese\", \"noodle\", \"chopsticks\"],\n          char: \"🍜\",\n        },\n        stew: {\n          keywords: [\"food\", \"meat\", \"soup\"],\n          char: \"🍲\",\n        },\n        fish_cake: {\n          keywords: [\n            \"food\",\n            \"japan\",\n            \"sea\",\n            \"beach\",\n            \"narutomaki\",\n            \"pink\",\n            \"swirl\",\n            \"kamaboko\",\n            \"surimi\",\n            \"ramen\",\n          ],\n          char: \"🍥\",\n        },\n        fortune_cookie: {\n          keywords: [\"food\", \"prophecy\"],\n          char: \"🥠\",\n        },\n        sushi: {\n          keywords: [\"food\", \"fish\", \"japanese\", \"rice\"],\n          char: \"🍣\",\n        },\n        bento: {\n          keywords: [\"food\", \"japanese\", \"box\"],\n          char: \"🍱\",\n        },\n        curry: {\n          keywords: [\"food\", \"spicy\", \"hot\", \"indian\"],\n          char: \"🍛\",\n        },\n        rice_ball: {\n          keywords: [\"food\", \"japanese\"],\n          char: \"🍙\",\n        },\n        rice: {\n          keywords: [\"food\", \"china\", \"asian\"],\n          char: \"🍚\",\n        },\n        rice_cracker: {\n          keywords: [\"food\", \"japanese\"],\n          char: \"🍘\",\n        },\n        oden: {\n          keywords: [\"food\", \"japanese\"],\n          char: \"🍢\",\n        },\n        dango: {\n          keywords: [\"food\", \"dessert\", \"sweet\", \"japanese\", \"barbecue\", \"meat\"],\n          char: \"🍡\",\n        },\n        shaved_ice: {\n          keywords: [\"hot\", \"dessert\", \"summer\"],\n          char: \"🍧\",\n        },\n        ice_cream: {\n          keywords: [\"food\", \"hot\", \"dessert\"],\n          char: \"🍨\",\n        },\n        icecream: {\n          keywords: [\"food\", \"hot\", \"dessert\", \"summer\"],\n          char: \"🍦\",\n        },\n        pie: {\n          keywords: [\"food\", \"dessert\", \"pastry\"],\n          char: \"🥧\",\n        },\n        cake: {\n          keywords: [\"food\", \"dessert\"],\n          char: \"🍰\",\n        },\n        cupcake: {\n          keywords: [\"food\", \"dessert\", \"bakery\", \"sweet\"],\n          char: \"🧁\",\n        },\n        moon_cake: {\n          keywords: [\"food\", \"autumn\"],\n          char: \"🥮\",\n        },\n        birthday: {\n          keywords: [\"food\", \"dessert\", \"cake\"],\n          char: \"🎂\",\n        },\n        custard: {\n          keywords: [\"dessert\", \"food\"],\n          char: \"🍮\",\n        },\n        candy: {\n          keywords: [\"snack\", \"dessert\", \"sweet\", \"lolly\"],\n          char: \"🍬\",\n        },\n        lollipop: {\n          keywords: [\"food\", \"snack\", \"candy\", \"sweet\"],\n          char: \"🍭\",\n        },\n        chocolate_bar: {\n          keywords: [\"food\", \"snack\", \"dessert\", \"sweet\"],\n          char: \"🍫\",\n        },\n        popcorn: {\n          keywords: [\"food\", \"movie theater\", \"films\", \"snack\"],\n          char: \"🍿\",\n        },\n        dumpling: {\n          keywords: [\"food\", \"empanada\", \"pierogi\", \"potsticker\"],\n          char: \"🥟\",\n        },\n        doughnut: {\n          keywords: [\"food\", \"dessert\", \"snack\", \"sweet\", \"donut\"],\n          char: \"🍩\",\n        },\n        cookie: {\n          keywords: [\"food\", \"snack\", \"oreo\", \"chocolate\", \"sweet\", \"dessert\"],\n          char: \"🍪\",\n        },\n        milk_glass: {\n          keywords: [\"beverage\", \"drink\", \"cow\"],\n          char: \"🥛\",\n        },\n        beer: {\n          keywords: [\n            \"relax\",\n            \"beverage\",\n            \"drink\",\n            \"drunk\",\n            \"party\",\n            \"pub\",\n            \"summer\",\n            \"alcohol\",\n            \"booze\",\n          ],\n          char: \"🍺\",\n        },\n        beers: {\n          keywords: [\n            \"relax\",\n            \"beverage\",\n            \"drink\",\n            \"drunk\",\n            \"party\",\n            \"pub\",\n            \"summer\",\n            \"alcohol\",\n            \"booze\",\n          ],\n          char: \"🍻\",\n        },\n        clinking_glasses: {\n          keywords: [\n            \"beverage\",\n            \"drink\",\n            \"party\",\n            \"alcohol\",\n            \"celebrate\",\n            \"cheers\",\n            \"wine\",\n            \"champagne\",\n            \"toast\",\n          ],\n          char: \"🥂\",\n        },\n        wine_glass: {\n          keywords: [\"drink\", \"beverage\", \"drunk\", \"alcohol\", \"booze\"],\n          char: \"🍷\",\n        },\n        tumbler_glass: {\n          keywords: [\n            \"drink\",\n            \"beverage\",\n            \"drunk\",\n            \"alcohol\",\n            \"liquor\",\n            \"booze\",\n            \"bourbon\",\n            \"scotch\",\n            \"whisky\",\n            \"glass\",\n            \"shot\",\n          ],\n          char: \"🥃\",\n        },\n        cocktail: {\n          keywords: [\"drink\", \"drunk\", \"alcohol\", \"beverage\", \"booze\", \"mojito\"],\n          char: \"🍸\",\n        },\n        tropical_drink: {\n          keywords: [\"beverage\", \"cocktail\", \"summer\", \"beach\", \"alcohol\", \"booze\", \"mojito\"],\n          char: \"🍹\",\n        },\n        champagne: {\n          keywords: [\"drink\", \"wine\", \"bottle\", \"celebration\"],\n          char: \"🍾\",\n        },\n        sake: {\n          keywords: [\"wine\", \"drink\", \"drunk\", \"beverage\", \"japanese\", \"alcohol\", \"booze\"],\n          char: \"🍶\",\n        },\n        tea: {\n          keywords: [\"drink\", \"bowl\", \"breakfast\", \"green\", \"british\"],\n          char: \"🍵\",\n        },\n        cup_with_straw: {\n          keywords: [\"drink\", \"soda\"],\n          char: \"🥤\",\n        },\n        coffee: {\n          keywords: [\"beverage\", \"caffeine\", \"latte\", \"espresso\"],\n          char: \"☕\",\n        },\n        baby_bottle: {\n          keywords: [\"food\", \"container\", \"milk\"],\n          char: \"🍼\",\n        },\n        salt: {\n          keywords: [\"condiment\", \"shaker\"],\n          char: \"🧂\",\n        },\n        spoon: {\n          keywords: [\"cutlery\", \"kitchen\", \"tableware\"],\n          char: \"🥄\",\n        },\n        fork_and_knife: {\n          keywords: [\"cutlery\", \"kitchen\"],\n          char: \"🍴\",\n        },\n        plate_with_cutlery: {\n          keywords: [\"food\", \"eat\", \"meal\", \"lunch\", \"dinner\", \"restaurant\"],\n          char: \"🍽\",\n        },\n        bowl_with_spoon: {\n          keywords: [\"food\", \"breakfast\", \"cereal\", \"oatmeal\", \"porridge\"],\n          char: \"🥣\",\n        },\n        takeout_box: {\n          keywords: [\"food\", \"leftovers\"],\n          char: \"🥡\",\n        },\n        chopsticks: {\n          keywords: [\"food\"],\n          char: \"🥢\",\n        },\n      },\n    },\n  },\n\n  {\n    activity: {\n      id: \"activity\",\n      name: t(\"ACTIVITY\"),\n      symbol: \"Activity\",\n      emojis: {\n        soccer: {\n          keywords: [\"sports\", \"football\"],\n          char: \"⚽\",\n        },\n        basketball: {\n          keywords: [\"sports\", \"balls\", \"NBA\"],\n          char: \"🏀\",\n        },\n        football: {\n          keywords: [\"sports\", \"balls\", \"NFL\"],\n          char: \"🏈\",\n        },\n        baseball: {\n          keywords: [\"sports\", \"balls\"],\n          char: \"⚾\",\n        },\n        softball: {\n          keywords: [\"sports\", \"balls\"],\n          char: \"🥎\",\n        },\n        tennis: {\n          keywords: [\"sports\", \"balls\", \"green\"],\n          char: \"🎾\",\n        },\n        volleyball: {\n          keywords: [\"sports\", \"balls\"],\n          char: \"🏐\",\n        },\n        rugby_football: {\n          keywords: [\"sports\", \"team\"],\n          char: \"🏉\",\n        },\n        flying_disc: {\n          keywords: [\"sports\", \"frisbee\", \"ultimate\"],\n          char: \"🥏\",\n        },\n        \"8ball\": {\n          keywords: [\"pool\", \"hobby\", \"game\", \"luck\", \"magic\"],\n          char: \"🎱\",\n        },\n        golf: {\n          keywords: [\"sports\", \"business\", \"flag\", \"hole\", \"summer\"],\n          char: \"⛳\",\n        },\n        golfing_woman: {\n          keywords: [\"sports\", \"business\", \"woman\", \"female\"],\n          char: \"🏌️‍♀️\",\n        },\n        golfing_man: {\n          keywords: [\"sports\", \"business\"],\n          char: \"🏌\",\n          fitzpatrick_scale: true,\n        },\n        ping_pong: {\n          keywords: [\"sports\", \"pingpong\"],\n          char: \"🏓\",\n        },\n        badminton: {\n          keywords: [\"sports\"],\n          char: \"🏸\",\n        },\n        goal_net: {\n          keywords: [\"sports\"],\n          char: \"🥅\",\n        },\n        ice_hockey: {\n          keywords: [\"sports\"],\n          char: \"🏒\",\n        },\n        field_hockey: {\n          keywords: [\"sports\"],\n          char: \"🏑\",\n        },\n        lacrosse: {\n          keywords: [\"sports\", \"ball\", \"stick\"],\n          char: \"🥍\",\n        },\n        cricket: {\n          keywords: [\"sports\"],\n          char: \"🏏\",\n        },\n        ski: {\n          keywords: [\"sports\", \"winter\", \"cold\", \"snow\"],\n          char: \"🎿\",\n        },\n        skier: {\n          keywords: [\"sports\", \"winter\", \"snow\"],\n          char: \"⛷\",\n        },\n        snowboarder: {\n          keywords: [\"sports\", \"winter\"],\n          char: \"🏂\",\n          fitzpatrick_scale: true,\n        },\n        person_fencing: {\n          keywords: [\"sports\", \"fencing\", \"sword\"],\n          char: \"🤺\",\n        },\n        women_wrestling: {\n          keywords: [\"sports\", \"wrestlers\"],\n          char: \"🤼‍♀️\",\n        },\n        men_wrestling: {\n          keywords: [\"sports\", \"wrestlers\"],\n          char: \"🤼‍♂️\",\n        },\n        woman_cartwheeling: {\n          keywords: [\"gymnastics\"],\n          char: \"🤸‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_cartwheeling: {\n          keywords: [\"gymnastics\"],\n          char: \"🤸‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_playing_handball: {\n          keywords: [\"sports\"],\n          char: \"🤾‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_playing_handball: {\n          keywords: [\"sports\"],\n          char: \"🤾‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        ice_skate: {\n          keywords: [\"sports\"],\n          char: \"⛸\",\n        },\n        curling_stone: {\n          keywords: [\"sports\"],\n          char: \"🥌\",\n        },\n        skateboard: {\n          keywords: [\"board\"],\n          char: \"🛹\",\n        },\n        sled: {\n          keywords: [\"sleigh\", \"luge\", \"toboggan\"],\n          char: \"🛷\",\n        },\n        bow_and_arrow: {\n          keywords: [\"sports\"],\n          char: \"🏹\",\n        },\n        fishing_pole_and_fish: {\n          keywords: [\"food\", \"hobby\", \"summer\"],\n          char: \"🎣\",\n        },\n        boxing_glove: {\n          keywords: [\"sports\", \"fighting\"],\n          char: \"🥊\",\n        },\n        martial_arts_uniform: {\n          keywords: [\"judo\", \"karate\", \"taekwondo\"],\n          char: \"🥋\",\n        },\n        rowing_woman: {\n          keywords: [\"sports\", \"hobby\", \"water\", \"ship\", \"woman\", \"female\"],\n          char: \"🚣‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        rowing_man: {\n          keywords: [\"sports\", \"hobby\", \"water\", \"ship\"],\n          char: \"🚣\",\n          fitzpatrick_scale: true,\n        },\n        climbing_woman: {\n          keywords: [\"sports\", \"hobby\", \"woman\", \"female\", \"rock\"],\n          char: \"🧗‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        climbing_man: {\n          keywords: [\"sports\", \"hobby\", \"man\", \"male\", \"rock\"],\n          char: \"🧗‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        swimming_woman: {\n          keywords: [\n            \"sports\",\n            \"exercise\",\n            \"human\",\n            \"athlete\",\n            \"water\",\n            \"summer\",\n            \"woman\",\n            \"female\",\n          ],\n          char: \"🏊‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        swimming_man: {\n          keywords: [\"sports\", \"exercise\", \"human\", \"athlete\", \"water\", \"summer\"],\n          char: \"🏊\",\n          fitzpatrick_scale: true,\n        },\n        woman_playing_water_polo: {\n          keywords: [\"sports\", \"pool\"],\n          char: \"🤽‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_playing_water_polo: {\n          keywords: [\"sports\", \"pool\"],\n          char: \"🤽‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        woman_in_lotus_position: {\n          keywords: [\"woman\", \"female\", \"meditation\", \"yoga\", \"serenity\", \"zen\", \"mindfulness\"],\n          char: \"🧘‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_in_lotus_position: {\n          keywords: [\"man\", \"male\", \"meditation\", \"yoga\", \"serenity\", \"zen\", \"mindfulness\"],\n          char: \"🧘‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        surfing_woman: {\n          keywords: [\"sports\", \"ocean\", \"sea\", \"summer\", \"beach\", \"woman\", \"female\"],\n          char: \"🏄‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        surfing_man: {\n          keywords: [\"sports\", \"ocean\", \"sea\", \"summer\", \"beach\"],\n          char: \"🏄\",\n          fitzpatrick_scale: true,\n        },\n        bath: {\n          keywords: [\"clean\", \"shower\", \"bathroom\"],\n          char: \"🛀\",\n          fitzpatrick_scale: true,\n        },\n        basketball_woman: {\n          keywords: [\"sports\", \"human\", \"woman\", \"female\"],\n          char: \"⛹️‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        basketball_man: {\n          keywords: [\"sports\", \"human\"],\n          char: \"⛹\",\n          fitzpatrick_scale: true,\n        },\n        weight_lifting_woman: {\n          keywords: [\"sports\", \"training\", \"exercise\", \"woman\", \"female\"],\n          char: \"🏋️‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        weight_lifting_man: {\n          keywords: [\"sports\", \"training\", \"exercise\"],\n          char: \"🏋\",\n          fitzpatrick_scale: true,\n        },\n        biking_woman: {\n          keywords: [\"sports\", \"bike\", \"exercise\", \"hipster\", \"woman\", \"female\"],\n          char: \"🚴‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        biking_man: {\n          keywords: [\"sports\", \"bike\", \"exercise\", \"hipster\"],\n          char: \"🚴\",\n          fitzpatrick_scale: true,\n        },\n        mountain_biking_woman: {\n          keywords: [\"transportation\", \"sports\", \"human\", \"race\", \"bike\", \"woman\", \"female\"],\n          char: \"🚵‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        mountain_biking_man: {\n          keywords: [\"transportation\", \"sports\", \"human\", \"race\", \"bike\"],\n          char: \"🚵\",\n          fitzpatrick_scale: true,\n        },\n        horse_racing: {\n          keywords: [\"animal\", \"betting\", \"competition\", \"gambling\", \"luck\"],\n          char: \"🏇\",\n          fitzpatrick_scale: true,\n        },\n        business_suit_levitating: {\n          keywords: [\"suit\", \"business\", \"levitate\", \"hover\", \"jump\"],\n          char: \"🕴\",\n          fitzpatrick_scale: true,\n        },\n        trophy: {\n          keywords: [\"win\", \"award\", \"contest\", \"place\", \"ftw\", \"ceremony\"],\n          char: \"🏆\",\n        },\n        running_shirt_with_sash: {\n          keywords: [\"play\", \"pageant\"],\n          char: \"🎽\",\n        },\n        medal_sports: {\n          keywords: [\"award\", \"winning\"],\n          char: \"🏅\",\n        },\n        medal_military: {\n          keywords: [\"award\", \"winning\", \"army\"],\n          char: \"🎖\",\n        },\n        \"1st_place_medal\": {\n          keywords: [\"award\", \"winning\", \"first\"],\n          char: \"🥇\",\n        },\n        \"2nd_place_medal\": {\n          keywords: [\"award\", \"second\"],\n          char: \"🥈\",\n        },\n        \"3rd_place_medal\": {\n          keywords: [\"award\", \"third\"],\n          char: \"🥉\",\n        },\n        reminder_ribbon: {\n          keywords: [\"sports\", \"cause\", \"support\", \"awareness\"],\n          char: \"🎗\",\n        },\n        rosette: {\n          keywords: [\"flower\", \"decoration\", \"military\"],\n          char: \"🏵\",\n        },\n        ticket: {\n          keywords: [\"event\", \"concert\", \"pass\"],\n          char: \"🎫\",\n        },\n        tickets: {\n          keywords: [\"sports\", \"concert\", \"entrance\"],\n          char: \"🎟\",\n        },\n        performing_arts: {\n          keywords: [\"acting\", \"theater\", \"drama\"],\n          char: \"🎭\",\n        },\n        art: {\n          keywords: [\"design\", \"paint\", \"draw\", \"colors\"],\n          char: \"🎨\",\n        },\n        circus_tent: {\n          keywords: [\"festival\", \"carnival\", \"party\"],\n          char: \"🎪\",\n        },\n        woman_juggling: {\n          keywords: [\"juggle\", \"balance\", \"skill\", \"multitask\"],\n          char: \"🤹‍♀️\",\n          fitzpatrick_scale: true,\n        },\n        man_juggling: {\n          keywords: [\"juggle\", \"balance\", \"skill\", \"multitask\"],\n          char: \"🤹‍♂️\",\n          fitzpatrick_scale: true,\n        },\n        microphone: {\n          keywords: [\"sound\", \"music\", \"PA\", \"sing\", \"talkshow\"],\n          char: \"🎤\",\n        },\n        headphones: {\n          keywords: [\"music\", \"score\", \"gadgets\"],\n          char: \"🎧\",\n        },\n        musical_score: {\n          keywords: [\"treble\", \"clef\", \"compose\"],\n          char: \"🎼\",\n        },\n        musical_keyboard: {\n          keywords: [\"piano\", \"instrument\", \"compose\"],\n          char: \"🎹\",\n        },\n        drum: {\n          keywords: [\"music\", \"instrument\", \"drumsticks\", \"snare\"],\n          char: \"🥁\",\n        },\n        saxophone: {\n          keywords: [\"music\", \"instrument\", \"jazz\", \"blues\"],\n          char: \"🎷\",\n        },\n        trumpet: {\n          keywords: [\"music\", \"brass\"],\n          char: \"🎺\",\n        },\n        guitar: {\n          keywords: [\"music\", \"instrument\"],\n          char: \"🎸\",\n        },\n        violin: {\n          keywords: [\"music\", \"instrument\", \"orchestra\", \"symphony\"],\n          char: \"🎻\",\n        },\n        clapper: {\n          keywords: [\"movie\", \"film\", \"record\"],\n          char: \"🎬\",\n        },\n        video_game: {\n          keywords: [\"play\", \"console\", \"PS4\", \"controller\"],\n          char: \"🎮\",\n        },\n        space_invader: {\n          keywords: [\"game\", \"arcade\", \"play\"],\n          char: \"👾\",\n        },\n        dart: {\n          keywords: [\"game\", \"play\", \"bar\", \"target\", \"bullseye\"],\n          char: \"🎯\",\n        },\n        game_die: {\n          keywords: [\"dice\", \"random\", \"tabletop\", \"play\", \"luck\"],\n          char: \"🎲\",\n        },\n        chess_pawn: {\n          keywords: [\"expendable\"],\n          char: \"♟\",\n        },\n        slot_machine: {\n          keywords: [\"bet\", \"gamble\", \"vegas\", \"fruit machine\", \"luck\", \"casino\"],\n          char: \"🎰\",\n        },\n        jigsaw: {\n          keywords: [\"interlocking\", \"puzzle\", \"piece\"],\n          char: \"🧩\",\n        },\n        bowling: {\n          keywords: [\"sports\", \"fun\", \"play\"],\n          char: \"🎳\",\n        },\n      },\n    },\n  },\n\n  {\n    travel: {\n      id: \"travel_and_places\",\n      name: t(\"TRAVEL_PLACES\"),\n      symbol: \"Travel\",\n      emojis: {\n        red_car: {\n          keywords: [\"red\", \"transportation\", \"vehicle\"],\n          char: \"🚗\",\n        },\n        taxi: {\n          keywords: [\"uber\", \"vehicle\", \"cars\", \"transportation\"],\n          char: \"🚕\",\n        },\n        blue_car: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚙\",\n        },\n        bus: {\n          keywords: [\"car\", \"vehicle\", \"transportation\"],\n          char: \"🚌\",\n        },\n        trolleybus: {\n          keywords: [\"bart\", \"transportation\", \"vehicle\"],\n          char: \"🚎\",\n        },\n        racing_car: {\n          keywords: [\"sports\", \"race\", \"fast\", \"formula\", \"f1\"],\n          char: \"🏎\",\n        },\n        police_car: {\n          keywords: [\"vehicle\", \"cars\", \"transportation\", \"law\", \"legal\", \"enforcement\"],\n          char: \"🚓\",\n        },\n        ambulance: {\n          keywords: [\"health\", \"911\", \"hospital\"],\n          char: \"🚑\",\n        },\n        fire_engine: {\n          keywords: [\"transportation\", \"cars\", \"vehicle\"],\n          char: \"🚒\",\n        },\n        minibus: {\n          keywords: [\"vehicle\", \"car\", \"transportation\"],\n          char: \"🚐\",\n        },\n        truck: {\n          keywords: [\"cars\", \"transportation\"],\n          char: \"🚚\",\n        },\n        articulated_lorry: {\n          keywords: [\"vehicle\", \"cars\", \"transportation\", \"express\"],\n          char: \"🚛\",\n        },\n        tractor: {\n          keywords: [\"vehicle\", \"car\", \"farming\", \"agriculture\"],\n          char: \"🚜\",\n        },\n        kick_scooter: {\n          keywords: [\"vehicle\", \"kick\", \"razor\"],\n          char: \"🛴\",\n        },\n        motorcycle: {\n          keywords: [\"race\", \"sports\", \"fast\"],\n          char: \"🏍\",\n        },\n        bike: {\n          keywords: [\"sports\", \"bicycle\", \"exercise\", \"hipster\"],\n          char: \"🚲\",\n        },\n        motor_scooter: {\n          keywords: [\"vehicle\", \"vespa\", \"sasha\"],\n          char: \"🛵\",\n        },\n        rotating_light: {\n          keywords: [\n            \"police\",\n            \"ambulance\",\n            \"911\",\n            \"emergency\",\n            \"alert\",\n            \"error\",\n            \"pinged\",\n            \"law\",\n            \"legal\",\n          ],\n          char: \"🚨\",\n        },\n        oncoming_police_car: {\n          keywords: [\"vehicle\", \"law\", \"legal\", \"enforcement\", \"911\"],\n          char: \"🚔\",\n        },\n        oncoming_bus: {\n          keywords: [\"vehicle\", \"transportation\"],\n          char: \"🚍\",\n        },\n        oncoming_automobile: {\n          keywords: [\"car\", \"vehicle\", \"transportation\"],\n          char: \"🚘\",\n        },\n        oncoming_taxi: {\n          keywords: [\"vehicle\", \"cars\", \"uber\"],\n          char: \"🚖\",\n        },\n        aerial_tramway: {\n          keywords: [\"transportation\", \"vehicle\", \"ski\"],\n          char: \"🚡\",\n        },\n        mountain_cableway: {\n          keywords: [\"transportation\", \"vehicle\", \"ski\"],\n          char: \"🚠\",\n        },\n        suspension_railway: {\n          keywords: [\"vehicle\", \"transportation\"],\n          char: \"🚟\",\n        },\n        railway_car: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚃\",\n        },\n        train: {\n          keywords: [\"transportation\", \"vehicle\", \"carriage\", \"public\", \"travel\"],\n          char: \"🚋\",\n        },\n        monorail: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚝\",\n        },\n        bullettrain_side: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚄\",\n        },\n        bullettrain_front: {\n          keywords: [\"transportation\", \"vehicle\", \"speed\", \"fast\", \"public\", \"travel\"],\n          char: \"🚅\",\n        },\n        light_rail: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚈\",\n        },\n        mountain_railway: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚞\",\n        },\n        steam_locomotive: {\n          keywords: [\"transportation\", \"vehicle\", \"train\"],\n          char: \"🚂\",\n        },\n        train2: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚆\",\n        },\n        metro: {\n          keywords: [\"transportation\", \"blue-square\", \"mrt\", \"underground\", \"tube\"],\n          char: \"🚇\",\n        },\n        tram: {\n          keywords: [\"transportation\", \"vehicle\"],\n          char: \"🚊\",\n        },\n        station: {\n          keywords: [\"transportation\", \"vehicle\", \"public\"],\n          char: \"🚉\",\n        },\n        flying_saucer: {\n          keywords: [\"transportation\", \"vehicle\", \"ufo\"],\n          char: \"🛸\",\n        },\n        helicopter: {\n          keywords: [\"transportation\", \"vehicle\", \"fly\"],\n          char: \"🚁\",\n        },\n        small_airplane: {\n          keywords: [\"flight\", \"transportation\", \"fly\", \"vehicle\"],\n          char: \"🛩\",\n        },\n        airplane: {\n          keywords: [\"vehicle\", \"transportation\", \"flight\", \"fly\"],\n          char: \"✈️\",\n        },\n        flight_departure: {\n          keywords: [\"airport\", \"flight\", \"landing\"],\n          char: \"🛫\",\n        },\n        flight_arrival: {\n          keywords: [\"airport\", \"flight\", \"boarding\"],\n          char: \"🛬\",\n        },\n        sailboat: {\n          keywords: [\"ship\", \"summer\", \"transportation\", \"water\", \"sailing\"],\n          char: \"⛵\",\n        },\n        motor_boat: {\n          keywords: [\"ship\"],\n          char: \"🛥\",\n        },\n        speedboat: {\n          keywords: [\"ship\", \"transportation\", \"vehicle\", \"summer\"],\n          char: \"🚤\",\n        },\n        ferry: {\n          keywords: [\"boat\", \"ship\", \"yacht\"],\n          char: \"⛴\",\n        },\n        passenger_ship: {\n          keywords: [\"yacht\", \"cruise\", \"ferry\"],\n          char: \"🛳\",\n        },\n        rocket: {\n          keywords: [\"launch\", \"ship\", \"staffmode\", \"NASA\", \"outer space\", \"outer_space\", \"fly\"],\n          char: \"🚀\",\n        },\n        artificial_satellite: {\n          keywords: [\"communication\", \"gps\", \"orbit\", \"spaceflight\", \"NASA\", \"ISS\"],\n          char: \"🛰\",\n        },\n        seat: {\n          keywords: [\"sit\", \"airplane\", \"transport\", \"bus\", \"flight\", \"fly\"],\n          char: \"💺\",\n        },\n        canoe: {\n          keywords: [\"boat\", \"paddle\", \"water\", \"ship\"],\n          char: \"🛶\",\n        },\n        anchor: {\n          keywords: [\"ship\", \"ferry\", \"sea\", \"boat\"],\n          char: \"⚓\",\n        },\n        construction: {\n          keywords: [\"wip\", \"progress\", \"caution\", \"warning\"],\n          char: \"🚧\",\n        },\n        fuelpump: {\n          keywords: [\"gas station\", \"petroleum\"],\n          char: \"⛽\",\n        },\n        busstop: {\n          keywords: [\"transportation\", \"wait\"],\n          char: \"🚏\",\n        },\n        vertical_traffic_light: {\n          keywords: [\"transportation\", \"driving\"],\n          char: \"🚦\",\n        },\n        traffic_light: {\n          keywords: [\"transportation\", \"signal\"],\n          char: \"🚥\",\n        },\n        checkered_flag: {\n          keywords: [\"contest\", \"finishline\", \"race\", \"gokart\"],\n          char: \"🏁\",\n        },\n        ship: {\n          keywords: [\"transportation\", \"titanic\", \"deploy\"],\n          char: \"🚢\",\n        },\n        ferris_wheel: {\n          keywords: [\"photo\", \"carnival\", \"londoneye\"],\n          char: \"🎡\",\n        },\n        roller_coaster: {\n          keywords: [\"carnival\", \"playground\", \"photo\", \"fun\"],\n          char: \"🎢\",\n        },\n        carousel_horse: {\n          keywords: [\"photo\", \"carnival\"],\n          char: \"🎠\",\n        },\n        building_construction: {\n          keywords: [\"wip\", \"working\", \"progress\"],\n          char: \"🏗\",\n        },\n        foggy: {\n          keywords: [\"photo\", \"mountain\"],\n          char: \"🌁\",\n        },\n        tokyo_tower: {\n          keywords: [\"photo\", \"japanese\"],\n          char: \"🗼\",\n        },\n        factory: {\n          keywords: [\"building\", \"industry\", \"pollution\", \"smoke\"],\n          char: \"🏭\",\n        },\n        fountain: {\n          keywords: [\"photo\", \"summer\", \"water\", \"fresh\"],\n          char: \"⛲\",\n        },\n        rice_scene: {\n          keywords: [\"photo\", \"japan\", \"asia\", \"tsukimi\"],\n          char: \"🎑\",\n        },\n        mountain: {\n          keywords: [\"photo\", \"nature\", \"environment\"],\n          char: \"⛰\",\n        },\n        mountain_snow: {\n          keywords: [\"photo\", \"nature\", \"environment\", \"winter\", \"cold\"],\n          char: \"🏔\",\n        },\n        mount_fuji: {\n          keywords: [\"photo\", \"mountain\", \"nature\", \"japanese\"],\n          char: \"🗻\",\n        },\n        volcano: {\n          keywords: [\"photo\", \"nature\", \"disaster\"],\n          char: \"🌋\",\n        },\n        japan: {\n          keywords: [\"nation\", \"country\", \"japanese\", \"asia\"],\n          char: \"🗾\",\n        },\n        camping: {\n          keywords: [\"photo\", \"outdoors\", \"tent\"],\n          char: \"🏕\",\n        },\n        tent: {\n          keywords: [\"photo\", \"camping\", \"outdoors\"],\n          char: \"⛺\",\n        },\n        national_park: {\n          keywords: [\"photo\", \"environment\", \"nature\"],\n          char: \"🏞\",\n        },\n        motorway: {\n          keywords: [\"road\", \"cupertino\", \"interstate\", \"highway\"],\n          char: \"🛣\",\n        },\n        railway_track: {\n          keywords: [\"train\", \"transportation\"],\n          char: \"🛤\",\n        },\n        sunrise: {\n          keywords: [\"morning\", \"view\", \"vacation\", \"photo\"],\n          char: \"🌅\",\n        },\n        sunrise_over_mountains: {\n          keywords: [\"view\", \"vacation\", \"photo\"],\n          char: \"🌄\",\n        },\n        desert: {\n          keywords: [\"photo\", \"warm\", \"saharah\"],\n          char: \"🏜\",\n        },\n        beach_umbrella: {\n          keywords: [\"weather\", \"summer\", \"sunny\", \"sand\", \"mojito\"],\n          char: \"🏖\",\n        },\n        desert_island: {\n          keywords: [\"photo\", \"tropical\", \"mojito\"],\n          char: \"🏝\",\n        },\n        city_sunrise: {\n          keywords: [\"photo\", \"good morning\", \"dawn\"],\n          char: \"🌇\",\n        },\n        city_sunset: {\n          keywords: [\"photo\", \"evening\", \"sky\", \"buildings\"],\n          char: \"🌆\",\n        },\n        cityscape: {\n          keywords: [\"photo\", \"night life\", \"urban\"],\n          char: \"🏙\",\n        },\n        night_with_stars: {\n          keywords: [\"evening\", \"city\", \"downtown\"],\n          char: \"🌃\",\n        },\n        bridge_at_night: {\n          keywords: [\"photo\", \"sanfrancisco\"],\n          char: \"🌉\",\n        },\n        milky_way: {\n          keywords: [\"photo\", \"space\", \"stars\"],\n          char: \"🌌\",\n        },\n        stars: {\n          keywords: [\"night\", \"photo\"],\n          char: \"🌠\",\n        },\n        sparkler: {\n          keywords: [\"stars\", \"night\", \"shine\"],\n          char: \"🎇\",\n        },\n        fireworks: {\n          keywords: [\"photo\", \"festival\", \"carnival\", \"congratulations\"],\n          char: \"🎆\",\n        },\n        rainbow: {\n          keywords: [\"nature\", \"happy\", \"unicorn_face\", \"photo\", \"sky\", \"spring\"],\n          char: \"🌈\",\n        },\n        houses: {\n          keywords: [\"buildings\", \"photo\"],\n          char: \"🏘\",\n        },\n        european_castle: {\n          keywords: [\"building\", \"royalty\", \"history\"],\n          char: \"🏰\",\n        },\n        japanese_castle: {\n          keywords: [\"photo\", \"building\"],\n          char: \"🏯\",\n        },\n        stadium: {\n          keywords: [\"photo\", \"place\", \"sports\", \"concert\", \"venue\"],\n          char: \"🏟\",\n        },\n        statue_of_liberty: {\n          keywords: [\"american\", \"newyork\"],\n          char: \"🗽\",\n        },\n        house: {\n          keywords: [\"building\", \"home\"],\n          char: \"🏠\",\n        },\n        house_with_garden: {\n          keywords: [\"home\", \"plant\", \"nature\"],\n          char: \"🏡\",\n        },\n        derelict_house: {\n          keywords: [\"abandon\", \"evict\", \"broken\", \"building\"],\n          char: \"🏚\",\n        },\n        office: {\n          keywords: [\"building\", \"bureau\", \"work\"],\n          char: \"🏢\",\n        },\n        department_store: {\n          keywords: [\"building\", \"shopping\", \"mall\"],\n          char: \"🏬\",\n        },\n        post_office: {\n          keywords: [\"building\", \"envelope\", \"communication\"],\n          char: \"🏣\",\n        },\n        european_post_office: {\n          keywords: [\"building\", \"email\"],\n          char: \"🏤\",\n        },\n        hospital: {\n          keywords: [\"building\", \"health\", \"surgery\", \"doctor\"],\n          char: \"🏥\",\n        },\n        bank: {\n          keywords: [\"building\", \"money\", \"sales\", \"cash\", \"business\", \"enterprise\"],\n          char: \"🏦\",\n        },\n        hotel: {\n          keywords: [\"building\", \"accomodation\", \"checkin\"],\n          char: \"🏨\",\n        },\n        convenience_store: {\n          keywords: [\"building\", \"shopping\", \"groceries\"],\n          char: \"🏪\",\n        },\n        school: {\n          keywords: [\"building\", \"student\", \"education\", \"learn\", \"teach\"],\n          char: \"🏫\",\n        },\n        love_hotel: {\n          keywords: [\"like\", \"affection\", \"dating\"],\n          char: \"🏩\",\n        },\n        wedding: {\n          keywords: [\"love\", \"like\", \"affection\", \"couple\", \"marriage\", \"bride\", \"groom\"],\n          char: \"💒\",\n        },\n        classical_building: {\n          keywords: [\"art\", \"culture\", \"history\"],\n          char: \"🏛\",\n        },\n        church: {\n          keywords: [\"building\", \"religion\", \"christ\"],\n          char: \"⛪\",\n        },\n        mosque: {\n          keywords: [\"islam\", \"worship\", \"minaret\"],\n          char: \"🕌\",\n        },\n        synagogue: {\n          keywords: [\"judaism\", \"worship\", \"temple\", \"jewish\"],\n          char: \"🕍\",\n        },\n        kaaba: {\n          keywords: [\"mecca\", \"mosque\", \"islam\"],\n          char: \"🕋\",\n        },\n        shinto_shrine: {\n          keywords: [\"temple\", \"japan\", \"kyoto\"],\n          char: \"⛩\",\n        },\n      },\n    },\n  },\n\n  {\n    objects: {\n      id: \"objects\",\n      name: t(\"OBJECTS\"),\n      symbol: \"Objects\",\n      emojis: {\n        watch: {\n          keywords: [\"time\", \"accessories\"],\n          char: \"⌚\",\n        },\n        iphone: {\n          keywords: [\"technology\", \"apple\", \"gadgets\", \"dial\"],\n          char: \"📱\",\n        },\n        calling: {\n          keywords: [\"iphone\", \"incoming\"],\n          char: \"📲\",\n        },\n        computer: {\n          keywords: [\"technology\", \"laptop\", \"screen\", \"display\", \"monitor\"],\n          char: \"💻\",\n        },\n        keyboard: {\n          keywords: [\"technology\", \"computer\", \"type\", \"input\", \"text\"],\n          char: \"⌨\",\n        },\n        desktop_computer: {\n          keywords: [\"technology\", \"computing\", \"screen\"],\n          char: \"🖥\",\n        },\n        printer: {\n          keywords: [\"paper\", \"ink\"],\n          char: \"🖨\",\n        },\n        computer_mouse: {\n          keywords: [\"click\"],\n          char: \"🖱\",\n        },\n        trackball: {\n          keywords: [\"technology\", \"trackpad\"],\n          char: \"🖲\",\n        },\n        joystick: {\n          keywords: [\"game\", \"play\"],\n          char: \"🕹\",\n        },\n        clamp: {\n          keywords: [\"tool\"],\n          char: \"🗜\",\n        },\n        minidisc: {\n          keywords: [\"technology\", \"record\", \"emojies\", \"disk\", \"90s\"],\n          char: \"💽\",\n        },\n        floppy_disk: {\n          keywords: [\"oldschool\", \"technology\", \"save\", \"90s\", \"80s\"],\n          char: \"💾\",\n        },\n        cd: {\n          keywords: [\"technology\", \"dvd\", \"disk\", \"disc\", \"90s\"],\n          char: \"💿\",\n        },\n        dvd: {\n          keywords: [\"cd\", \"disk\", \"disc\"],\n          char: \"📀\",\n        },\n        vhs: {\n          keywords: [\"record\", \"video\", \"oldschool\", \"90s\", \"80s\"],\n          char: \"📼\",\n        },\n        camera: {\n          keywords: [\"gadgets\", \"photography\"],\n          char: \"📷\",\n        },\n        camera_flash: {\n          keywords: [\"photography\", \"gadgets\"],\n          char: \"📸\",\n        },\n        video_camera: {\n          keywords: [\"film\", \"record\"],\n          char: \"📹\",\n        },\n        movie_camera: {\n          keywords: [\"film\", \"record\"],\n          char: \"🎥\",\n        },\n        film_projector: {\n          keywords: [\"video\", \"tape\", \"record\", \"movie\"],\n          char: \"📽\",\n        },\n        film_strip: {\n          keywords: [\"movie\"],\n          char: \"🎞\",\n        },\n        telephone_receiver: {\n          keywords: [\"technology\", \"communication\", \"dial\"],\n          char: \"📞\",\n        },\n        phone: {\n          keywords: [\"technology\", \"communication\", \"dial\", \"telephone\"],\n          char: \"☎️\",\n        },\n        pager: {\n          keywords: [\"bbcall\", \"oldschool\", \"90s\"],\n          char: \"📟\",\n        },\n        fax: {\n          keywords: [\"communication\", \"technology\"],\n          char: \"📠\",\n        },\n        tv: {\n          keywords: [\"technology\", \"program\", \"oldschool\", \"show\", \"television\"],\n          char: \"📺\",\n        },\n        radio: {\n          keywords: [\"communication\", \"music\", \"podcast\", \"program\"],\n          char: \"📻\",\n        },\n        studio_microphone: {\n          keywords: [\"sing\", \"recording\", \"artist\", \"talkshow\"],\n          char: \"🎙\",\n        },\n        level_slider: {\n          keywords: [\"scale\"],\n          char: \"🎚\",\n        },\n        control_knobs: {\n          keywords: [\"dial\"],\n          char: \"🎛\",\n        },\n        compass: {\n          keywords: [\"magnetic\", \"navigation\", \"orienteering\"],\n          char: \"🧭\",\n        },\n        stopwatch: {\n          keywords: [\"time\", \"deadline\"],\n          char: \"⏱\",\n        },\n        timer_clock: {\n          keywords: [\"alarm\"],\n          char: \"⏲\",\n        },\n        alarm_clock: {\n          keywords: [\"time\", \"wake\"],\n          char: \"⏰\",\n        },\n        mantelpiece_clock: {\n          keywords: [\"time\"],\n          char: \"🕰\",\n        },\n        hourglass_flowing_sand: {\n          keywords: [\"oldschool\", \"time\", \"countdown\"],\n          char: \"⏳\",\n        },\n        hourglass: {\n          keywords: [\"time\", \"clock\", \"oldschool\", \"limit\", \"exam\", \"quiz\", \"test\"],\n          char: \"⌛\",\n        },\n        satellite: {\n          keywords: [\"communication\", \"future\", \"radio\", \"space\"],\n          char: \"📡\",\n        },\n        battery: {\n          keywords: [\"power\", \"energy\", \"sustain\"],\n          char: \"🔋\",\n        },\n        electric_plug: {\n          keywords: [\"charger\", \"power\"],\n          char: \"🔌\",\n        },\n        bulb: {\n          keywords: [\"light\", \"electricity\", \"idea\"],\n          char: \"💡\",\n        },\n        flashlight: {\n          keywords: [\"dark\", \"camping\", \"sight\", \"night\"],\n          char: \"🔦\",\n        },\n        candle: {\n          keywords: [\"fire\", \"wax\"],\n          char: \"🕯\",\n        },\n        fire_extinguisher: {\n          keywords: [\"quench\"],\n          char: \"🧯\",\n        },\n        wastebasket: {\n          keywords: [\"bin\", \"trash\", \"rubbish\", \"garbage\", \"toss\"],\n          char: \"🗑\",\n        },\n        oil_drum: {\n          keywords: [\"barrell\"],\n          char: \"🛢\",\n        },\n        money_with_wings: {\n          keywords: [\"dollar\", \"bills\", \"payment\", \"sale\"],\n          char: \"💸\",\n        },\n        dollar: {\n          keywords: [\"money\", \"sales\", \"bill\", \"currency\"],\n          char: \"💵\",\n        },\n        yen: {\n          keywords: [\"money\", \"sales\", \"japanese\", \"dollar\", \"currency\"],\n          char: \"💴\",\n        },\n        euro: {\n          keywords: [\"money\", \"sales\", \"dollar\", \"currency\"],\n          char: \"💶\",\n        },\n        pound: {\n          keywords: [\"british\", \"sterling\", \"money\", \"sales\", \"bills\", \"uk\", \"england\", \"currency\"],\n          char: \"💷\",\n        },\n        moneybag: {\n          keywords: [\"dollar\", \"payment\", \"coins\", \"sale\"],\n          char: \"💰\",\n        },\n        credit_card: {\n          keywords: [\"money\", \"sales\", \"dollar\", \"bill\", \"payment\", \"shopping\"],\n          char: \"💳\",\n        },\n        gem: {\n          keywords: [\"blue\", \"ruby\", \"diamond\", \"jewelry\"],\n          char: \"💎\",\n        },\n        balance_scale: {\n          keywords: [\"law\", \"fairness\", \"weight\"],\n          char: \"⚖\",\n        },\n        toolbox: {\n          keywords: [\"tools\", \"diy\", \"fix\", \"maintainer\", \"mechanic\"],\n          char: \"🧰\",\n        },\n        wrench: {\n          keywords: [\"tools\", \"diy\", \"ikea\", \"fix\", \"maintainer\"],\n          char: \"🔧\",\n        },\n        hammer: {\n          keywords: [\"tools\", \"build\", \"create\"],\n          char: \"🔨\",\n        },\n        hammer_and_pick: {\n          keywords: [\"tools\", \"build\", \"create\"],\n          char: \"⚒\",\n        },\n        hammer_and_wrench: {\n          keywords: [\"tools\", \"build\", \"create\"],\n          char: \"🛠\",\n        },\n        pick: {\n          keywords: [\"tools\", \"dig\"],\n          char: \"⛏\",\n        },\n        nut_and_bolt: {\n          keywords: [\"handy\", \"tools\", \"fix\"],\n          char: \"🔩\",\n        },\n        gear: {\n          keywords: [\"cog\"],\n          char: \"⚙\",\n        },\n        brick: {\n          keywords: [\"bricks\"],\n          char: \"🧱\",\n        },\n        chains: {\n          keywords: [\"lock\", \"arrest\"],\n          char: \"⛓\",\n        },\n        magnet: {\n          keywords: [\"attraction\", \"magnetic\"],\n          char: \"🧲\",\n        },\n        gun: {\n          keywords: [\"violence\", \"weapon\", \"pistol\", \"revolver\"],\n          char: \"🔫\",\n        },\n        bomb: {\n          keywords: [\"boom\", \"explode\", \"explosion\", \"terrorism\"],\n          char: \"💣\",\n        },\n        firecracker: {\n          keywords: [\"dynamite\", \"boom\", \"explode\", \"explosion\", \"explosive\"],\n          char: \"🧨\",\n        },\n        hocho: {\n          keywords: [\"knife\", \"blade\", \"cutlery\", \"kitchen\", \"weapon\"],\n          char: \"🔪\",\n        },\n        dagger: {\n          keywords: [\"weapon\"],\n          char: \"🗡\",\n        },\n        crossed_swords: {\n          keywords: [\"weapon\"],\n          char: \"⚔\",\n        },\n        shield: {\n          keywords: [\"protection\", \"security\"],\n          char: \"🛡\",\n        },\n        smoking: {\n          keywords: [\"kills\", \"tobacco\", \"cigarette\", \"joint\", \"smoke\"],\n          char: \"🚬\",\n        },\n        skull_and_crossbones: {\n          keywords: [\"poison\", \"danger\", \"deadly\", \"scary\", \"death\", \"pirate\", \"evil\"],\n          char: \"☠\",\n        },\n        coffin: {\n          keywords: [\n            \"vampire\",\n            \"dead\",\n            \"die\",\n            \"death\",\n            \"rip\",\n            \"graveyard\",\n            \"cemetery\",\n            \"casket\",\n            \"funeral\",\n            \"box\",\n          ],\n          char: \"⚰\",\n        },\n        funeral_urn: {\n          keywords: [\"dead\", \"die\", \"death\", \"rip\", \"ashes\"],\n          char: \"⚱\",\n        },\n        amphora: {\n          keywords: [\"vase\", \"jar\"],\n          char: \"🏺\",\n        },\n        crystal_ball: {\n          keywords: [\"disco\", \"party\", \"magic\", \"circus\", \"fortune_teller\"],\n          char: \"🔮\",\n        },\n        prayer_beads: {\n          keywords: [\"dhikr\", \"religious\"],\n          char: \"📿\",\n        },\n        nazar_amulet: {\n          keywords: [\"bead\", \"charm\"],\n          char: \"🧿\",\n        },\n        barber: {\n          keywords: [\"hair\", \"salon\", \"style\"],\n          char: \"💈\",\n        },\n        alembic: {\n          keywords: [\"distilling\", \"science\", \"experiment\", \"chemistry\"],\n          char: \"⚗\",\n        },\n        telescope: {\n          keywords: [\"stars\", \"space\", \"zoom\", \"science\", \"astronomy\"],\n          char: \"🔭\",\n        },\n        microscope: {\n          keywords: [\"laboratory\", \"experiment\", \"zoomin\", \"science\", \"study\"],\n          char: \"🔬\",\n        },\n        hole: {\n          keywords: [\"embarrassing\"],\n          char: \"🕳\",\n        },\n        pill: {\n          keywords: [\"health\", \"medicine\", \"doctor\", \"pharmacy\", \"drug\"],\n          char: \"💊\",\n        },\n        syringe: {\n          keywords: [\n            \"health\",\n            \"hospital\",\n            \"drugs\",\n            \"blood\",\n            \"medicine\",\n            \"needle\",\n            \"doctor\",\n            \"nurse\",\n          ],\n          char: \"💉\",\n        },\n        dna: {\n          keywords: [\"biologist\", \"genetics\", \"life\"],\n          char: \"🧬\",\n        },\n        microbe: {\n          keywords: [\"amoeba\", \"bacteria\", \"germs\"],\n          char: \"🦠\",\n        },\n        petri_dish: {\n          keywords: [\"bacteria\", \"biology\", \"culture\", \"lab\"],\n          char: \"🧫\",\n        },\n        test_tube: {\n          keywords: [\"chemistry\", \"experiment\", \"lab\", \"science\"],\n          char: \"🧪\",\n        },\n        thermometer: {\n          keywords: [\"weather\", \"temperature\", \"hot\", \"cold\"],\n          char: \"🌡\",\n        },\n        broom: {\n          keywords: [\"cleaning\", \"sweeping\", \"witch\"],\n          char: \"🧹\",\n        },\n        basket: {\n          keywords: [\"laundry\"],\n          char: \"🧺\",\n        },\n        toilet_paper: {\n          keywords: [\"roll\"],\n          char: \"🧻\",\n        },\n        label: {\n          keywords: [\"sale\", \"tag\"],\n          char: \"🏷\",\n        },\n        bookmark: {\n          keywords: [\"favorite\", \"label\", \"save\"],\n          char: \"🔖\",\n        },\n        toilet: {\n          keywords: [\"restroom\", \"wc\", \"washroom\", \"bathroom\", \"potty\"],\n          char: \"🚽\",\n        },\n        shower: {\n          keywords: [\"clean\", \"water\", \"bathroom\"],\n          char: \"🚿\",\n        },\n        bathtub: {\n          keywords: [\"clean\", \"shower\", \"bathroom\"],\n          char: \"🛁\",\n        },\n        soap: {\n          keywords: [\"bar\", \"bathing\", \"cleaning\", \"lather\"],\n          char: \"🧼\",\n        },\n        sponge: {\n          keywords: [\"absorbing\", \"cleaning\", \"porous\"],\n          char: \"🧽\",\n        },\n        lotion_bottle: {\n          keywords: [\"moisturizer\", \"sunscreen\"],\n          char: \"🧴\",\n        },\n        key: {\n          keywords: [\"lock\", \"door\", \"password\"],\n          char: \"🔑\",\n        },\n        old_key: {\n          keywords: [\"lock\", \"door\", \"password\"],\n          char: \"🗝\",\n        },\n        couch_and_lamp: {\n          keywords: [\"read\", \"chill\"],\n          char: \"🛋\",\n        },\n        sleeping_bed: {\n          keywords: [\"bed\", \"rest\"],\n          char: \"🛌\",\n          fitzpatrick_scale: true,\n        },\n        bed: {\n          keywords: [\"sleep\", \"rest\"],\n          char: \"🛏\",\n        },\n        door: {\n          keywords: [\"house\", \"entry\", \"exit\"],\n          char: \"🚪\",\n        },\n        bellhop_bell: {\n          keywords: [\"service\"],\n          char: \"🛎\",\n        },\n        teddy_bear: {\n          keywords: [\"plush\", \"stuffed\"],\n          char: \"🧸\",\n        },\n        framed_picture: {\n          keywords: [\"photography\"],\n          char: \"🖼\",\n        },\n        world_map: {\n          keywords: [\"location\", \"direction\"],\n          char: \"🗺\",\n        },\n        parasol_on_ground: {\n          keywords: [\"weather\", \"summer\"],\n          char: \"⛱\",\n        },\n        moyai: {\n          keywords: [\"rock\", \"easter island\", \"moai\"],\n          char: \"🗿\",\n        },\n        shopping: {\n          keywords: [\"mall\", \"buy\", \"purchase\"],\n          char: \"🛍\",\n        },\n        shopping_cart: {\n          keywords: [\"trolley\"],\n          char: \"🛒\",\n        },\n        balloon: {\n          keywords: [\"party\", \"celebration\", \"birthday\", \"circus\"],\n          char: \"🎈\",\n        },\n        flags: {\n          keywords: [\"fish\", \"japanese\", \"koinobori\", \"carp\", \"banner\"],\n          char: \"🎏\",\n        },\n        ribbon: {\n          keywords: [\"decoration\", \"pink\", \"girl\", \"bowtie\"],\n          char: \"🎀\",\n        },\n        gift: {\n          keywords: [\"present\", \"birthday\", \"christmas\", \"xmas\"],\n          char: \"🎁\",\n        },\n        confetti_ball: {\n          keywords: [\"festival\", \"party\", \"birthday\", \"circus\"],\n          char: \"🎊\",\n        },\n        tada: {\n          keywords: [\"party\", \"congratulations\", \"birthday\", \"magic\", \"circus\", \"celebration\"],\n          char: \"🎉\",\n        },\n        dolls: {\n          keywords: [\"japanese\", \"toy\", \"kimono\"],\n          char: \"🎎\",\n        },\n        wind_chime: {\n          keywords: [\"nature\", \"ding\", \"spring\", \"bell\"],\n          char: \"🎐\",\n        },\n        crossed_flags: {\n          keywords: [\"japanese\", \"nation\", \"country\", \"border\"],\n          char: \"🎌\",\n        },\n        izakaya_lantern: {\n          keywords: [\"light\", \"paper\", \"halloween\", \"spooky\"],\n          char: \"🏮\",\n        },\n        red_envelope: {\n          keywords: [\"gift\"],\n          char: \"🧧\",\n        },\n        email: {\n          keywords: [\"letter\", \"postal\", \"inbox\", \"communication\"],\n          char: \"✉️\",\n        },\n        envelope_with_arrow: {\n          keywords: [\"email\", \"communication\"],\n          char: \"📩\",\n        },\n        incoming_envelope: {\n          keywords: [\"email\", \"inbox\"],\n          char: \"📨\",\n        },\n        \"e-mail\": {\n          keywords: [\"communication\", \"inbox\"],\n          char: \"📧\",\n        },\n        love_letter: {\n          keywords: [\"email\", \"like\", \"affection\", \"envelope\", \"valentines\"],\n          char: \"💌\",\n        },\n        postbox: {\n          keywords: [\"email\", \"letter\", \"envelope\"],\n          char: \"📮\",\n        },\n        mailbox_closed: {\n          keywords: [\"email\", \"communication\", \"inbox\"],\n          char: \"📪\",\n        },\n        mailbox: {\n          keywords: [\"email\", \"inbox\", \"communication\"],\n          char: \"📫\",\n        },\n        mailbox_with_mail: {\n          keywords: [\"email\", \"inbox\", \"communication\"],\n          char: \"📬\",\n        },\n        mailbox_with_no_mail: {\n          keywords: [\"email\", \"inbox\"],\n          char: \"📭\",\n        },\n        package: {\n          keywords: [\"mail\", \"gift\", \"cardboard\", \"box\", \"moving\"],\n          char: \"📦\",\n        },\n        postal_horn: {\n          keywords: [\"instrument\", \"music\"],\n          char: \"📯\",\n        },\n        inbox_tray: {\n          keywords: [\"email\", \"documents\"],\n          char: \"📥\",\n        },\n        outbox_tray: {\n          keywords: [\"inbox\", \"email\"],\n          char: \"📤\",\n        },\n        scroll: {\n          keywords: [\"documents\", \"ancient\", \"history\", \"paper\"],\n          char: \"📜\",\n        },\n        page_with_curl: {\n          keywords: [\"documents\", \"office\", \"paper\"],\n          char: \"📃\",\n        },\n        bookmark_tabs: {\n          keywords: [\"favorite\", \"save\", \"order\", \"tidy\"],\n          char: \"📑\",\n        },\n        receipt: {\n          keywords: [\"accounting\", \"expenses\"],\n          char: \"🧾\",\n        },\n        bar_chart: {\n          keywords: [\"graph\", \"presentation\", \"stats\"],\n          char: \"📊\",\n        },\n        chart_with_upwards_trend: {\n          keywords: [\n            \"graph\",\n            \"presentation\",\n            \"stats\",\n            \"recovery\",\n            \"business\",\n            \"economics\",\n            \"money\",\n            \"sales\",\n            \"good\",\n            \"success\",\n          ],\n          char: \"📈\",\n        },\n        chart_with_downwards_trend: {\n          keywords: [\n            \"graph\",\n            \"presentation\",\n            \"stats\",\n            \"recession\",\n            \"business\",\n            \"economics\",\n            \"money\",\n            \"sales\",\n            \"bad\",\n            \"failure\",\n          ],\n          char: \"📉\",\n        },\n        page_facing_up: {\n          keywords: [\"documents\", \"office\", \"paper\", \"information\"],\n          char: \"📄\",\n        },\n        date: {\n          keywords: [\"calendar\", \"schedule\"],\n          char: \"📅\",\n        },\n        calendar: {\n          keywords: [\"schedule\", \"date\", \"planning\"],\n          char: \"📆\",\n        },\n        spiral_calendar: {\n          keywords: [\"date\", \"schedule\", \"planning\"],\n          char: \"🗓\",\n        },\n        card_index: {\n          keywords: [\"business\", \"stationery\"],\n          char: \"📇\",\n        },\n        card_file_box: {\n          keywords: [\"business\", \"stationery\"],\n          char: \"🗃\",\n        },\n        ballot_box: {\n          keywords: [\"election\", \"vote\"],\n          char: \"🗳\",\n        },\n        file_cabinet: {\n          keywords: [\"filing\", \"organizing\"],\n          char: \"🗄\",\n        },\n        clipboard: {\n          keywords: [\"stationery\", \"documents\"],\n          char: \"📋\",\n        },\n        spiral_notepad: {\n          keywords: [\"memo\", \"stationery\"],\n          char: \"🗒\",\n        },\n        file_folder: {\n          keywords: [\"documents\", \"business\", \"office\"],\n          char: \"📁\",\n        },\n        open_file_folder: {\n          keywords: [\"documents\", \"load\"],\n          char: \"📂\",\n        },\n        card_index_dividers: {\n          keywords: [\"organizing\", \"business\", \"stationery\"],\n          char: \"🗂\",\n        },\n        newspaper_roll: {\n          keywords: [\"press\", \"headline\"],\n          char: \"🗞\",\n        },\n        newspaper: {\n          keywords: [\"press\", \"headline\"],\n          char: \"📰\",\n        },\n        notebook: {\n          keywords: [\"stationery\", \"record\", \"notes\", \"paper\", \"study\"],\n          char: \"📓\",\n        },\n        closed_book: {\n          keywords: [\"read\", \"library\", \"knowledge\", \"textbook\", \"learn\"],\n          char: \"📕\",\n        },\n        green_book: {\n          keywords: [\"read\", \"library\", \"knowledge\", \"study\"],\n          char: \"📗\",\n        },\n        blue_book: {\n          keywords: [\"read\", \"library\", \"knowledge\", \"learn\", \"study\"],\n          char: \"📘\",\n        },\n        orange_book: {\n          keywords: [\"read\", \"library\", \"knowledge\", \"textbook\", \"study\"],\n          char: \"📙\",\n        },\n        notebook_with_decorative_cover: {\n          keywords: [\"classroom\", \"notes\", \"record\", \"paper\", \"study\"],\n          char: \"📔\",\n        },\n        ledger: {\n          keywords: [\"notes\", \"paper\"],\n          char: \"📒\",\n        },\n        books: {\n          keywords: [\"literature\", \"library\", \"study\"],\n          char: \"📚\",\n        },\n        open_book: {\n          keywords: [\"book\", \"read\", \"library\", \"knowledge\", \"literature\", \"learn\", \"study\"],\n          char: \"📖\",\n        },\n        safety_pin: {\n          keywords: [\"diaper\"],\n          char: \"🧷\",\n        },\n        link: {\n          keywords: [\"rings\", \"url\"],\n          char: \"🔗\",\n        },\n        paperclip: {\n          keywords: [\"documents\", \"stationery\"],\n          char: \"📎\",\n        },\n        paperclips: {\n          keywords: [\"documents\", \"stationery\"],\n          char: \"🖇\",\n        },\n        scissors: {\n          keywords: [\"stationery\", \"cut\"],\n          char: \"✂️\",\n        },\n        triangular_ruler: {\n          keywords: [\"stationery\", \"math\", \"architect\", \"sketch\"],\n          char: \"📐\",\n        },\n        straight_ruler: {\n          keywords: [\n            \"stationery\",\n            \"calculate\",\n            \"length\",\n            \"math\",\n            \"school\",\n            \"drawing\",\n            \"architect\",\n            \"sketch\",\n          ],\n          char: \"📏\",\n        },\n        abacus: {\n          keywords: [\"calculation\"],\n          char: \"🧮\",\n        },\n        pushpin: {\n          keywords: [\"stationery\", \"mark\", \"here\"],\n          char: \"📌\",\n        },\n        round_pushpin: {\n          keywords: [\"stationery\", \"location\", \"map\", \"here\"],\n          char: \"📍\",\n        },\n        triangular_flag_on_post: {\n          keywords: [\"mark\", \"milestone\", \"place\"],\n          char: \"🚩\",\n        },\n        white_flag: {\n          keywords: [\"losing\", \"loser\", \"lost\", \"surrender\", \"give up\", \"fail\"],\n          char: \"🏳\",\n        },\n        black_flag: {\n          keywords: [\"pirate\"],\n          char: \"🏴\",\n        },\n        rainbow_flag: {\n          keywords: [\n            \"flag\",\n            \"rainbow\",\n            \"pride\",\n            \"gay\",\n            \"lgbt\",\n            \"glbt\",\n            \"queer\",\n            \"homosexual\",\n            \"lesbian\",\n            \"bisexual\",\n            \"transgender\",\n          ],\n          char: \"🏳️‍🌈\",\n        },\n        closed_lock_with_key: {\n          keywords: [\"security\", \"privacy\"],\n          char: \"🔐\",\n        },\n        lock: {\n          keywords: [\"security\", \"password\", \"padlock\"],\n          char: \"🔒\",\n        },\n        unlock: {\n          keywords: [\"privacy\", \"security\"],\n          char: \"🔓\",\n        },\n        lock_with_ink_pen: {\n          keywords: [\"security\", \"secret\"],\n          char: \"🔏\",\n        },\n        pen: {\n          keywords: [\"stationery\", \"writing\", \"write\"],\n          char: \"🖊\",\n        },\n        fountain_pen: {\n          keywords: [\"stationery\", \"writing\", \"write\"],\n          char: \"🖋\",\n        },\n        black_nib: {\n          keywords: [\"pen\", \"stationery\", \"writing\", \"write\"],\n          char: \"✒️\",\n        },\n        memo: {\n          keywords: [\n            \"write\",\n            \"documents\",\n            \"stationery\",\n            \"pencil\",\n            \"paper\",\n            \"writing\",\n            \"legal\",\n            \"exam\",\n            \"quiz\",\n            \"test\",\n            \"study\",\n            \"compose\",\n          ],\n          char: \"📝\",\n        },\n        pencil2: {\n          keywords: [\"stationery\", \"write\", \"paper\", \"writing\", \"school\", \"study\"],\n          char: \"✏️\",\n        },\n        crayon: {\n          keywords: [\"drawing\", \"creativity\"],\n          char: \"🖍\",\n        },\n        paintbrush: {\n          keywords: [\"drawing\", \"creativity\", \"art\"],\n          char: \"🖌\",\n        },\n        mag: {\n          keywords: [\"search\", \"zoom\", \"find\", \"detective\"],\n          char: \"🔍\",\n        },\n        mag_right: {\n          keywords: [\"search\", \"zoom\", \"find\", \"detective\"],\n          char: \"🔎\",\n        },\n      },\n    },\n  },\n\n  {\n    symbols: {\n      id: \"symbols\",\n      name: t(\"SYMBOLS\"),\n      symbol: \"Symbols\",\n      emojis: {\n        heart: {\n          keywords: [\"love\", \"like\", \"valentines\"],\n          char: \"❤️\",\n        },\n        orange_heart: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"🧡\",\n        },\n        yellow_heart: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💛\",\n        },\n        green_heart: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💚\",\n        },\n        blue_heart: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💙\",\n        },\n        purple_heart: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💜\",\n        },\n        black_heart: {\n          keywords: [\"evil\"],\n          char: \"🖤\",\n        },\n        broken_heart: {\n          keywords: [\"sad\", \"sorry\", \"break\", \"heart\", \"heartbreak\"],\n          char: \"💔\",\n        },\n        heavy_heart_exclamation: {\n          keywords: [\"decoration\", \"love\"],\n          char: \"❣\",\n        },\n        two_hearts: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\", \"heart\"],\n          char: \"💕\",\n        },\n        revolving_hearts: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💞\",\n        },\n        heartbeat: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\", \"pink\", \"heart\"],\n          char: \"💓\",\n        },\n        heartpulse: {\n          keywords: [\"like\", \"love\", \"affection\", \"valentines\", \"pink\"],\n          char: \"💗\",\n        },\n        sparkling_heart: {\n          keywords: [\"love\", \"like\", \"affection\", \"valentines\"],\n          char: \"💖\",\n        },\n        cupid: {\n          keywords: [\"love\", \"like\", \"heart\", \"affection\", \"valentines\"],\n          char: \"💘\",\n        },\n        gift_heart: {\n          keywords: [\"love\", \"valentines\"],\n          char: \"💝\",\n        },\n        heart_decoration: {\n          keywords: [\"purple-square\", \"love\", \"like\"],\n          char: \"💟\",\n        },\n        peace_symbol: {\n          keywords: [\"hippie\"],\n          char: \"☮\",\n        },\n        latin_cross: {\n          keywords: [\"christianity\"],\n          char: \"✝\",\n        },\n        star_and_crescent: {\n          keywords: [\"islam\"],\n          char: \"☪\",\n        },\n        om: {\n          keywords: [\"hinduism\", \"buddhism\", \"sikhism\", \"jainism\"],\n          char: \"🕉\",\n        },\n        wheel_of_dharma: {\n          keywords: [\"hinduism\", \"buddhism\", \"sikhism\", \"jainism\"],\n          char: \"☸\",\n        },\n        star_of_david: {\n          keywords: [\"judaism\"],\n          char: \"✡\",\n        },\n        six_pointed_star: {\n          keywords: [\"purple-square\", \"religion\", \"jewish\", \"hexagram\"],\n          char: \"🔯\",\n        },\n        menorah: {\n          keywords: [\"hanukkah\", \"candles\", \"jewish\"],\n          char: \"🕎\",\n        },\n        yin_yang: {\n          keywords: [\"balance\"],\n          char: \"☯\",\n        },\n        orthodox_cross: {\n          keywords: [\"suppedaneum\", \"religion\"],\n          char: \"☦\",\n        },\n        place_of_worship: {\n          keywords: [\"religion\", \"church\", \"temple\", \"prayer\"],\n          char: \"🛐\",\n        },\n        ophiuchus: {\n          keywords: [\"sign\", \"purple-square\", \"constellation\", \"astrology\"],\n          char: \"⛎\",\n        },\n        aries: {\n          keywords: [\"sign\", \"purple-square\", \"zodiac\", \"astrology\"],\n          char: \"♈\",\n        },\n        taurus: {\n          keywords: [\"purple-square\", \"sign\", \"zodiac\", \"astrology\"],\n          char: \"♉\",\n        },\n        gemini: {\n          keywords: [\"sign\", \"zodiac\", \"purple-square\", \"astrology\"],\n          char: \"♊\",\n        },\n        cancer: {\n          keywords: [\"sign\", \"zodiac\", \"purple-square\", \"astrology\"],\n          char: \"♋\",\n        },\n        leo: {\n          keywords: [\"sign\", \"purple-square\", \"zodiac\", \"astrology\"],\n          char: \"♌\",\n        },\n        virgo: {\n          keywords: [\"sign\", \"zodiac\", \"purple-square\", \"astrology\"],\n          char: \"♍\",\n        },\n        libra: {\n          keywords: [\"sign\", \"purple-square\", \"zodiac\", \"astrology\"],\n          char: \"♎\",\n        },\n        scorpius: {\n          keywords: [\"sign\", \"zodiac\", \"purple-square\", \"astrology\", \"scorpio\"],\n          char: \"♏\",\n        },\n        sagittarius: {\n          keywords: [\"sign\", \"zodiac\", \"purple-square\", \"astrology\"],\n          char: \"♐\",\n        },\n        capricorn: {\n          keywords: [\"sign\", \"zodiac\", \"purple-square\", \"astrology\"],\n          char: \"♑\",\n        },\n        aquarius: {\n          keywords: [\"sign\", \"purple-square\", \"zodiac\", \"astrology\"],\n          char: \"♒\",\n        },\n        pisces: {\n          keywords: [\"purple-square\", \"sign\", \"zodiac\", \"astrology\"],\n          char: \"♓\",\n        },\n        id: {\n          keywords: [\"purple-square\", \"words\"],\n          char: \"🆔\",\n        },\n        atom_symbol: {\n          keywords: [\"science\", \"physics\", \"chemistry\"],\n          char: \"⚛\",\n        },\n        u7a7a: {\n          keywords: [\"kanji\", \"japanese\", \"chinese\", \"empty\", \"sky\", \"blue-square\"],\n          char: \"🈳\",\n        },\n        u5272: {\n          keywords: [\"cut\", \"divide\", \"chinese\", \"kanji\", \"pink-square\"],\n          char: \"🈹\",\n        },\n        radioactive: {\n          keywords: [\"nuclear\", \"danger\"],\n          char: \"☢\",\n        },\n        biohazard: {\n          keywords: [\"danger\"],\n          char: \"☣\",\n        },\n        mobile_phone_off: {\n          keywords: [\"mute\", \"orange-square\", \"silence\", \"quiet\"],\n          char: \"📴\",\n        },\n        vibration_mode: {\n          keywords: [\"orange-square\", \"phone\"],\n          char: \"📳\",\n        },\n        u6709: {\n          keywords: [\"orange-square\", \"chinese\", \"have\", \"kanji\"],\n          char: \"🈶\",\n        },\n        u7121: {\n          keywords: [\"nothing\", \"chinese\", \"kanji\", \"japanese\", \"orange-square\"],\n          char: \"🈚\",\n        },\n        u7533: {\n          keywords: [\"chinese\", \"japanese\", \"kanji\", \"orange-square\"],\n          char: \"🈸\",\n        },\n        u55b6: {\n          keywords: [\"japanese\", \"opening hours\", \"orange-square\"],\n          char: \"🈺\",\n        },\n        u6708: {\n          keywords: [\"chinese\", \"month\", \"moon\", \"japanese\", \"orange-square\", \"kanji\"],\n          char: \"🈷️\",\n        },\n        eight_pointed_black_star: {\n          keywords: [\"orange-square\", \"shape\", \"polygon\"],\n          char: \"✴️\",\n        },\n        vs: {\n          keywords: [\"words\", \"orange-square\"],\n          char: \"🆚\",\n        },\n        accept: {\n          keywords: [\"ok\", \"good\", \"chinese\", \"kanji\", \"agree\", \"yes\", \"orange-circle\"],\n          char: \"🉑\",\n        },\n        white_flower: {\n          keywords: [\"japanese\", \"spring\"],\n          char: \"💮\",\n        },\n        ideograph_advantage: {\n          keywords: [\"chinese\", \"kanji\", \"obtain\", \"get\", \"circle\"],\n          char: \"🉐\",\n        },\n        secret: {\n          keywords: [\"privacy\", \"chinese\", \"sshh\", \"kanji\", \"red-circle\"],\n          char: \"㊙️\",\n        },\n        congratulations: {\n          keywords: [\"chinese\", \"kanji\", \"japanese\", \"red-circle\"],\n          char: \"㊗️\",\n        },\n        u5408: {\n          keywords: [\"japanese\", \"chinese\", \"join\", \"kanji\", \"red-square\"],\n          char: \"🈴\",\n        },\n        u6e80: {\n          keywords: [\"full\", \"chinese\", \"japanese\", \"red-square\", \"kanji\"],\n          char: \"🈵\",\n        },\n        u7981: {\n          keywords: [\n            \"kanji\",\n            \"japanese\",\n            \"chinese\",\n            \"forbidden\",\n            \"limit\",\n            \"restricted\",\n            \"red-square\",\n          ],\n          char: \"🈲\",\n        },\n        a: {\n          keywords: [\"red-square\", \"alphabet\", \"letter\"],\n          char: \"🅰️\",\n        },\n        b: {\n          keywords: [\"red-square\", \"alphabet\", \"letter\"],\n          char: \"🅱️\",\n        },\n        ab: {\n          keywords: [\"red-square\", \"alphabet\"],\n          char: \"🆎\",\n        },\n        cl: {\n          keywords: [\"alphabet\", \"words\", \"red-square\"],\n          char: \"🆑\",\n        },\n        o2: {\n          keywords: [\"alphabet\", \"red-square\", \"letter\"],\n          char: \"🅾️\",\n        },\n        sos: {\n          keywords: [\"help\", \"red-square\", \"words\", \"emergency\", \"911\"],\n          char: \"🆘\",\n        },\n        no_entry: {\n          keywords: [\"limit\", \"security\", \"privacy\", \"bad\", \"denied\", \"stop\", \"circle\"],\n          char: \"⛔\",\n        },\n        name_badge: {\n          keywords: [\"fire\", \"forbid\"],\n          char: \"📛\",\n        },\n        no_entry_sign: {\n          keywords: [\"forbid\", \"stop\", \"limit\", \"denied\", \"disallow\", \"circle\"],\n          char: \"🚫\",\n        },\n        x: {\n          keywords: [\"no\", \"delete\", \"remove\", \"cancel\", \"red\"],\n          char: \"❌\",\n        },\n        o: {\n          keywords: [\"circle\", \"round\"],\n          char: \"⭕\",\n        },\n        stop_sign: {\n          keywords: [\"stop\"],\n          char: \"🛑\",\n        },\n        anger: {\n          keywords: [\"angry\", \"mad\"],\n          char: \"💢\",\n        },\n        hotsprings: {\n          keywords: [\"bath\", \"warm\", \"relax\"],\n          char: \"♨️\",\n        },\n        no_pedestrians: {\n          keywords: [\"rules\", \"crossing\", \"walking\", \"circle\"],\n          char: \"🚷\",\n        },\n        do_not_litter: {\n          keywords: [\"trash\", \"bin\", \"garbage\", \"circle\"],\n          char: \"🚯\",\n        },\n        no_bicycles: {\n          keywords: [\"cyclist\", \"prohibited\", \"circle\"],\n          char: \"🚳\",\n        },\n        \"non-potable_water\": {\n          keywords: [\"drink\", \"faucet\", \"tap\", \"circle\"],\n          char: \"🚱\",\n        },\n        underage: {\n          keywords: [\"18\", \"drink\", \"pub\", \"night\", \"minor\", \"circle\"],\n          char: \"🔞\",\n        },\n        no_mobile_phones: {\n          keywords: [\"iphone\", \"mute\", \"circle\"],\n          char: \"📵\",\n        },\n        exclamation: {\n          keywords: [\n            \"heavy_exclamation_mark\",\n            \"danger\",\n            \"surprise\",\n            \"punctuation\",\n            \"wow\",\n            \"warning\",\n          ],\n          char: \"❗\",\n        },\n        grey_exclamation: {\n          keywords: [\"surprise\", \"punctuation\", \"gray\", \"wow\", \"warning\"],\n          char: \"❕\",\n        },\n        question: {\n          keywords: [\"doubt\", \"confused\"],\n          char: \"❓\",\n        },\n        grey_question: {\n          keywords: [\"doubts\", \"gray\", \"huh\", \"confused\"],\n          char: \"❔\",\n        },\n        bangbang: {\n          keywords: [\"exclamation\", \"surprise\"],\n          char: \"‼️\",\n        },\n        interrobang: {\n          keywords: [\"wat\", \"punctuation\", \"surprise\"],\n          char: \"⁉️\",\n        },\n        100: {\n          keywords: [\n            \"score\",\n            \"perfect\",\n            \"numbers\",\n            \"century\",\n            \"exam\",\n            \"quiz\",\n            \"test\",\n            \"pass\",\n            \"hundred\",\n          ],\n          char: \"💯\",\n        },\n        low_brightness: {\n          keywords: [\"sun\", \"afternoon\", \"warm\", \"summer\"],\n          char: \"🔅\",\n        },\n        high_brightness: {\n          keywords: [\"sun\", \"light\"],\n          char: \"🔆\",\n        },\n        trident: {\n          keywords: [\"weapon\", \"spear\"],\n          char: \"🔱\",\n        },\n        fleur_de_lis: {\n          keywords: [\"decorative\", \"scout\"],\n          char: \"⚜\",\n        },\n        part_alternation_mark: {\n          keywords: [\"graph\", \"presentation\", \"stats\", \"business\", \"economics\", \"bad\"],\n          char: \"〽️\",\n        },\n        warning: {\n          keywords: [\"exclamation\", \"wip\", \"alert\", \"error\", \"problem\", \"issue\"],\n          char: \"⚠️\",\n        },\n        children_crossing: {\n          keywords: [\"school\", \"warning\", \"danger\", \"sign\", \"driving\", \"yellow-diamond\"],\n          char: \"🚸\",\n        },\n        beginner: {\n          keywords: [\"badge\", \"shield\"],\n          char: \"🔰\",\n        },\n        recycle: {\n          keywords: [\"arrow\", \"environment\", \"garbage\", \"trash\"],\n          char: \"♻️\",\n        },\n        u6307: {\n          keywords: [\"chinese\", \"point\", \"green-square\", \"kanji\"],\n          char: \"🈯\",\n        },\n        chart: {\n          keywords: [\"green-square\", \"graph\", \"presentation\", \"stats\"],\n          char: \"💹\",\n        },\n        sparkle: {\n          keywords: [\"stars\", \"green-square\", \"awesome\", \"good\", \"fireworks\"],\n          char: \"❇️\",\n        },\n        eight_spoked_asterisk: {\n          keywords: [\"star\", \"sparkle\", \"green-square\"],\n          char: \"✳️\",\n        },\n        negative_squared_cross_mark: {\n          keywords: [\"x\", \"green-square\", \"no\", \"deny\"],\n          char: \"❎\",\n        },\n        white_check_mark: {\n          keywords: [\"green-square\", \"ok\", \"agree\", \"vote\", \"election\", \"answer\", \"tick\"],\n          char: \"✅\",\n        },\n        diamond_shape_with_a_dot_inside: {\n          keywords: [\"jewel\", \"blue\", \"gem\", \"crystal\", \"fancy\"],\n          char: \"💠\",\n        },\n        cyclone: {\n          keywords: [\n            \"weather\",\n            \"swirl\",\n            \"blue\",\n            \"cloud\",\n            \"vortex\",\n            \"spiral\",\n            \"whirlpool\",\n            \"spin\",\n            \"tornado\",\n            \"hurricane\",\n            \"typhoon\",\n          ],\n          char: \"🌀\",\n        },\n        loop: {\n          keywords: [\"tape\", \"cassette\"],\n          char: \"➿\",\n        },\n        globe_with_meridians: {\n          keywords: [\"earth\", \"international\", \"world\", \"internet\", \"interweb\", \"i18n\"],\n          char: \"🌐\",\n        },\n        m: {\n          keywords: [\"alphabet\", \"blue-circle\", \"letter\"],\n          char: \"Ⓜ️\",\n        },\n        atm: {\n          keywords: [\"money\", \"sales\", \"cash\", \"blue-square\", \"payment\", \"bank\"],\n          char: \"🏧\",\n        },\n        sa: {\n          keywords: [\"japanese\", \"blue-square\", \"katakana\"],\n          char: \"🈂️\",\n        },\n        passport_control: {\n          keywords: [\"custom\", \"blue-square\"],\n          char: \"🛂\",\n        },\n        customs: {\n          keywords: [\"passport\", \"border\", \"blue-square\"],\n          char: \"🛃\",\n        },\n        baggage_claim: {\n          keywords: [\"blue-square\", \"airport\", \"transport\"],\n          char: \"🛄\",\n        },\n        left_luggage: {\n          keywords: [\"blue-square\", \"travel\"],\n          char: \"🛅\",\n        },\n        wheelchair: {\n          keywords: [\"blue-square\", \"disabled\", \"a11y\", \"accessibility\"],\n          char: \"♿\",\n        },\n        no_smoking: {\n          keywords: [\"cigarette\", \"blue-square\", \"smell\", \"smoke\"],\n          char: \"🚭\",\n        },\n        wc: {\n          keywords: [\"toilet\", \"restroom\", \"blue-square\"],\n          char: \"🚾\",\n        },\n        parking: {\n          keywords: [\"cars\", \"blue-square\", \"alphabet\", \"letter\"],\n          char: \"🅿️\",\n        },\n        potable_water: {\n          keywords: [\"blue-square\", \"liquid\", \"restroom\", \"cleaning\", \"faucet\"],\n          char: \"🚰\",\n        },\n        mens: {\n          keywords: [\"toilet\", \"restroom\", \"wc\", \"blue-square\", \"gender\", \"male\"],\n          char: \"🚹\",\n        },\n        womens: {\n          keywords: [\"purple-square\", \"woman\", \"female\", \"toilet\", \"loo\", \"restroom\", \"gender\"],\n          char: \"🚺\",\n        },\n        baby_symbol: {\n          keywords: [\"orange-square\", \"child\"],\n          char: \"🚼\",\n        },\n        restroom: {\n          keywords: [\"blue-square\", \"toilet\", \"refresh\", \"wc\", \"gender\"],\n          char: \"🚻\",\n        },\n        put_litter_in_its_place: {\n          keywords: [\"blue-square\", \"sign\", \"human\", \"info\"],\n          char: \"🚮\",\n        },\n        cinema: {\n          keywords: [\"blue-square\", \"record\", \"film\", \"movie\", \"curtain\", \"stage\", \"theater\"],\n          char: \"🎦\",\n        },\n        signal_strength: {\n          keywords: [\n            \"blue-square\",\n            \"reception\",\n            \"phone\",\n            \"internet\",\n            \"connection\",\n            \"wifi\",\n            \"bluetooth\",\n            \"bars\",\n          ],\n          char: \"📶\",\n        },\n        koko: {\n          keywords: [\"blue-square\", \"here\", \"katakana\", \"japanese\", \"destination\"],\n          char: \"🈁\",\n        },\n        ng: {\n          keywords: [\"blue-square\", \"words\", \"shape\", \"icon\"],\n          char: \"🆖\",\n        },\n        ok: {\n          keywords: [\"good\", \"agree\", \"yes\", \"blue-square\"],\n          char: \"🆗\",\n        },\n        up: {\n          keywords: [\"blue-square\", \"above\", \"high\"],\n          char: \"🆙\",\n        },\n        cool: {\n          keywords: [\"words\", \"blue-square\"],\n          char: \"🆒\",\n        },\n        new: {\n          keywords: [\"blue-square\", \"words\", \"start\"],\n          char: \"🆕\",\n        },\n        free: {\n          keywords: [\"blue-square\", \"words\"],\n          char: \"🆓\",\n        },\n        zero: {\n          keywords: [\"0\", \"numbers\", \"blue-square\", \"null\"],\n          char: \"0️⃣\",\n        },\n        one: {\n          keywords: [\"blue-square\", \"numbers\", \"1\"],\n          char: \"1️⃣\",\n        },\n        two: {\n          keywords: [\"numbers\", \"2\", \"prime\", \"blue-square\"],\n          char: \"2️⃣\",\n        },\n        three: {\n          keywords: [\"3\", \"numbers\", \"prime\", \"blue-square\"],\n          char: \"3️⃣\",\n        },\n        four: {\n          keywords: [\"4\", \"numbers\", \"blue-square\"],\n          char: \"4️⃣\",\n        },\n        five: {\n          keywords: [\"5\", \"numbers\", \"blue-square\", \"prime\"],\n          char: \"5️⃣\",\n        },\n        six: {\n          keywords: [\"6\", \"numbers\", \"blue-square\"],\n          char: \"6️⃣\",\n        },\n        seven: {\n          keywords: [\"7\", \"numbers\", \"blue-square\", \"prime\"],\n          char: \"7️⃣\",\n        },\n        eight: {\n          keywords: [\"8\", \"blue-square\", \"numbers\"],\n          char: \"8️⃣\",\n        },\n        nine: {\n          keywords: [\"blue-square\", \"numbers\", \"9\"],\n          char: \"9️⃣\",\n        },\n        keycap_ten: {\n          keywords: [\"numbers\", \"10\", \"blue-square\"],\n          char: \"🔟\",\n        },\n        asterisk: {\n          keywords: [\"star\", \"keycap\"],\n          char: \"*⃣\",\n        },\n        1234: {\n          keywords: [\"numbers\", \"blue-square\"],\n          char: \"🔢\",\n        },\n        eject_button: {\n          keywords: [\"blue-square\"],\n          char: \"⏏️\",\n        },\n        arrow_forward: {\n          keywords: [\"blue-square\", \"right\", \"direction\", \"play\"],\n          char: \"▶️\",\n        },\n        pause_button: {\n          keywords: [\"pause\", \"blue-square\"],\n          char: \"⏸\",\n        },\n        next_track_button: {\n          keywords: [\"forward\", \"next\", \"blue-square\"],\n          char: \"⏭\",\n        },\n        stop_button: {\n          keywords: [\"blue-square\"],\n          char: \"⏹\",\n        },\n        record_button: {\n          keywords: [\"blue-square\"],\n          char: \"⏺\",\n        },\n        play_or_pause_button: {\n          keywords: [\"blue-square\", \"play\", \"pause\"],\n          char: \"⏯\",\n        },\n        previous_track_button: {\n          keywords: [\"backward\"],\n          char: \"⏮\",\n        },\n        fast_forward: {\n          keywords: [\"blue-square\", \"play\", \"speed\", \"continue\"],\n          char: \"⏩\",\n        },\n        rewind: {\n          keywords: [\"play\", \"blue-square\"],\n          char: \"⏪\",\n        },\n        twisted_rightwards_arrows: {\n          keywords: [\"blue-square\", \"shuffle\", \"music\", \"random\"],\n          char: \"🔀\",\n        },\n        repeat: {\n          keywords: [\"loop\", \"record\"],\n          char: \"🔁\",\n        },\n        repeat_one: {\n          keywords: [\"blue-square\", \"loop\"],\n          char: \"🔂\",\n        },\n        arrow_backward: {\n          keywords: [\"blue-square\", \"left\", \"direction\"],\n          char: \"◀️\",\n        },\n        arrow_up_small: {\n          keywords: [\"blue-square\", \"triangle\", \"direction\", \"point\", \"forward\", \"top\"],\n          char: \"🔼\",\n        },\n        arrow_down_small: {\n          keywords: [\"blue-square\", \"direction\", \"bottom\"],\n          char: \"🔽\",\n        },\n        arrow_double_up: {\n          keywords: [\"blue-square\", \"direction\", \"top\"],\n          char: \"⏫\",\n        },\n        arrow_double_down: {\n          keywords: [\"blue-square\", \"direction\", \"bottom\"],\n          char: \"⏬\",\n        },\n        arrow_right: {\n          keywords: [\"blue-square\", \"next\"],\n          char: \"➡️\",\n        },\n        arrow_left: {\n          keywords: [\"blue-square\", \"previous\", \"back\"],\n          char: \"⬅️\",\n        },\n        arrow_up: {\n          keywords: [\"blue-square\", \"continue\", \"top\", \"direction\"],\n          char: \"⬆️\",\n        },\n        arrow_down: {\n          keywords: [\"blue-square\", \"direction\", \"bottom\"],\n          char: \"⬇️\",\n        },\n        arrow_upper_right: {\n          keywords: [\"blue-square\", \"point\", \"direction\", \"diagonal\", \"northeast\"],\n          char: \"↗️\",\n        },\n        arrow_lower_right: {\n          keywords: [\"blue-square\", \"direction\", \"diagonal\", \"southeast\"],\n          char: \"↘️\",\n        },\n        arrow_lower_left: {\n          keywords: [\"blue-square\", \"direction\", \"diagonal\", \"southwest\"],\n          char: \"↙️\",\n        },\n        arrow_upper_left: {\n          keywords: [\"blue-square\", \"point\", \"direction\", \"diagonal\", \"northwest\"],\n          char: \"↖️\",\n        },\n        arrow_up_down: {\n          keywords: [\"blue-square\", \"direction\", \"way\", \"vertical\"],\n          char: \"↕️\",\n        },\n        left_right_arrow: {\n          keywords: [\"shape\", \"direction\", \"horizontal\", \"sideways\"],\n          char: \"↔️\",\n        },\n        arrows_counterclockwise: {\n          keywords: [\"blue-square\", \"sync\", \"cycle\"],\n          char: \"🔄\",\n        },\n        arrow_right_hook: {\n          keywords: [\"blue-square\", \"return\", \"rotate\", \"direction\"],\n          char: \"↪️\",\n        },\n        leftwards_arrow_with_hook: {\n          keywords: [\"back\", \"return\", \"blue-square\", \"undo\", \"enter\"],\n          char: \"↩️\",\n        },\n        arrow_heading_up: {\n          keywords: [\"blue-square\", \"direction\", \"top\"],\n          char: \"⤴️\",\n        },\n        arrow_heading_down: {\n          keywords: [\"blue-square\", \"direction\", \"bottom\"],\n          char: \"⤵️\",\n        },\n        hash: {\n          keywords: [\"symbol\", \"blue-square\", \"twitter\"],\n          char: \"#️⃣\",\n        },\n        information_source: {\n          keywords: [\"blue-square\", \"alphabet\", \"letter\"],\n          char: \"ℹ️\",\n        },\n        abc: {\n          keywords: [\"blue-square\", \"alphabet\"],\n          char: \"🔤\",\n        },\n        abcd: {\n          keywords: [\"blue-square\", \"alphabet\"],\n          char: \"🔡\",\n        },\n        capital_abcd: {\n          keywords: [\"alphabet\", \"words\", \"blue-square\"],\n          char: \"🔠\",\n        },\n        symbols: {\n          keywords: [\n            \"blue-square\",\n            \"music\",\n            \"note\",\n            \"ampersand\",\n            \"percent\",\n            \"glyphs\",\n            \"characters\",\n          ],\n          char: \"🔣\",\n        },\n        musical_note: {\n          keywords: [\"score\", \"tone\", \"sound\"],\n          char: \"🎵\",\n        },\n        notes: {\n          keywords: [\"music\", \"score\"],\n          char: \"🎶\",\n        },\n        wavy_dash: {\n          keywords: [\"draw\", \"line\", \"moustache\", \"mustache\", \"squiggle\", \"scribble\"],\n          char: \"〰️\",\n        },\n        curly_loop: {\n          keywords: [\"scribble\", \"draw\", \"shape\", \"squiggle\"],\n          char: \"➰\",\n        },\n        heavy_check_mark: {\n          keywords: [\"ok\", \"nike\", \"answer\", \"yes\", \"tick\"],\n          char: \"✔️\",\n        },\n        arrows_clockwise: {\n          keywords: [\"sync\", \"cycle\", \"round\", \"repeat\"],\n          char: \"🔃\",\n        },\n        heavy_plus_sign: {\n          keywords: [\"math\", \"calculation\", \"addition\", \"more\", \"increase\"],\n          char: \"➕\",\n        },\n        heavy_minus_sign: {\n          keywords: [\"math\", \"calculation\", \"subtract\", \"less\"],\n          char: \"➖\",\n        },\n        heavy_division_sign: {\n          keywords: [\"divide\", \"math\", \"calculation\"],\n          char: \"➗\",\n        },\n        heavy_multiplication_x: {\n          keywords: [\"math\", \"calculation\"],\n          char: \"✖️\",\n        },\n        infinity: {\n          keywords: [\"forever\"],\n          char: \"♾\",\n        },\n        heavy_dollar_sign: {\n          keywords: [\"money\", \"sales\", \"payment\", \"currency\", \"buck\"],\n          char: \"💲\",\n        },\n        currency_exchange: {\n          keywords: [\"money\", \"sales\", \"dollar\", \"travel\"],\n          char: \"💱\",\n        },\n        copyright: {\n          keywords: [\"ip\", \"license\", \"circle\", \"law\", \"legal\"],\n          char: \"©️\",\n        },\n        registered: {\n          keywords: [\"alphabet\", \"circle\"],\n          char: \"®️\",\n        },\n        tm: {\n          keywords: [\"trademark\", \"brand\", \"law\", \"legal\"],\n          char: \"™️\",\n        },\n        end: {\n          keywords: [\"words\", \"arrow\"],\n          char: \"🔚\",\n        },\n        back: {\n          keywords: [\"arrow\", \"words\", \"return\"],\n          char: \"🔙\",\n        },\n        on: {\n          keywords: [\"arrow\", \"words\"],\n          char: \"🔛\",\n        },\n        top: {\n          keywords: [\"words\", \"blue-square\"],\n          char: \"🔝\",\n        },\n        soon: {\n          keywords: [\"arrow\", \"words\"],\n          char: \"🔜\",\n        },\n        ballot_box_with_check: {\n          keywords: [\"ok\", \"agree\", \"confirm\", \"black-square\", \"vote\", \"election\", \"yes\", \"tick\"],\n          char: \"☑️\",\n        },\n        radio_button: {\n          keywords: [\"input\", \"old\", \"music\", \"circle\"],\n          char: \"🔘\",\n        },\n        white_circle: {\n          keywords: [\"shape\", \"round\"],\n          char: \"⚪\",\n        },\n        black_circle: {\n          keywords: [\"shape\", \"button\", \"round\"],\n          char: \"⚫\",\n        },\n        red_circle: {\n          keywords: [\"shape\", \"error\", \"danger\"],\n          char: \"🔴\",\n        },\n        large_blue_circle: {\n          keywords: [\"shape\", \"icon\", \"button\"],\n          char: \"🔵\",\n        },\n        small_orange_diamond: {\n          keywords: [\"shape\", \"jewel\", \"gem\"],\n          char: \"🔸\",\n        },\n        small_blue_diamond: {\n          keywords: [\"shape\", \"jewel\", \"gem\"],\n          char: \"🔹\",\n        },\n        large_orange_diamond: {\n          keywords: [\"shape\", \"jewel\", \"gem\"],\n          char: \"🔶\",\n        },\n        large_blue_diamond: {\n          keywords: [\"shape\", \"jewel\", \"gem\"],\n          char: \"🔷\",\n        },\n        small_red_triangle: {\n          keywords: [\"shape\", \"direction\", \"up\", \"top\"],\n          char: \"🔺\",\n        },\n        black_small_square: {\n          keywords: [\"shape\", \"icon\"],\n          char: \"▪️\",\n        },\n        white_small_square: {\n          keywords: [\"shape\", \"icon\"],\n          char: \"▫️\",\n        },\n        black_large_square: {\n          keywords: [\"shape\", \"icon\", \"button\"],\n          char: \"⬛\",\n        },\n        white_large_square: {\n          keywords: [\"shape\", \"icon\", \"stone\", \"button\"],\n          char: \"⬜\",\n        },\n        small_red_triangle_down: {\n          keywords: [\"shape\", \"direction\", \"bottom\"],\n          char: \"🔻\",\n        },\n        black_medium_square: {\n          keywords: [\"shape\", \"button\", \"icon\"],\n          char: \"◼️\",\n        },\n        white_medium_square: {\n          keywords: [\"shape\", \"stone\", \"icon\"],\n          char: \"◻️\",\n        },\n        black_medium_small_square: {\n          keywords: [\"icon\", \"shape\", \"button\"],\n          char: \"◾\",\n        },\n        white_medium_small_square: {\n          keywords: [\"shape\", \"stone\", \"icon\", \"button\"],\n          char: \"◽\",\n        },\n        black_square_button: {\n          keywords: [\"shape\", \"input\", \"frame\"],\n          char: \"🔲\",\n        },\n        white_square_button: {\n          keywords: [\"shape\", \"input\"],\n          char: \"🔳\",\n        },\n        speaker: {\n          keywords: [\"sound\", \"volume\", \"silence\", \"broadcast\"],\n          char: \"🔈\",\n        },\n        sound: {\n          keywords: [\"volume\", \"speaker\", \"broadcast\"],\n          char: \"🔉\",\n        },\n        loud_sound: {\n          keywords: [\"volume\", \"noise\", \"noisy\", \"speaker\", \"broadcast\"],\n          char: \"🔊\",\n        },\n        mute: {\n          keywords: [\"sound\", \"volume\", \"silence\", \"quiet\"],\n          char: \"🔇\",\n        },\n        mega: {\n          keywords: [\"sound\", \"speaker\", \"volume\"],\n          char: \"📣\",\n        },\n        loudspeaker: {\n          keywords: [\"volume\", \"sound\"],\n          char: \"📢\",\n        },\n        bell: {\n          keywords: [\"sound\", \"notification\", \"christmas\", \"xmas\", \"chime\"],\n          char: \"🔔\",\n        },\n        no_bell: {\n          keywords: [\"sound\", \"volume\", \"mute\", \"quiet\", \"silent\"],\n          char: \"🔕\",\n        },\n        black_joker: {\n          keywords: [\"poker\", \"cards\", \"game\", \"play\", \"magic\"],\n          char: \"🃏\",\n        },\n        mahjong: {\n          keywords: [\"game\", \"play\", \"chinese\", \"kanji\"],\n          char: \"🀄\",\n        },\n        spades: {\n          keywords: [\"poker\", \"cards\", \"suits\", \"magic\"],\n          char: \"♠️\",\n        },\n        clubs: {\n          keywords: [\"poker\", \"cards\", \"magic\", \"suits\"],\n          char: \"♣️\",\n        },\n        hearts: {\n          keywords: [\"poker\", \"cards\", \"magic\", \"suits\"],\n          char: \"♥️\",\n        },\n        diamonds: {\n          keywords: [\"poker\", \"cards\", \"magic\", \"suits\"],\n          char: \"♦️\",\n        },\n        flower_playing_cards: {\n          keywords: [\"game\", \"sunset\", \"red\"],\n          char: \"🎴\",\n        },\n        thought_balloon: {\n          keywords: [\"bubble\", \"cloud\", \"speech\", \"thinking\", \"dream\"],\n          char: \"💭\",\n        },\n        right_anger_bubble: {\n          keywords: [\"caption\", \"speech\", \"thinking\", \"mad\"],\n          char: \"🗯\",\n        },\n        speech_balloon: {\n          keywords: [\"bubble\", \"words\", \"message\", \"talk\", \"chatting\"],\n          char: \"💬\",\n        },\n        left_speech_bubble: {\n          keywords: [\"words\", \"message\", \"talk\", \"chatting\"],\n          char: \"🗨\",\n        },\n        clock1: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕐\",\n        },\n        clock2: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕑\",\n        },\n        clock3: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕒\",\n        },\n        clock4: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕓\",\n        },\n        clock5: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕔\",\n        },\n        clock6: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\", \"dawn\", \"dusk\"],\n          char: \"🕕\",\n        },\n        clock7: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕖\",\n        },\n        clock8: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕗\",\n        },\n        clock9: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕘\",\n        },\n        clock10: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕙\",\n        },\n        clock11: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕚\",\n        },\n        clock12: {\n          keywords: [\"time\", \"noon\", \"midnight\", \"midday\", \"late\", \"early\", \"schedule\"],\n          char: \"🕛\",\n        },\n        clock130: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕜\",\n        },\n        clock230: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕝\",\n        },\n        clock330: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕞\",\n        },\n        clock430: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕟\",\n        },\n        clock530: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕠\",\n        },\n        clock630: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕡\",\n        },\n        clock730: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕢\",\n        },\n        clock830: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕣\",\n        },\n        clock930: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕤\",\n        },\n        clock1030: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕥\",\n        },\n        clock1130: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕦\",\n        },\n        clock1230: {\n          keywords: [\"time\", \"late\", \"early\", \"schedule\"],\n          char: \"🕧\",\n        },\n      },\n    },\n  },\n\n  {\n    flags: {\n      id: \"flags\",\n      name: t(\"FLAGS\"),\n      symbol: \"Flags\",\n      emojis: {\n        afghanistan: {\n          keywords: [\"af\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇫\",\n        },\n        aland_islands: {\n          keywords: [\"Åland\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇽\",\n        },\n        albania: {\n          keywords: [\"al\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇱\",\n        },\n        algeria: {\n          keywords: [\"dz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇩🇿\",\n        },\n        american_samoa: {\n          keywords: [\"american\", \"ws\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇸\",\n        },\n        andorra: {\n          keywords: [\"ad\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇩\",\n        },\n        angola: {\n          keywords: [\"ao\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇴\",\n        },\n        anguilla: {\n          keywords: [\"ai\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇮\",\n        },\n        antarctica: {\n          keywords: [\"aq\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇶\",\n        },\n        antigua_barbuda: {\n          keywords: [\"antigua\", \"barbuda\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇬\",\n        },\n        argentina: {\n          keywords: [\"ar\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇷\",\n        },\n        armenia: {\n          keywords: [\"am\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇲\",\n        },\n        aruba: {\n          keywords: [\"aw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇼\",\n        },\n        australia: {\n          keywords: [\"au\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇺\",\n        },\n        austria: {\n          keywords: [\"at\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇹\",\n        },\n        azerbaijan: {\n          keywords: [\"az\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇿\",\n        },\n        bahamas: {\n          keywords: [\"bs\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇸\",\n        },\n        bahrain: {\n          keywords: [\"bh\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇭\",\n        },\n        bangladesh: {\n          keywords: [\"bd\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇩\",\n        },\n        barbados: {\n          keywords: [\"bb\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇧\",\n        },\n        belarus: {\n          keywords: [\"by\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇾\",\n        },\n        belgium: {\n          keywords: [\"be\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇪\",\n        },\n        belize: {\n          keywords: [\"bz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇿\",\n        },\n        benin: {\n          keywords: [\"bj\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇯\",\n        },\n        bermuda: {\n          keywords: [\"bm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇲\",\n        },\n        bhutan: {\n          keywords: [\"bt\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇹\",\n        },\n        bolivia: {\n          keywords: [\"bo\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇴\",\n        },\n        caribbean_netherlands: {\n          keywords: [\"bonaire\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇶\",\n        },\n        bosnia_herzegovina: {\n          keywords: [\"bosnia\", \"herzegovina\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇦\",\n        },\n        botswana: {\n          keywords: [\"bw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇼\",\n        },\n        brazil: {\n          keywords: [\"br\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇷\",\n        },\n        british_indian_ocean_territory: {\n          keywords: [\n            \"british\",\n            \"indian\",\n            \"ocean\",\n            \"territory\",\n            \"flag\",\n            \"nation\",\n            \"country\",\n            \"banner\",\n          ],\n          char: \"🇮🇴\",\n        },\n        british_virgin_islands: {\n          keywords: [\"british\", \"virgin\", \"islands\", \"bvi\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇬\",\n        },\n        brunei: {\n          keywords: [\"bn\", \"darussalam\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇳\",\n        },\n        bulgaria: {\n          keywords: [\"bg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇬\",\n        },\n        burkina_faso: {\n          keywords: [\"burkina\", \"faso\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇫\",\n        },\n        burundi: {\n          keywords: [\"bi\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇮\",\n        },\n        cape_verde: {\n          keywords: [\"cabo\", \"verde\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇻\",\n        },\n        cambodia: {\n          keywords: [\"kh\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇭\",\n        },\n        cameroon: {\n          keywords: [\"cm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇲\",\n        },\n        canada: {\n          keywords: [\"ca\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇦\",\n        },\n        canary_islands: {\n          keywords: [\"canary\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇨\",\n        },\n        cayman_islands: {\n          keywords: [\"cayman\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇾\",\n        },\n        central_african_republic: {\n          keywords: [\"central\", \"african\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇫\",\n        },\n        chad: {\n          keywords: [\"td\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇩\",\n        },\n        chile: {\n          keywords: [\"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇱\",\n        },\n        cn: {\n          keywords: [\"china\", \"chinese\", \"prc\", \"flag\", \"country\", \"nation\", \"banner\"],\n          char: \"🇨🇳\",\n        },\n        christmas_island: {\n          keywords: [\"christmas\", \"island\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇽\",\n        },\n        cocos_islands: {\n          keywords: [\"cocos\", \"keeling\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇨\",\n        },\n        colombia: {\n          keywords: [\"co\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇴\",\n        },\n        comoros: {\n          keywords: [\"km\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇲\",\n        },\n        congo_brazzaville: {\n          keywords: [\"congo\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇬\",\n        },\n        congo_kinshasa: {\n          keywords: [\"congo\", \"democratic\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇩\",\n        },\n        cook_islands: {\n          keywords: [\"cook\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇰\",\n        },\n        costa_rica: {\n          keywords: [\"costa\", \"rica\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇷\",\n        },\n        croatia: {\n          keywords: [\"hr\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇭🇷\",\n        },\n        cuba: {\n          keywords: [\"cu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇺\",\n        },\n        curacao: {\n          keywords: [\"curaçao\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇼\",\n        },\n        cyprus: {\n          keywords: [\"cy\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇾\",\n        },\n        czech_republic: {\n          keywords: [\"cz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇿\",\n        },\n        denmark: {\n          keywords: [\"dk\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇩🇰\",\n        },\n        djibouti: {\n          keywords: [\"dj\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇩🇯\",\n        },\n        dominica: {\n          keywords: [\"dm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇩🇲\",\n        },\n        dominican_republic: {\n          keywords: [\"dominican\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇩🇴\",\n        },\n        ecuador: {\n          keywords: [\"ec\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇨\",\n        },\n        egypt: {\n          keywords: [\"eg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇬\",\n        },\n        el_salvador: {\n          keywords: [\"el\", \"salvador\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇻\",\n        },\n        equatorial_guinea: {\n          keywords: [\"equatorial\", \"gn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇶\",\n        },\n        eritrea: {\n          keywords: [\"er\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇷\",\n        },\n        estonia: {\n          keywords: [\"ee\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇪\",\n        },\n        ethiopia: {\n          keywords: [\"et\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇹\",\n        },\n        eu: {\n          keywords: [\"european\", \"union\", \"flag\", \"banner\"],\n          char: \"🇪🇺\",\n        },\n        falkland_islands: {\n          keywords: [\"falkland\", \"islands\", \"malvinas\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇫🇰\",\n        },\n        faroe_islands: {\n          keywords: [\"faroe\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇫🇴\",\n        },\n        fiji: {\n          keywords: [\"fj\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇫🇯\",\n        },\n        finland: {\n          keywords: [\"fi\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇫🇮\",\n        },\n        fr: {\n          keywords: [\"banner\", \"flag\", \"nation\", \"france\", \"french\", \"country\"],\n          char: \"🇫🇷\",\n        },\n        french_guiana: {\n          keywords: [\"french\", \"guiana\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇫\",\n        },\n        french_polynesia: {\n          keywords: [\"french\", \"polynesia\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇫\",\n        },\n        french_southern_territories: {\n          keywords: [\"french\", \"southern\", \"territories\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇫\",\n        },\n        gabon: {\n          keywords: [\"ga\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇦\",\n        },\n        gambia: {\n          keywords: [\"gm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇲\",\n        },\n        georgia: {\n          keywords: [\"ge\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇪\",\n        },\n        de: {\n          keywords: [\"german\", \"nation\", \"flag\", \"country\", \"banner\"],\n          char: \"🇩🇪\",\n        },\n        ghana: {\n          keywords: [\"gh\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇭\",\n        },\n        gibraltar: {\n          keywords: [\"gi\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇮\",\n        },\n        greece: {\n          keywords: [\"gr\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇷\",\n        },\n        greenland: {\n          keywords: [\"gl\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇱\",\n        },\n        grenada: {\n          keywords: [\"gd\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇩\",\n        },\n        guadeloupe: {\n          keywords: [\"gp\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇵\",\n        },\n        guam: {\n          keywords: [\"gu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇺\",\n        },\n        guatemala: {\n          keywords: [\"gt\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇹\",\n        },\n        guernsey: {\n          keywords: [\"gg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇬\",\n        },\n        guinea: {\n          keywords: [\"gn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇳\",\n        },\n        guinea_bissau: {\n          keywords: [\"gw\", \"bissau\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇼\",\n        },\n        guyana: {\n          keywords: [\"gy\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇬🇾\",\n        },\n        haiti: {\n          keywords: [\"ht\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇭🇹\",\n        },\n        honduras: {\n          keywords: [\"hn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇭🇳\",\n        },\n        hong_kong: {\n          keywords: [\"hong\", \"kong\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇭🇰\",\n        },\n        hungary: {\n          keywords: [\"hu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇭🇺\",\n        },\n        iceland: {\n          keywords: [\"is\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇸\",\n        },\n        india: {\n          keywords: [\"in\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇳\",\n        },\n        indonesia: {\n          keywords: [\"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇩\",\n        },\n        iran: {\n          keywords: [\"iran,\", \"islamic\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇷\",\n        },\n        iraq: {\n          keywords: [\"iq\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇶\",\n        },\n        ireland: {\n          keywords: [\"ie\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇪\",\n        },\n        isle_of_man: {\n          keywords: [\"isle\", \"man\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇲\",\n        },\n        israel: {\n          keywords: [\"il\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇱\",\n        },\n        it: {\n          keywords: [\"italy\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇮🇹\",\n        },\n        cote_divoire: {\n          keywords: [\"ivory\", \"coast\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇮\",\n        },\n        jamaica: {\n          keywords: [\"jm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇯🇲\",\n        },\n        jp: {\n          keywords: [\"japanese\", \"nation\", \"flag\", \"country\", \"banner\"],\n          char: \"🇯🇵\",\n        },\n        jersey: {\n          keywords: [\"je\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇯🇪\",\n        },\n        jordan: {\n          keywords: [\"jo\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇯🇴\",\n        },\n        kazakhstan: {\n          keywords: [\"kz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇿\",\n        },\n        kenya: {\n          keywords: [\"ke\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇪\",\n        },\n        kiribati: {\n          keywords: [\"ki\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇮\",\n        },\n        kosovo: {\n          keywords: [\"xk\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇽🇰\",\n        },\n        kuwait: {\n          keywords: [\"kw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇼\",\n        },\n        kyrgyzstan: {\n          keywords: [\"kg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇬\",\n        },\n        laos: {\n          keywords: [\"lao\", \"democratic\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇦\",\n        },\n        latvia: {\n          keywords: [\"lv\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇻\",\n        },\n        lebanon: {\n          keywords: [\"lb\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇧\",\n        },\n        lesotho: {\n          keywords: [\"ls\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇸\",\n        },\n        liberia: {\n          keywords: [\"lr\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇷\",\n        },\n        libya: {\n          keywords: [\"ly\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇾\",\n        },\n        liechtenstein: {\n          keywords: [\"li\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇮\",\n        },\n        lithuania: {\n          keywords: [\"lt\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇹\",\n        },\n        luxembourg: {\n          keywords: [\"lu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇺\",\n        },\n        macau: {\n          keywords: [\"macao\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇴\",\n        },\n        macedonia: {\n          keywords: [\"macedonia,\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇰\",\n        },\n        madagascar: {\n          keywords: [\"mg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇬\",\n        },\n        malawi: {\n          keywords: [\"mw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇼\",\n        },\n        malaysia: {\n          keywords: [\"my\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇾\",\n        },\n        maldives: {\n          keywords: [\"mv\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇻\",\n        },\n        mali: {\n          keywords: [\"ml\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇱\",\n        },\n        malta: {\n          keywords: [\"mt\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇹\",\n        },\n        marshall_islands: {\n          keywords: [\"marshall\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇭\",\n        },\n        martinique: {\n          keywords: [\"mq\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇶\",\n        },\n        mauritania: {\n          keywords: [\"mr\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇷\",\n        },\n        mauritius: {\n          keywords: [\"mu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇺\",\n        },\n        mayotte: {\n          keywords: [\"yt\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇾🇹\",\n        },\n        mexico: {\n          keywords: [\"mx\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇽\",\n        },\n        micronesia: {\n          keywords: [\"micronesia,\", \"federated\", \"states\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇫🇲\",\n        },\n        moldova: {\n          keywords: [\"moldova,\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇩\",\n        },\n        monaco: {\n          keywords: [\"mc\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇨\",\n        },\n        mongolia: {\n          keywords: [\"mn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇳\",\n        },\n        montenegro: {\n          keywords: [\"me\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇪\",\n        },\n        montserrat: {\n          keywords: [\"ms\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇸\",\n        },\n        morocco: {\n          keywords: [\"ma\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇦\",\n        },\n        mozambique: {\n          keywords: [\"mz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇿\",\n        },\n        myanmar: {\n          keywords: [\"mm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇲\",\n        },\n        namibia: {\n          keywords: [\"na\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇦\",\n        },\n        nauru: {\n          keywords: [\"nr\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇷\",\n        },\n        nepal: {\n          keywords: [\"np\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇵\",\n        },\n        netherlands: {\n          keywords: [\"nl\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇱\",\n        },\n        new_caledonia: {\n          keywords: [\"new\", \"caledonia\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇨\",\n        },\n        new_zealand: {\n          keywords: [\"new\", \"zealand\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇿\",\n        },\n        nicaragua: {\n          keywords: [\"ni\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇮\",\n        },\n        niger: {\n          keywords: [\"ne\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇪\",\n        },\n        nigeria: {\n          keywords: [\"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇬\",\n        },\n        niue: {\n          keywords: [\"nu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇺\",\n        },\n        norfolk_island: {\n          keywords: [\"norfolk\", \"island\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇫\",\n        },\n        northern_mariana_islands: {\n          keywords: [\"northern\", \"mariana\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇲🇵\",\n        },\n        north_korea: {\n          keywords: [\"north\", \"korea\", \"nation\", \"flag\", \"country\", \"banner\"],\n          char: \"🇰🇵\",\n        },\n        norway: {\n          keywords: [\"no\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇳🇴\",\n        },\n        oman: {\n          keywords: [\"om_symbol\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇴🇲\",\n        },\n        pakistan: {\n          keywords: [\"pk\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇰\",\n        },\n        palau: {\n          keywords: [\"pw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇼\",\n        },\n        palestinian_territories: {\n          keywords: [\n            \"palestine\",\n            \"palestinian\",\n            \"territories\",\n            \"flag\",\n            \"nation\",\n            \"country\",\n            \"banner\",\n          ],\n          char: \"🇵🇸\",\n        },\n        panama: {\n          keywords: [\"pa\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇦\",\n        },\n        papua_new_guinea: {\n          keywords: [\"papua\", \"new\", \"guinea\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇬\",\n        },\n        paraguay: {\n          keywords: [\"py\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇾\",\n        },\n        peru: {\n          keywords: [\"pe\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇪\",\n        },\n        philippines: {\n          keywords: [\"ph\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇭\",\n        },\n        pitcairn_islands: {\n          keywords: [\"pitcairn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇳\",\n        },\n        poland: {\n          keywords: [\"pl\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇱\",\n        },\n        portugal: {\n          keywords: [\"pt\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇹\",\n        },\n        puerto_rico: {\n          keywords: [\"puerto\", \"rico\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇷\",\n        },\n        qatar: {\n          keywords: [\"qa\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇶🇦\",\n        },\n        reunion: {\n          keywords: [\"réunion\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇷🇪\",\n        },\n        romania: {\n          keywords: [\"ro\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇷🇴\",\n        },\n        ru: {\n          keywords: [\"russian\", \"federation\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇷🇺\",\n        },\n        rwanda: {\n          keywords: [\"rw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇷🇼\",\n        },\n        st_barthelemy: {\n          keywords: [\"saint\", \"barthélemy\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇧🇱\",\n        },\n        st_helena: {\n          keywords: [\n            \"saint\",\n            \"helena\",\n            \"ascension\",\n            \"tristan\",\n            \"cunha\",\n            \"flag\",\n            \"nation\",\n            \"country\",\n            \"banner\",\n          ],\n          char: \"🇸🇭\",\n        },\n        st_kitts_nevis: {\n          keywords: [\"saint\", \"kitts\", \"nevis\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇰🇳\",\n        },\n        st_lucia: {\n          keywords: [\"saint\", \"lucia\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇨\",\n        },\n        st_pierre_miquelon: {\n          keywords: [\"saint\", \"pierre\", \"miquelon\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇵🇲\",\n        },\n        st_vincent_grenadines: {\n          keywords: [\"saint\", \"vincent\", \"grenadines\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇨\",\n        },\n        samoa: {\n          keywords: [\"ws\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇼🇸\",\n        },\n        san_marino: {\n          keywords: [\"san\", \"marino\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇲\",\n        },\n        sao_tome_principe: {\n          keywords: [\"sao\", \"tome\", \"principe\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇹\",\n        },\n        saudi_arabia: {\n          keywords: [\"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇦\",\n        },\n        senegal: {\n          keywords: [\"sn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇳\",\n        },\n        serbia: {\n          keywords: [\"rs\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇷🇸\",\n        },\n        seychelles: {\n          keywords: [\"sc\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇨\",\n        },\n        sierra_leone: {\n          keywords: [\"sierra\", \"leone\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇱\",\n        },\n        singapore: {\n          keywords: [\"sg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇬\",\n        },\n        sint_maarten: {\n          keywords: [\"sint\", \"maarten\", \"dutch\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇽\",\n        },\n        slovakia: {\n          keywords: [\"sk\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇰\",\n        },\n        slovenia: {\n          keywords: [\"si\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇮\",\n        },\n        solomon_islands: {\n          keywords: [\"solomon\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇧\",\n        },\n        somalia: {\n          keywords: [\"so\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇴\",\n        },\n        south_africa: {\n          keywords: [\"south\", \"africa\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇿🇦\",\n        },\n        south_georgia_south_sandwich_islands: {\n          keywords: [\n            \"south\",\n            \"georgia\",\n            \"sandwich\",\n            \"islands\",\n            \"flag\",\n            \"nation\",\n            \"country\",\n            \"banner\",\n          ],\n          char: \"🇬🇸\",\n        },\n        kr: {\n          keywords: [\"south\", \"korea\", \"nation\", \"flag\", \"country\", \"banner\"],\n          char: \"🇰🇷\",\n        },\n        south_sudan: {\n          keywords: [\"south\", \"sd\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇸\",\n        },\n        es: {\n          keywords: [\"spain\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇸\",\n        },\n        sri_lanka: {\n          keywords: [\"sri\", \"lanka\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇱🇰\",\n        },\n        sudan: {\n          keywords: [\"sd\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇩\",\n        },\n        suriname: {\n          keywords: [\"sr\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇷\",\n        },\n        swaziland: {\n          keywords: [\"sz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇿\",\n        },\n        sweden: {\n          keywords: [\"se\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇪\",\n        },\n        switzerland: {\n          keywords: [\"ch\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇨🇭\",\n        },\n        syria: {\n          keywords: [\"syrian\", \"arab\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇸🇾\",\n        },\n        taiwan: {\n          keywords: [\"tw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇼\",\n        },\n        tajikistan: {\n          keywords: [\"tj\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇯\",\n        },\n        tanzania: {\n          keywords: [\"tanzania,\", \"united\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇿\",\n        },\n        thailand: {\n          keywords: [\"th\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇭\",\n        },\n        timor_leste: {\n          keywords: [\"timor\", \"leste\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇱\",\n        },\n        togo: {\n          keywords: [\"tg\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇬\",\n        },\n        tokelau: {\n          keywords: [\"tk\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇰\",\n        },\n        tonga: {\n          keywords: [\"to\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇴\",\n        },\n        trinidad_tobago: {\n          keywords: [\"trinidad\", \"tobago\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇹\",\n        },\n        tunisia: {\n          keywords: [\"tn\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇳\",\n        },\n        tr: {\n          keywords: [\"turkey\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇷\",\n        },\n        turkmenistan: {\n          keywords: [\"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇲\",\n        },\n        turks_caicos_islands: {\n          keywords: [\"turks\", \"caicos\", \"islands\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇨\",\n        },\n        tuvalu: {\n          keywords: [\"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇹🇻\",\n        },\n        uganda: {\n          keywords: [\"ug\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇺🇬\",\n        },\n        ukraine: {\n          keywords: [\"ua\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇺🇦\",\n        },\n        united_arab_emirates: {\n          keywords: [\"united\", \"arab\", \"emirates\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇦🇪\",\n        },\n        uk: {\n          keywords: [\n            \"united\",\n            \"kingdom\",\n            \"great\",\n            \"britain\",\n            \"northern\",\n            \"ireland\",\n            \"flag\",\n            \"nation\",\n            \"country\",\n            \"banner\",\n            \"british\",\n            \"UK\",\n            \"english\",\n            \"england\",\n            \"union jack\",\n          ],\n          char: \"🇬🇧\",\n        },\n        england: {\n          keywords: [\"flag\", \"english\"],\n          char: \"🏴󠁧󠁢󠁥󠁮󠁧󠁿\",\n        },\n        scotland: {\n          keywords: [\"flag\", \"scottish\"],\n          char: \"🏴󠁧󠁢󠁳󠁣󠁴󠁿\",\n        },\n        wales: {\n          keywords: [\"flag\", \"welsh\"],\n          char: \"🏴󠁧󠁢󠁷󠁬󠁳󠁿\",\n        },\n        us: {\n          keywords: [\"united\", \"states\", \"america\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇺🇸\",\n        },\n        us_virgin_islands: {\n          keywords: [\"virgin\", \"islands\", \"us\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇮\",\n        },\n        uruguay: {\n          keywords: [\"uy\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇺🇾\",\n        },\n        uzbekistan: {\n          keywords: [\"uz\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇺🇿\",\n        },\n        vanuatu: {\n          keywords: [\"vu\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇺\",\n        },\n        vatican_city: {\n          keywords: [\"vatican\", \"city\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇦\",\n        },\n        venezuela: {\n          keywords: [\"ve\", \"bolivarian\", \"republic\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇪\",\n        },\n        vietnam: {\n          keywords: [\"viet\", \"nam\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇻🇳\",\n        },\n        wallis_futuna: {\n          keywords: [\"wallis\", \"futuna\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇼🇫\",\n        },\n        western_sahara: {\n          keywords: [\"western\", \"sahara\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇪🇭\",\n        },\n        yemen: {\n          keywords: [\"ye\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇾🇪\",\n        },\n        zambia: {\n          keywords: [\"zm\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇿🇲\",\n        },\n        zimbabwe: {\n          keywords: [\"zw\", \"flag\", \"nation\", \"country\", \"banner\"],\n          char: \"🇿🇼\",\n        },\n        united_nations: {\n          keywords: [\"un\", \"flag\", \"banner\"],\n          char: \"🇺🇳\",\n        },\n        pirate_flag: {\n          keywords: [\"skull\", \"crossbones\", \"flag\", \"banner\"],\n          char: \"🏴‍☠️\",\n        },\n      },\n    },\n  },\n];\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/index.ts",
    "content": "import { CometChatEmojiKeyboard } from \"./CometChatEmojiKeyboard\";\n\nexport { CometChatEmojiKeyboard };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/resources/index.ts",
    "content": "import activity from \"./activity.png\";\nimport animals from \"./animals.png\";\nimport flags from \"./flags.png\";\nimport food from \"./food.png\";\nimport objects from \"./objects.png\";\nimport smileys from \"./smileys.png\";\nimport symbols from \"./symbols.png\";\nimport travel from \"./travel.png\";\n\nexport { activity, animals, flags, food, objects, smileys, symbols, travel };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatEmojiKeyboard/style.ts",
    "content": "import { Dimensions, Platform, StyleSheet } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\n/**\n * Styles for the CometChatEmojiKeyboard component.\n * Adjust spacing, colors, and layout as needed.\n */\nconst Styles = (theme: CometChatTheme) => {\n  const screenWidth = Dimensions.get(\"window\").width;\n  const numColumns = 8; // Ensure this matches the main component's NUM_COLUMNS\n\n  return StyleSheet.create({\n    fixedHeader: {\n      paddingBottom: 8,\n      paddingLeft: 12,\n      alignSelf: \"flex-start\",\n      top: 0,\n      left: 0,\n      zIndex: 1,\n    },\n    flatListContent: {\n      paddingBottom: 10,\n      gap: 8,\n    },\n    categoryContainer: {\n      flexDirection: \"row\",\n      justifyContent: \"space-around\",\n      alignItems: \"center\",\n      borderTopWidth: 1,\n      borderTopColor: \"#e0e0e0\",\n    },\n    categoryListContainer: {\n      flexDirection: \"row\",\n      justifyContent: \"space-around\",\n      width: \"100%\",\n      paddingVertical: 10,\n      paddingHorizontal: 10,\n    },\n    emojiItem: {\n      width: (screenWidth - 24) / numColumns,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    emojiText: {\n      fontSize: Platform.OS === \"ios\" ? 28 : 24,\n      paddingHorizontal: 6,\n    },\n    iconContainer: {\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      padding: 10,\n      borderRadius: theme.spacing.spacing.s8,\n    },\n    activeIcon: {\n      backgroundColor: theme.color.extendedPrimary100,\n      borderRadius: theme.spacing.spacing.s8,\n    },\n    emojiKeyboardContainer: {\n      width: \"100%\",\n      flex: 1,\n      borderRadius: 12,\n      backgroundColor: theme.color.background1,\n      flexDirection: \"column\",\n    },\n    emojiGridContainer: {\n      flex: 1,\n      padding: 8,\n    },\n    flatList: {\n      flex: 1,\n    },\n  });\n};\n\nexport default Styles;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatFileBubble/CometChatFileBubble.tsx",
    "content": "import React, { JSX, useEffect, useMemo, useRef } from \"react\";\nimport {\n  ActivityIndicator,\n  EmitterSubscription,\n  ImageSourcePropType,\n  ImageStyle,\n  NativeEventEmitter,\n  NativeModules,\n  NativeSyntheticEvent,\n  NativeTouchEvent,\n  Platform,\n  Text,\n  TextStyle,\n  View,\n} from \"react-native\";\nimport { Icon } from \"../../icons/Icon\";\nimport { useTheme } from \"../../../theme\";\nimport { getFileTypeIcon } from \"../../constants/UIKitConstants\";\n\nconst { FileManager } = NativeModules;\nconst eventEmitter = new NativeEventEmitter(FileManager);\n\n/**\n * Props for the CometChatFileBubble component.\n */\nexport interface CometChatFileBubbleInterface {\n  /**\n   * URL of the file to be displayed/downloaded.\n   */\n  fileUrl: string;\n  /**\n   * Title of the file.\n   */\n  title: string;\n  /**\n   * Subtitle or description for the file.\n   */\n  subtitle?: string;\n  /**\n   * Custom style for the title text.\n   */\n  titleStyle?: TextStyle;\n  /**\n   * Custom style for the subtitle text.\n   */\n  subtitleStyle?: TextStyle;\n  /**\n   * Custom icon for download. Can be an image source or a JSX element.\n   */\n  downloadIcon?: ImageSourcePropType | JSX.Element;\n  /**\n   * Custom style for the download icon.\n   */\n  downloadIconStyle?: ImageStyle;\n}\n\n/**\n * CometChatFileBubble is a component that displays a file bubble with title,\n * subtitle and an icon indicating the file type. It handles file download,\n * existence check and opening of the file.\n *\n *  Props for the component.\n *  The rendered file bubble.\n */\nexport const CometChatFileBubble = ({\n  fileUrl,\n  title,\n  titleStyle,\n  subtitleStyle,\n  subtitle,\n  downloadIcon,\n  downloadIconStyle,\n}: CometChatFileBubbleInterface) => {\n  const [processing, setProcessing] = React.useState(false);\n  const [fileExists, setFileExists] = React.useState(false); // State to track if the file exists\n  const theme = useTheme();\n  let listener: EmitterSubscription;\n\n  // Android-specific download identifier\n  const downloadIdRef = useRef(0);\n  // Flag to indicate if file should be opened after downloading\n  const openFileAfterDownloading = useRef(false);\n\n  useEffect(() => {\n    checkFileExists();\n\n    if (Platform.OS == \"android\") {\n      listener = eventEmitter.addListener(\n        \"downloadComplete\",\n        (data: { downloadId: number }) => {\n          if (data.downloadId && downloadIdRef.current && data.downloadId == downloadIdRef.current) {\n            setProcessing(false);\n            setFileExists(true);\n            if (openFileAfterDownloading.current) {\n              openFile();\n              openFileAfterDownloading.current = false;\n            }\n          }\n        }\n      );\n    }\n    return () => {\n      if (Platform.OS == \"android\") {\n        listener.remove();\n      }\n    };\n  }, [fileUrl]);\n\n  /**\n   * Checks if the file exists locally.\n   *\n   * If true, download and open the file if it doesn't exist.\n   */\n  const checkFileExists = async (downloadAndOpenFile = false) => {\n    if (!fileUrl) return;\n\n    const fileName = getFileName();\n    setProcessing(true);\n    FileManager.doesFileExist(fileName, (result: string) => {\n      setProcessing(false);\n      if (JSON.parse(result).exists) {\n        setFileExists(true);\n        if (downloadAndOpenFile) {\n          openFile();\n          openFileAfterDownloading.current = false;\n        }\n      } else {\n        if (downloadAndOpenFile) {\n          openFileAfterDownloading.current = true;\n          downloadFile();\n        }\n      }\n    });\n  };\n\n  /**\n   * Initiates the file download if it is not already processed or exists.\n   */\n  const downloadFile = () => {\n    if (processing || fileExists) return; // Do not process if file already exists\n\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.checkAndDownload(fileUrl, getFileName(), (result: string) => {\n      if (Platform.OS == \"ios\") {\n        let parsedResult = JSON.parse(result);\n        if (parsedResult.success == true) {\n          setProcessing(false);\n          setFileExists(true);\n        }\n        if (openFileAfterDownloading.current) {\n          openFile();\n          openFileAfterDownloading.current = false;\n        }\n      } else if (Platform.OS == \"android\") {\n        downloadIdRef.current = JSON.parse(result).downloadId;\n      }\n    });\n  };\n\n  /**\n   * Opens the file using the FileManager.\n   */\n  const openFile = () => {\n    if (processing) return;\n    if (!fileUrl) return;\n\n    setProcessing(true);\n    FileManager.openFile(fileUrl, getFileName(), (isOpened: string) => {\n      setProcessing(false);\n    });\n  };\n\n  /**\n   * Extracts the file name from the file URL.\n   *\n   * The file name.\n   */\n  const getFileName = () => {\n    return fileUrl.substring(fileUrl.lastIndexOf(\"/\") + 1).replace(\" \", \"_\");\n  };\n\n  // Touch handling for iOS\n  const wrapperPressTime = useRef<number | null>(0);\n  const viewProps = useMemo(() => {\n    return Platform.OS === \"ios\"\n      ? {\n          onTouchStart: () => {\n            wrapperPressTime.current = Date.now();\n          },\n          onTouchMove: () => {\n            wrapperPressTime.current = null;\n          },\n          onTouchEnd: () => {\n            if (wrapperPressTime.current === null) return;\n            const endTime = Date.now();\n            const pressDuration = endTime - wrapperPressTime.current;\n            if (pressDuration < 500) {\n              checkFileExists(true);\n            }\n          },\n        }\n      : {};\n  }, []);\n\n  // Touch handling for Android\n  const pressTimeOnAndroid = useRef(0);\n  const viewPropsForAndroid = useMemo(() => {\n    return Platform.OS === \"android\"\n      ? {\n          onTouchStart: () => {\n            pressTimeOnAndroid.current = Date.now();\n          },\n          onTouchEnd: () => {\n            const endTime = Date.now();\n            const pressDuration = endTime - pressTimeOnAndroid.current;\n            if (pressDuration < 500) {\n              checkFileExists(true);\n            }\n          },\n        }\n      : {};\n  }, []);\n\n  /**\n   * Handler to download the file when the download icon is pressed.\n   *\n   * @param {NativeSyntheticEvent<NativeTouchEvent>} e - The touch event.\n   */\n  const shouldDownload = (e: NativeSyntheticEvent<NativeTouchEvent>) => {\n    e.stopPropagation();\n    setProcessing(true);\n    downloadFile();\n  };\n\n  return (\n    <View {...viewProps} style={{ flexDirection: \"row\", gap: 8 }}>\n      <View {...viewPropsForAndroid} style={{ height: 32, width: 32 }}>\n        <Icon name={getFileTypeIcon(title)} size={32}></Icon>\n      </View>\n      <View\n        {...viewPropsForAndroid}\n        style={{ flexDirection: \"column\", flexGrow: 1, flexShrink: 1 }}\n      >\n        {title && (\n          <Text numberOfLines={1} ellipsizeMode={\"tail\"} style={[titleStyle]}>\n            {title}\n          </Text>\n        )}\n        {subtitle && (\n          <Text numberOfLines={1} ellipsizeMode={\"tail\"} style={subtitleStyle}>\n            {subtitle}\n          </Text>\n        )}\n      </View>\n      {processing && !fileExists && (\n        <ActivityIndicator color={downloadIconStyle?.tintColor} />\n      )}\n    </View>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatFileBubble/index.ts",
    "content": "import { CometChatFileBubble, CometChatFileBubbleInterface } from \"./CometChatFileBubble\";\nimport { getFileBubbleStyleLight, getFileBubbleStyleDark } from \"./style\";\n\nexport {\n  CometChatFileBubble,\n  getFileBubbleStyleLight,\n  getFileBubbleStyleDark,\n};\n\nexport type {\n  CometChatFileBubbleInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatFileBubble/resources/index.ts",
    "content": "import downloadIcon from \"./download.png\";\nimport fileIcon from \"./file_upload.png\";\n\nexport { downloadIcon, fileIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatFileBubble/style.ts",
    "content": "import { CometChatTheme } from \"../../../theme/type\";\n\nexport const getFileBubbleStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingFileBubbleStyle: Partial<CometChatTheme[\"fileBubbleStyles\"]>;\n  outgoingFileBubbleStyle: Partial<CometChatTheme[\"fileBubbleStyles\"]>;\n} => {\n  return {\n    incomingFileBubbleStyle: {\n      containerStyle: {\n        paddingTop: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p2,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        width: 240,\n      },\n      titleStyle: {\n        color: color.neutral900,\n        ...typography.body.medium,\n        flexShrink: 1,\n        maxWidth: \"90%\",\n      },\n      subtitleStyle: {\n        color: color.neutral600,\n        ...typography.caption2.regular,\n      },\n      downloadIconStyle: {\n        height: 20,\n        width: 20,\n        tintColor: color.primary,\n      },\n    },\n    outgoingFileBubbleStyle: {\n      containerStyle: {\n        paddingTop: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p2,\n        borderRadius: spacing.radius.r3,\n        width: 240,\n      },\n      titleStyle: {\n        color: color.staticWhite,\n        ...typography.body.medium,\n        flexShrink: 1,\n        maxWidth: \"90%\",\n      },\n      subtitleStyle: {\n        color: color.staticWhite,\n        ...typography.caption2.regular,\n      },\n      downloadIconStyle: {\n        height: 20,\n        width: 20,\n        tintColor: color.staticWhite,\n      },\n    },\n  };\n};\n\nexport const getFileBubbleStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingFileBubbleStyle: Partial<CometChatTheme[\"fileBubbleStyles\"]>;\n  outgoingFileBubbleStyle: Partial<CometChatTheme[\"fileBubbleStyles\"]>;\n} => {\n  return {\n    incomingFileBubbleStyle: {\n      containerStyle: {\n        paddingTop: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p2,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        width: 240,\n      },\n      titleStyle: {\n        color: color.neutral900,\n        ...typography.body.medium,\n        flexShrink: 1,\n        maxWidth: \"90%\",\n      },\n      subtitleStyle: {\n        color: color.neutral600,\n        ...typography.caption2.regular,\n      },\n      downloadIconStyle: {\n        height: 20,\n        width: 20,\n        tintColor: color.primary,\n      },\n    },\n    outgoingFileBubbleStyle: {\n      containerStyle: {\n        paddingTop: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p2,\n        borderRadius: spacing.radius.r3,\n        width: 240,\n      },\n      titleStyle: {\n        color: color.staticWhite,\n        ...typography.body.medium,\n        flexShrink: 1,\n        maxWidth: \"90%\",\n      },\n      subtitleStyle: {\n        color: color.staticWhite,\n        ...typography.caption2.regular,\n      },\n      downloadIconStyle: {\n        height: 20,\n        width: 20,\n        tintColor: color.staticWhite,\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageBubble/CometChatImageBubble.tsx",
    "content": "import React from \"react\";\nimport {\n  ImageSourcePropType,\n  ImageStyle,\n} from \"react-native\";\nimport { CometChatImageLoader } from \"./CometChatImageLoader\";\n\n/**\n * Props for the CometChatImageBubble component.\n */\nexport interface CometChatImageBubbleInterface {\n  /**\n   * Image URL to be displayed.\n   * Pass as an object with a `uri` property, e.g., `{ uri: \"dummyUrl\" }`.\n   */\n  imageUrl: ImageSourcePropType;\n  /**\n   * Thumbnail image URL.\n   *\n   * @type {ImageSourcePropType}\n   */\n  thumbnailUrl?: ImageSourcePropType;\n  /**\n   * Placeholder image to display while the main image is loading.\n   */\n  placeHolderImage?: ImageSourcePropType;\n  /**\n   * Callback function to execute when the image is pressed.\n   */\n  onPress?: Function;\n  /**\n   * Custom style for the image.\n   */\n  style?: ImageStyle;\n  /**\n   * Resize mode for the image.\n   *\n   * @default \"cover\"\n   */\n  resizeMode?: \"cover\" | \"contain\" | \"stretch\" | \"repeat\" | \"center\";\n}\n\n/**\n * CometChatImageBubble is a component that displays an image with support for thumbnails,\n * placeholders, and custom styling. It uses the CometChatImageLoader component internally.\n *\n * Props for the image bubble component.\n * The rendered image bubble.\n */\nexport const CometChatImageBubble = (props: CometChatImageBubbleInterface) => {\n  const { thumbnailUrl, imageUrl, placeHolderImage, style, resizeMode } = props;\n\n  return (\n    <>\n      <CometChatImageLoader\n        imageUrl={imageUrl}\n        thumbnailUrl={thumbnailUrl}\n        activityIndicatorSize={48}\n        activityIndicatorViewStyle={{\n          height: style?.height,\n          width: style?.width,\n          backgroundColor: style?.backgroundColor,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          borderRadius: style?.borderRadius,\n        }}\n        placeHolderImage={placeHolderImage}\n        style={style}\n        imageResizeMode={resizeMode || \"cover\"}\n      />\n    </>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageBubble/CometChatImageLoader.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Image,\n  View,\n  StyleSheet,\n  Platform,\n  ImageSourcePropType,\n  ImageStyle,\n  ViewStyle,\n} from \"react-native\";\nimport { ImageViewerModal } from \"../CometChatImageViewerModal\";\nimport { CommonUtils } from \"../../utils/CommonUtils\";\n\n/**\n * Props for the CometChatImageLoader component.\n */\nexport interface CometChatImageLoaderPropType {\n  /**\n   * Image URL passed as an object with a `uri` property.\n   * Example: `{ uri: \"dummyUrl\" }`\n   */\n  imageUrl: ImageSourcePropType;\n  /**\n   * Thumbnail image URL.\n   *\n   * @type {ImageSourcePropType}\n   * @description Thumbnail image to display while the full image loads.\n   */\n  thumbnailUrl?: ImageSourcePropType;\n  /**\n   * Placeholder image to display while loading.\n   */\n  placeHolderImage?: ImageSourcePropType;\n  /**\n   * Custom callback function to execute when the image is pressed.\n   */\n  onPress?: Function;\n  /**\n   * Custom style for the image.\n   */\n  style?: ImageStyle;\n  /**\n   * Resize mode for the image.\n   *\n   * @default \"cover\"\n   */\n  imageResizeMode?: \"cover\" | \"contain\" | \"stretch\" | \"repeat\" | \"center\";\n  /**\n   * Size of the activity indicator.\n   */\n  activityIndicatorSize: number;\n  /**\n   * Custom style for the view containing the activity indicator.\n   */\n  activityIndicatorViewStyle: ViewStyle;\n}\n\n/**\n * CometChatImageLoader is a component that displays an image with an activity indicator\n * until the image is loaded. It supports thumbnail prefetching and opens an image viewer\n * modal on a quick tap.\n *\n *  Props for the component.\n *  The rendered image loader component.\n */\nexport const CometChatImageLoader = (props: CometChatImageLoaderPropType) => {\n  const {\n    thumbnailUrl,\n    imageUrl,\n    style,\n    imageResizeMode,\n    activityIndicatorSize,\n    activityIndicatorViewStyle,\n  } = props;\n\n  const [isLoaded, setIsLoaded] = useState(false);\n  const [isVisible, setIsVisible] = useState(false);\n  const [imageSource, setImageSource] = useState<ImageSourcePropType>();\n\n  // Ref to record touch press time for detecting quick taps\n  const pressTime = useRef<number | null>(0);\n\n  /**\n   * Handles the touch start event.\n   */\n  const handleTouchStart = () => {\n    pressTime.current = Date.now();\n  };\n\n  /**\n   * Handles the touch end event and opens the image viewer if tap duration is short.\n   */\n  const handleTouchEnd = () => {\n    if (pressTime.current === null && Platform.OS === \"ios\") return;\n    const endTime = Date.now();\n    const pressDuration = endTime - pressTime.current!;\n    if (pressDuration < 500) {\n      setIsVisible(true);\n    }\n  };\n\n  /**\n   * Handles the touch move event. On iOS, cancels the tap detection.\n   */\n  const onTouchMove = () => {\n    if (Platform.OS === \"ios\") {\n      pressTime.current = null;\n    }\n  };\n\n  useEffect(() => {\n    // Prefetch the thumbnail if available, else fallback to the full image\n    if (thumbnailUrl && typeof thumbnailUrl === \"object\" && \"uri\" in thumbnailUrl) {\n      CommonUtils.prefetchThumbnail(thumbnailUrl.uri!).then((success: any) => {\n        if (success) {\n          setImageSource(thumbnailUrl);\n        } else {\n          setImageSource(imageUrl); // Fallback to original imageUrl if prefetch fails\n        }\n      });\n    } else {\n      setImageSource(imageUrl); // No thumbnail available, fallback to imageUrl\n    }\n  }, [thumbnailUrl, imageUrl]);\n\n  return (\n    <>\n      {isVisible && (\n        <ImageViewerModal\n          imageUrl={imageUrl}\n          isVisible={isVisible}\n          onClose={() => {\n            setIsVisible(false);\n          }}\n        />\n      )}\n      <View\n        onTouchStart={handleTouchStart}\n        onTouchEnd={handleTouchEnd}\n        onTouchMove={onTouchMove}\n        hitSlop={{top: 10, bottom: 10, left: 10, right: 10}}\n        style={{\n          position: \"relative\",\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          height: style?.height,\n          width: style?.width,\n          minWidth: style?.minWidth,\n        }}\n      >\n        {/* Render the image. It is initially hidden until loaded. */}\n        <Image\n          resizeMode={imageResizeMode || \"cover\"}\n          source={imageSource}\n          style={[styles.image, style]}\n          onLoad={() => setIsLoaded(true)}\n        />\n        {/* Render the activity indicator until the image is loaded */}\n        {!isLoaded && (\n          <ActivityIndicator\n            size={activityIndicatorSize || \"large\"}\n            style={[styles.loader, activityIndicatorViewStyle]}\n          />\n        )}\n      </View>\n    </>\n  );\n};\n\n// Default styles for the component\nconst styles = StyleSheet.create({\n  container: {\n    position: \"relative\", // Use relative positioning for the container\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  loader: {\n    position: \"absolute\", // Keep the loader centered over the image\n    zIndex: 1, // Ensure it is rendered above the image until the image loads\n  },\n  image: {\n    position: \"absolute\", // The image remains fixed in place while loading\n  },\n});"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageBubble/assets/index.ts",
    "content": "import Spinner from \"./spineer.png\";\n\nexport const ICONS = {\n  Spinner,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageBubble/index.ts",
    "content": "import { CometChatImageBubble, CometChatImageBubbleInterface } from \"./CometChatImageBubble\";\n\nexport {\n  CometChatImageBubble,\n};\n\nexport type {\n  CometChatImageBubbleInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageViewerModal/ImageViewerModal.tsx",
    "content": "import React, { useLayoutEffect, useState } from \"react\";\nimport { ActivityIndicator, Image, Modal, StyleSheet, TouchableOpacity, View } from \"react-native\";\nimport { SafeAreaView, useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { ReactNativeZoomableViewWithGestures } from \"../../libs/ImageZoom\";\nimport { Icon } from \"../../icons/Icon\";\n\nexport const ImageViewerModal = ({ imageUrl, isVisible, onClose }: any) => {\n  const [downloaded, setDownloaded] = useState(false);\n  const insets = useSafeAreaInsets();\n\n  useLayoutEffect(() => {\n    // include imageUrl as dep so it re-runs when URL changes\n    if (!imageUrl) return;\n    Image.prefetch(typeof imageUrl === \"string\" ? imageUrl : imageUrl.uri).then((res) => {\n      setDownloaded(res);\n    });\n  }, [imageUrl]);\n\n  return (\n    <Modal animationType='slide' transparent={false} visible={isVisible} onRequestClose={onClose}>\n      {/* keep SafeAreaView so bottom inset is handled too */}\n      <SafeAreaView style={styles.centeredView} edges={[\"bottom\"]}>\n        <View\n          style={[\n            styles.header,\n            {\n              // push header below the notch/status bar\n              paddingTop: insets.top,\n              height: 60 + insets.top,\n            },\n          ]}\n        >\n          <TouchableOpacity\n            style={[\n              styles.closeButton,\n              {\n                // respect left inset (very useful on iPhone with rounded corners)\n                marginLeft: Math.max(10, insets.left + 6),\n              },\n            ]}\n            onPress={onClose}\n          >\n            <Icon name='arrow-back-fill' color={\"#fff\"} height={22} width={22} />\n          </TouchableOpacity>\n        </View>\n\n        <View style={styles.container}>\n          {downloaded ? (\n            <View style={styles.imageContainer}>\n              <ReactNativeZoomableViewWithGestures onSwipeDown={onClose}>\n                <Image source={imageUrl} style={styles.imageStyle} resizeMode='contain' />\n              </ReactNativeZoomableViewWithGestures>\n            </View>\n          ) : (\n            <View style={styles.loaderContainer}>\n              <ActivityIndicator color={\"#fff\"} size='large' />\n            </View>\n          )}\n        </View>\n      </SafeAreaView>\n    </Modal>\n  );\n};\n\nconst styles = StyleSheet.create({\n  centeredView: {\n    flex: 1,\n    backgroundColor: \"#000\",\n  },\n  container: {\n    flex: 1,\n    backgroundColor: \"#000\",\n  },\n  loaderContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  header: {\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n    right: 0,\n    backgroundColor: \"transparent\",\n    zIndex: 1000,\n    elevation: 50,\n    justifyContent: \"center\",\n  },\n  imageContainer: {\n    flex: 1,\n    marginVertical: 60,\n  },\n  closeButton: {\n    height: 44,\n    width: 44,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    backgroundColor: \"rgba(0,0,0,0.3)\",\n    borderRadius: 22,\n  },\n  imageStyle: {\n    width: \"100%\",\n    height: \"100%\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageViewerModal/index.tsx",
    "content": "export { ImageViewerModal } from \"./ImageViewerModal\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatImageViewerModal/resources/index.ts",
    "content": "import backIcon from \"./Back.png\";\n\nexport const ICONS = {\n  backIcon,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatInlineAudioRecorder/AudioWaveformVisualizer.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  Animated,\n  GestureResponderEvent,\n  LayoutChangeEvent,\n  PanResponder,\n  PanResponderGestureState,\n  StyleSheet,\n  View,\n} from 'react-native';\nimport { useTheme } from '../../../theme';\nimport { AudioWaveformVisualizerProps, WaveformStyle } from './types';\n\n/**\n * Default style values for the waveform visualizer.\n * Matches Figma design: thin rounded pill bars with compact spacing\n */\nconst DEFAULT_STYLE: Required<WaveformStyle> = {\n  recordingBarColor: '#6852D6', // Purple primary - will be overridden by theme\n  playedBarColor: '#6852D6', // Purple primary - will be overridden by theme\n  unplayedBarColor: '#A1A1A1', // Neutral-500 grey\n  barWidth: 2,        // Figma: 2px width\n  barSpacing: 2.694,  // Figma: ~2.694px gap between bars\n  minBarHeight: 2,    // Figma: 2px minimum height\n  maxBarHeight: 16,   // Figma: 16px maximum height\n  height: 24,         // Figma: 24px container height\n  containerStyle: {},\n};\n\n/**\n * Normalizes an amplitude value to the range [0.0, 1.0].\n * Handles edge cases like negative values and values > 1.\n * \n * @param amplitude - Raw amplitude value\n * @returns Normalized amplitude in range [0.0, 1.0]\n * @validates Requirements 2.3\n */\nexport function normalizeAmplitude(amplitude: number): number {\n  // Handle edge cases\n  if (!Number.isFinite(amplitude)) {\n    return 0;\n  }\n  // Clamp to [0, 1] range\n  return Math.max(0, Math.min(1, amplitude));\n}\n\n/**\n * Amplifies low amplitude values for better visual response.\n * WhatsApp-style: more natural variation, less aggressive amplification.\n * Quiet sounds show small bars, loud sounds show tall bars.\n * \n * @param normalizedAmplitude - Amplitude value already normalized to [0, 1]\n * @returns Amplified amplitude in range [0.0, 1.0]\n * @validates Requirements 2.4\n */\nexport function amplifyAmplitude(normalizedAmplitude: number): number {\n  // Handle edge cases\n  if (!Number.isFinite(normalizedAmplitude) || normalizedAmplitude <= 0) {\n    return 0;\n  }\n  if (normalizedAmplitude >= 1) {\n    return 1;\n  }\n  \n  // WhatsApp-style: Use a gentler curve that preserves natural variation\n  // This creates more dynamic waveforms with visible differences between quiet and loud\n  // pow(x, 0.7) gives a gentler boost than sqrt while still making quiet sounds visible\n  return Math.pow(normalizedAmplitude, 0.7);\n}\n\n/**\n * Processes a raw amplitude value through normalization and amplification.\n * \n * @param rawAmplitude - Raw amplitude value from audio source\n * @returns Processed amplitude in range [0.0, 1.0]\n * @validates Requirements 2.3, 2.4\n */\nexport function processAmplitude(rawAmplitude: number): number {\n  const normalized = normalizeAmplitude(rawAmplitude);\n  return amplifyAmplitude(normalized);\n}\n\n/**\n * Calculates seek position from touch coordinates.\n * Clamps result to [0.0, 1.0] range regardless of input.\n * \n * @param touchX - X coordinate of touch\n * @param containerWidth - Width of the waveform container\n * @returns Seek progress in range [0.0, 1.0]\n * @validates Requirements 5.1, 5.2, 5.3\n */\nexport function calculateSeekPosition(touchX: number, containerWidth: number): number {\n  // Handle edge cases\n  if (!Number.isFinite(touchX) || !Number.isFinite(containerWidth) || containerWidth <= 0) {\n    return 0;\n  }\n  \n  // Calculate progress and clamp to [0, 1]\n  const progress = touchX / containerWidth;\n  return Math.max(0, Math.min(1, progress));\n}\n\n/**\n * AudioWaveformVisualizer component displays animated waveform bars\n * during recording and playback.\n * \n * Features:\n * - Animated bars based on audio amplitude\n * - Amplitude normalization and amplification for consistent visualization\n * - Playback progress coloring (grey to purple)\n * - Touch/drag seeking support (when allowed) with smooth local state updates\n * - Scrolling effect during recording (new bars on right, scroll left)\n * \n * @validates Requirements 2.1, 2.2, 2.3, 2.4, 2.5, 4.2, 5.1, 5.2, 5.3, 5.5\n */\nexport const AudioWaveformVisualizer: React.FC<AudioWaveformVisualizerProps> = ({\n  isAnimating,\n  isPlaying: _isPlaying, // Reserved for future animation enhancements\n  playbackProgress,\n  amplitudes,\n  onSeek,\n  onSeekStart,\n  onSeekEnd,\n  allowSeeking,\n  style,\n}) => {\n  const theme = useTheme();\n  const [containerWidth, setContainerWidth] = useState(0);\n  const barAnimationsRef = useRef<Animated.Value[]>([]);\n  \n  // Local state for smooth dragging - updates immediately without waiting for native\n  const [isDragging, setIsDragging] = useState(false);\n  const [localProgress, setLocalProgress] = useState(0);\n  \n  // Merge styles with defaults and theme\n  const mergedStyle = useMemo((): Required<WaveformStyle> => {\n    const themeDefaults: WaveformStyle = {\n      recordingBarColor: theme.color.primary as string,\n      playedBarColor: theme.color.primary as string,\n      unplayedBarColor: theme.color.neutral400 as string,\n    };\n    \n    // First merge defaults with theme colors, then with user style\n    const baseStyle = { ...DEFAULT_STYLE, ...themeDefaults };\n    const finalStyle = { ...baseStyle, ...(style ?? {}) };\n    \n    return finalStyle as Required<WaveformStyle>;\n  }, [theme, style]);\n\n  // Calculate how many bars can fit in the container\n  const maxBars = useMemo(() => {\n    if (containerWidth <= 0) return 0;\n    const totalBarWidth = mergedStyle.barWidth + mergedStyle.barSpacing;\n    return Math.floor(containerWidth / totalBarWidth);\n  }, [containerWidth, mergedStyle.barWidth, mergedStyle.barSpacing]);\n\n  // Get visible amplitudes (last N amplitudes that fit in container)\n  const visibleAmplitudes = useMemo(() => {\n    if (maxBars <= 0) return [];\n    \n    // During recording, show the most recent amplitudes\n    // During playback, show all stored amplitudes\n    if (isAnimating) {\n      // Show last maxBars amplitudes during recording\n      return amplitudes.slice(-maxBars);\n    } else {\n      // During playback, distribute amplitudes across the container\n      if (amplitudes.length === 0) return [];\n      \n      // If we have fewer amplitudes than bars, use what we have\n      if (amplitudes.length <= maxBars) {\n        return amplitudes;\n      }\n      \n      // Sample amplitudes to fit the container\n      const step = amplitudes.length / maxBars;\n      const sampled: number[] = [];\n      for (let i = 0; i < maxBars; i++) {\n        const index = Math.floor(i * step);\n        sampled.push(amplitudes[index]);\n      }\n      return sampled;\n    }\n  }, [amplitudes, maxBars, isAnimating]);\n\n  // Initialize bar animations when visible amplitudes change\n  useEffect(() => {\n    const currentLength = barAnimationsRef.current.length;\n    const targetLength = visibleAmplitudes.length;\n    \n    if (targetLength > currentLength) {\n      // Add new animated values\n      for (let i = currentLength; i < targetLength; i++) {\n        barAnimationsRef.current.push(new Animated.Value(0));\n      }\n    }\n    \n    // Animate bars to their target heights\n    visibleAmplitudes.forEach((amplitude, index) => {\n      if (barAnimationsRef.current[index]) {\n        const processedAmplitude = processAmplitude(amplitude);\n        Animated.timing(barAnimationsRef.current[index], {\n          toValue: processedAmplitude,\n          duration: 100,\n          useNativeDriver: false, // Height animation requires non-native driver\n        }).start();\n      }\n    });\n  }, [visibleAmplitudes]);\n\n  // Handle layout changes to get container width\n  const handleLayout = useCallback((event: LayoutChangeEvent) => {\n    const { width } = event.nativeEvent.layout;\n    setContainerWidth(width);\n  }, []);\n\n  // Use the effective progress - local during drag, prop otherwise\n  const effectiveProgress = isDragging ? localProgress : playbackProgress;\n\n  // Ref to track dragging state for pan responder (avoids stale closure issues)\n  const isDraggingRef = useRef(false);\n  \n  // Ref to store the initial touch position and progress for accurate delta tracking\n  const gestureStartRef = useRef({ touchX: 0, progress: 0 });\n  \n  // Keep ref in sync with state\n  useEffect(() => {\n    isDraggingRef.current = isDragging;\n  }, [isDragging]);\n\n  // Pan responder for seek gestures - smooth local updates during drag\n  // Uses delta-based tracking for more accurate and smooth dragging\n  const panResponder = useMemo(() => {\n    return PanResponder.create({\n      onStartShouldSetPanResponder: () => allowSeeking,\n      onMoveShouldSetPanResponder: () => allowSeeking,\n      onPanResponderGrant: (event: GestureResponderEvent) => {\n        if (!allowSeeking || containerWidth <= 0) return;\n        \n        // Use locationX which is relative to the touched view\n        const touchX = event.nativeEvent.locationX;\n        const progress = calculateSeekPosition(touchX, containerWidth);\n        \n        // Store initial position for delta-based tracking\n        gestureStartRef.current = { touchX, progress };\n        \n        // Start dragging - use ref for immediate access in move handler\n        isDraggingRef.current = true;\n        setIsDragging(true);\n        setLocalProgress(progress);\n        \n        // Notify parent that seeking started\n        onSeekStart?.(progress);\n      },\n      onPanResponderMove: (event: GestureResponderEvent, gestureState) => {\n        // Use ref instead of state to avoid stale closure\n        if (!allowSeeking || !isDraggingRef.current || containerWidth <= 0) return;\n        \n        // Use delta-based calculation for smoother, more accurate tracking\n        // gestureState.dx gives the total horizontal distance moved since gesture started\n        const deltaProgress = gestureState.dx / containerWidth;\n        const newProgress = Math.max(0, Math.min(1, gestureStartRef.current.progress + deltaProgress));\n        \n        // Update local state immediately for smooth visual feedback\n        setLocalProgress(newProgress);\n      },\n      onPanResponderRelease: (event: GestureResponderEvent, gestureState) => {\n        if (!allowSeeking || containerWidth <= 0) return;\n        \n        // Calculate final progress using delta\n        const deltaProgress = gestureState.dx / containerWidth;\n        const finalProgress = Math.max(0, Math.min(1, gestureStartRef.current.progress + deltaProgress));\n        \n        // End dragging\n        isDraggingRef.current = false;\n        setIsDragging(false);\n        \n        // Now call the actual seek handler (which may trigger native calls)\n        onSeek?.(finalProgress);\n        onSeekEnd?.(finalProgress);\n      },\n      onPanResponderTerminate: () => {\n        // Drag was interrupted\n        isDraggingRef.current = false;\n        setIsDragging(false);\n      },\n    });\n  // Only recreate when these stable dependencies change - NOT isDragging\n  }, [allowSeeking, containerWidth, onSeek, onSeekStart, onSeekEnd]);\n\n  // Calculate bar height from amplitude\n  const getBarHeight = useCallback((amplitude: number): number => {\n    const processed = processAmplitude(amplitude);\n    const heightRange = mergedStyle.maxBarHeight - mergedStyle.minBarHeight;\n    return mergedStyle.minBarHeight + (processed * heightRange);\n  }, [mergedStyle.minBarHeight, mergedStyle.maxBarHeight]);\n\n  // Determine bar color based on playback progress\n  const getBarColor = useCallback((barIndex: number, totalBars: number): string => {\n    if (isAnimating) {\n      // During recording, all bars are recording color\n      return mergedStyle.recordingBarColor;\n    }\n    \n    if (totalBars === 0) {\n      return mergedStyle.unplayedBarColor;\n    }\n    \n    // During playback/completed, color based on progress (use effective progress for smooth drag)\n    const barProgress = (barIndex + 1) / totalBars;\n    if (barProgress <= effectiveProgress) {\n      return mergedStyle.playedBarColor;\n    }\n    return mergedStyle.unplayedBarColor;\n  }, [isAnimating, effectiveProgress, mergedStyle]);\n\n  // Render bars\n  const renderBars = useCallback(() => {\n    if (visibleAmplitudes.length === 0) {\n      // Show placeholder bars when no amplitudes\n      const placeholderCount = Math.min(maxBars, 20);\n      return Array.from({ length: placeholderCount }).map((_, index) => (\n        <View\n          key={`placeholder-${index}`}\n          style={[\n            styles.bar,\n            {\n              width: mergedStyle.barWidth,\n              height: mergedStyle.minBarHeight,\n              backgroundColor: mergedStyle.unplayedBarColor,\n              marginHorizontal: mergedStyle.barSpacing / 2,\n            },\n          ]}\n        />\n      ));\n    }\n\n    return visibleAmplitudes.map((amplitude, index) => {\n      const animatedValue = barAnimationsRef.current[index];\n      const barColor = getBarColor(index, visibleAmplitudes.length);\n      \n      if (animatedValue) {\n        // Use animated height\n        const animatedHeight = animatedValue.interpolate({\n          inputRange: [0, 1],\n          outputRange: [mergedStyle.minBarHeight, mergedStyle.maxBarHeight],\n        });\n        \n        return (\n          <Animated.View\n            key={`bar-${index}`}\n            style={[\n              styles.bar,\n              {\n                width: mergedStyle.barWidth,\n                height: animatedHeight,\n                backgroundColor: barColor,\n                marginHorizontal: mergedStyle.barSpacing / 2,\n              },\n            ]}\n          />\n        );\n      }\n      \n      // Fallback to static height\n      const height = getBarHeight(amplitude);\n      return (\n        <View\n          key={`bar-${index}`}\n          style={[\n            styles.bar,\n            {\n              width: mergedStyle.barWidth,\n              height,\n              backgroundColor: barColor,\n              marginHorizontal: mergedStyle.barSpacing / 2,\n            },\n          ]}\n        />\n      );\n    });\n  }, [visibleAmplitudes, maxBars, mergedStyle, getBarColor, getBarHeight]);\n\n  return (\n    <View\n      style={[\n        styles.container,\n        { height: mergedStyle.height },\n        mergedStyle.containerStyle,\n      ]}\n      onLayout={handleLayout}\n      {...panResponder.panHandlers}\n    >\n      <View style={styles.barsContainer}>\n        {renderBars()}\n      </View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: 'center',\n    overflow: 'hidden',\n  },\n  barsContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'flex-start',\n  },\n  bar: {\n    borderRadius: 1000, // Figma: fully rounded pill shape\n  },\n});\n\nexport default AudioWaveformVisualizer;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatInlineAudioRecorder/CometChatInlineAudioRecorder.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  Animated,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n} from 'react-native';\nimport { useTheme } from '../../../theme';\nimport { Icon } from '../../icons/Icon';\nimport { AudioWaveformVisualizer } from './AudioWaveformVisualizer';\nimport { useAudioRecorder } from './useAudioRecorder';\nimport { getInlineAudioRecorderStyle } from './style';\nimport {\n  CometChatInlineAudioRecorderProps,\n} from './types';\n\n/**\n * Formats duration in milliseconds to MM:SS format.\n * \n * @param durationMs - Duration in milliseconds\n * @returns Formatted string in MM:SS format\n * @validates Requirements 1.4, 4.3\n */\nexport function formatDuration(durationMs: number): string {\n  if (!Number.isFinite(durationMs) || durationMs < 0) {\n    return '00:00';\n  }\n  \n  const totalSeconds = Math.floor(durationMs / 1000);\n  const minutes = Math.floor(totalSeconds / 60);\n  const seconds = totalSeconds % 60;\n  \n  const paddedMinutes = String(minutes).padStart(2, '0');\n  const paddedSeconds = String(seconds).padStart(2, '0');\n  \n  return `${paddedMinutes}:${paddedSeconds}`;\n}\n\n/**\n * CometChatInlineAudioRecorder component provides an inline audio recording\n * experience within the message composer area.\n * \n * Features:\n * - UI Layout: Delete | Record/Play | Waveform | Duration | Pause/Mic | Send\n * - Red pulsing dot indicator during active recording\n * - Waveform visualization with amplitude bars\n * - Duration display in MM:SS format\n * - Button state logic based on recorder state\n * \n * @validates Requirements 1.1, 1.2, 1.5, 3.2, 8.2\n */\nexport const CometChatInlineAudioRecorder: React.FC<CometChatInlineAudioRecorderProps> = ({\n  onSubmit,\n  onCancel,\n  style,\n  deleteIcon,\n  sendIcon,\n  recordIcon,\n  pauseIcon,\n  micIcon,\n  playIcon,\n}) => {\n  const theme = useTheme();\n  const recorder = useAudioRecorder();\n  \n  // Animation value for the pulsing recording indicator\n  const pulseAnim = useRef(new Animated.Value(1)).current;\n  \n  // Local state for smooth drag feedback - updates immediately without waiting for native\n  const [isDragging, setIsDragging] = useState(false);\n  const [dragProgress, setDragProgress] = useState(0);\n  \n  // Merge styles with defaults using the style.ts function\n  // Uses CometChatThemeHelper pattern for default colors\n  const mergedStyle = useMemo(() => {\n    return getInlineAudioRecorderStyle(\n      theme.color,\n      theme.spacing,\n      theme.typography,\n      style\n    );\n  }, [theme, style]);\n\n  // Start recording automatically when component mounts\n  useEffect(() => {\n    recorder.startRecording();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // Pulsing animation for recording indicator\n  useEffect(() => {\n    let animation: Animated.CompositeAnimation | null = null;\n    \n    if (recorder.isRecording) {\n      animation = Animated.loop(\n        Animated.sequence([\n          Animated.timing(pulseAnim, {\n            toValue: 0.3,\n            duration: 500,\n            useNativeDriver: true,\n          }),\n          Animated.timing(pulseAnim, {\n            toValue: 1,\n            duration: 500,\n            useNativeDriver: true,\n          }),\n        ])\n      );\n      animation.start();\n    } else {\n      pulseAnim.setValue(1);\n    }\n\n    return () => {\n      if (animation) {\n        animation.stop();\n      }\n    };\n  }, [recorder.isRecording, pulseAnim]);\n\n  /**\n   * Handle delete button press.\n   * Cancels recording and calls onCancel callback.\n   * @validates Requirements 7.1, 7.3\n   */\n  const handleDelete = useCallback(async () => {\n    await recorder.cancel();\n    onCancel();\n  }, [recorder, onCancel]);\n\n  /**\n   * Handle send button press.\n   * Stops any active recording or playback and sends the audio file.\n   * @validates Requirements 6.1, 6.2\n   */\n  const handleSend = useCallback(async () => {\n    let filePath = recorder.filePath;\n    \n    // Stop playback if currently playing\n    if (recorder.isPlaying) {\n      await recorder.pausePlayback();\n    }\n    \n    // If still recording or paused (recording paused), stop recording first\n    if (recorder.isRecording || recorder.isPaused) {\n      filePath = await recorder.stopRecording();\n    }\n    \n    if (filePath) {\n      onSubmit(filePath);\n    }\n  }, [recorder, onSubmit]);\n\n  /**\n   * Handle record/play button press.\n   * - If playing: pause playback\n   * - If paused: start playback (preview recorded segments)\n   * - If completed: resume playback if position > 0, otherwise start from beginning\n   * @validates Requirements 4.1, 4.5, 3.4\n   */\n  const handleRecordPlayPress = useCallback(async () => {\n    if (recorder.isPlaying) {\n      // Pause playback\n      await recorder.pausePlayback();\n    } else if (recorder.isPaused && recorder.hasRecording) {\n      // WhatsApp-style: Preview recorded segments while paused\n      await recorder.startPlaybackPreview();\n    } else if (recorder.isCompleted) {\n      // If we have a position (paused mid-playback), resume from there\n      // Otherwise start from the beginning\n      if (recorder.currentPosition > 0 && recorder.currentPosition < recorder.duration) {\n        // Resume playback from current position - instant since player is already prepared\n        await recorder.resumePlayback();\n      } else {\n        // Start playback from beginning\n        await recorder.startPlayback();\n      }\n    }\n  }, [recorder]);\n\n  /**\n   * Handle pause/mic/stop button press.\n   * - Recording: pause recording (finalize segment)\n   * - Paused: continue recording (start new segment) - WhatsApp-style\n   * @validates Requirements 3.1, 3.4, 3.5\n   */\n  const handlePauseMicPress = useCallback(async () => {\n    if (recorder.isRecording) {\n      // Pause recording (finalize current segment)\n      await recorder.pauseRecording();\n    } else if (recorder.isPaused) {\n      // Continue recording (start new segment) - WhatsApp-style flow\n      await recorder.continueRecording();\n    }\n  }, [recorder]);\n\n  /**\n   * Handle seek in waveform.\n   * In preview/paused mode or completed mode, tapping starts playback from that position.\n   * @validates Requirements 5.1, 5.2\n   */\n  const handleSeek = useCallback(async (progress: number) => {\n    // If playing, just seek to the position\n    if (recorder.isPlaying) {\n      await recorder.seekTo(progress);\n      return;\n    }\n    \n    // If paused (preview mode) or completed, seek and start playback\n    if (recorder.isPaused || recorder.isCompleted) {\n      await recorder.seekAndPlay(progress);\n    }\n  }, [recorder]);\n\n  /**\n   * Handle seek start - called when user starts dragging the waveform.\n   * Updates local state for smooth visual feedback.\n   * @validates Requirements 5.1\n   */\n  const handleSeekStart = useCallback((progress: number) => {\n    setIsDragging(true);\n    setDragProgress(progress);\n  }, []);\n\n  /**\n   * Handle seek end - called when user finishes dragging the waveform.\n   * Clears local drag state.\n   * @validates Requirements 5.1\n   */\n  const handleSeekEnd = useCallback((progress: number) => {\n    setIsDragging(false);\n    setDragProgress(progress);\n  }, []);\n\n  /**\n   * Calculate playback progress for waveform visualization.\n   * Uses drag progress during dragging for smooth visual feedback.\n   */\n  const playbackProgress = useMemo(() => {\n    // During dragging, use local drag progress for smooth visual feedback\n    if (isDragging) {\n      return dragProgress;\n    }\n    if (recorder.duration <= 0) return 0;\n    return recorder.currentPosition / recorder.duration;\n  }, [isDragging, dragProgress, recorder.currentPosition, recorder.duration]);\n\n  /**\n   * Determine which duration to display.\n   * Shows drag position during dragging, playback position during playing, \n   * recording duration otherwise.\n   * @validates Requirements 1.4, 4.3\n   */\n  const displayDuration = useMemo(() => {\n    // During dragging, show the position based on drag progress\n    if (isDragging) {\n      const dragPosition = Math.floor(dragProgress * recorder.duration);\n      return formatDuration(dragPosition);\n    }\n    if (recorder.isPlaying) {\n      return formatDuration(recorder.currentPosition);\n    }\n    return formatDuration(recorder.duration);\n  }, [isDragging, dragProgress, recorder.isPlaying, recorder.currentPosition, recorder.duration]);\n\n  /**\n   * Determine if seeking is allowed.\n   * Seeking is disabled during active recording.\n   * @validates Requirements 5.5\n   */\n  const allowSeeking = useMemo(() => {\n    return !recorder.isRecording && recorder.hasRecording;\n  }, [recorder.isRecording, recorder.hasRecording]);\n\n  /**\n   * Determine if send button should be enabled.\n   * Only enabled when a recording exists.\n   * @validates Requirements 6.5\n   */\n  const isSendEnabled = recorder.hasRecording;\n\n  /**\n   * Render the recording indicator (red pulsing dot).\n   * Shown during active recording state.\n   * @validates Requirements 1.2\n   */\n  const renderRecordingIndicator = () => {\n    if (!recorder.isRecording) return null;\n    \n    return (\n      <Animated.View\n        style={[\n          styles.recordingIndicator,\n          {\n            backgroundColor: mergedStyle.recordingIndicatorColor,\n            opacity: pulseAnim,\n          },\n        ]}\n      />\n    );\n  };\n\n  /**\n   * Render the record/play button.\n   * - Recording: shows recording indicator (red dot)\n   * - Paused: shows play icon (to preview recording) - WhatsApp-style\n   * - Completed: shows play icon\n   * - Playing: shows pause icon\n   * @validates Requirements 3.2, 3.4, 4.1\n   */\n  const renderRecordPlayButton = () => {\n    // During active recording, show the recording indicator\n    if (recorder.isRecording) {\n      return (\n        <View style={styles.buttonContainer}>\n          {renderRecordingIndicator()}\n        </View>\n      );\n    }\n\n    // When paused (recording paused), show play icon for preview - WhatsApp-style\n    if (recorder.isPaused && recorder.hasRecording) {\n      return (\n        <TouchableOpacity\n          style={styles.buttonContainer}\n          onPress={handleRecordPlayPress}\n        >\n          <Icon\n            name=\"play-arrow-fill\"\n            height={mergedStyle.recordPlayButtonStyle?.iconSize}\n            width={mergedStyle.recordPlayButtonStyle?.iconSize}\n            color={mergedStyle.recordPlayButtonStyle?.iconColor}\n            icon={playIcon || recordIcon}\n          />\n        </TouchableOpacity>\n      );\n    }\n\n    // Completed or playing state - show play/pause\n    const isPauseState = recorder.isPlaying;\n    \n    let iconName: 'play-arrow-fill' | 'pause-fill' = 'play-arrow-fill';\n    let iconColor = mergedStyle.recordPlayButtonStyle?.iconColor;\n    let customIcon = playIcon || recordIcon;\n    \n    if (isPauseState) {\n      iconName = 'pause-fill';\n      iconColor = mergedStyle.pauseButtonStyle?.iconColor;\n      customIcon = pauseIcon;\n    }\n\n    // Enable button only when completed (has recording and file is finalized)\n    const isEnabled = recorder.isCompleted || recorder.isPlaying;\n\n    return (\n      <TouchableOpacity\n        style={[styles.buttonContainer, !isEnabled && styles.buttonDisabled]}\n        onPress={handleRecordPlayPress}\n        disabled={!isEnabled}\n      >\n        <Icon\n          name={iconName}\n          height={mergedStyle.recordPlayButtonStyle?.iconSize}\n          width={mergedStyle.recordPlayButtonStyle?.iconSize}\n          color={iconColor}\n          icon={customIcon}\n        />\n      </TouchableOpacity>\n    );\n  };\n\n  /**\n   * Render the pause/mic button.\n   * - Recording: shows pause icon (to pause recording)\n   * - Paused: shows mic icon (to continue recording) - WhatsApp-style\n   * - Completed/Playing: hidden (empty space)\n   * @validates Requirements 1.5, 3.2, 3.5\n   */\n  const renderPauseMicButton = () => {\n    // Only show during recording or paused states\n    if (!recorder.isRecording && !recorder.isPaused) {\n      return <View style={styles.buttonContainer} />;\n    }\n\n    // Recording state: show pause icon\n    if (recorder.isRecording) {\n      return (\n        <TouchableOpacity\n          style={styles.buttonContainer}\n          onPress={handlePauseMicPress}\n        >\n          <Icon\n            name=\"pause-fill\"\n            height={mergedStyle.pauseButtonStyle?.iconSize}\n            width={mergedStyle.pauseButtonStyle?.iconSize}\n            color={mergedStyle.pauseButtonStyle?.iconColor}\n            icon={pauseIcon}\n          />\n        </TouchableOpacity>\n      );\n    }\n\n    // Paused state: show mic icon (to continue recording) - WhatsApp-style\n    return (\n      <TouchableOpacity\n        style={styles.buttonContainer}\n        onPress={handlePauseMicPress}\n      >\n        <Icon\n          name=\"mic-fill\"\n          height={mergedStyle.micButtonStyle?.iconSize}\n          width={mergedStyle.micButtonStyle?.iconSize}\n          color={mergedStyle.micButtonStyle?.iconColor}\n          icon={micIcon}\n        />\n      </TouchableOpacity>\n    );\n  };\n\n  return (\n    <View\n      style={[\n        styles.container,\n        {\n          backgroundColor: mergedStyle.backgroundColor,\n          borderRadius: mergedStyle.borderRadius,\n        },\n        mergedStyle.containerStyle,\n        mergedStyle.border,\n      ]}\n    >\n      {/* Delete Button */}\n      <TouchableOpacity\n        style={styles.buttonContainer}\n        onPress={handleDelete}\n      >\n        <Icon\n          name=\"delete-fill\"\n          height={mergedStyle.deleteButtonStyle?.iconSize}\n          width={mergedStyle.deleteButtonStyle?.iconSize}\n          color={mergedStyle.deleteButtonStyle?.iconColor}\n          icon={deleteIcon}\n        />\n      </TouchableOpacity>\n\n      {/* Record/Play Button */}\n      {renderRecordPlayButton()}\n\n      {/* Waveform Visualizer */}\n      <View style={styles.waveformContainer}>\n        <AudioWaveformVisualizer\n          isAnimating={recorder.isRecording}\n          isPlaying={recorder.isPlaying}\n          playbackProgress={playbackProgress}\n          amplitudes={recorder.amplitudes}\n          onSeek={handleSeek}\n          onSeekStart={handleSeekStart}\n          onSeekEnd={handleSeekEnd}\n          allowSeeking={allowSeeking}\n          style={mergedStyle.waveformStyle}\n        />\n      </View>\n\n      {/* Duration Display */}\n      <Text style={[styles.duration, mergedStyle.durationTextStyle]}>\n        {displayDuration}\n      </Text>\n\n      {/* Pause/Mic Button */}\n      {renderPauseMicButton()}\n\n      {/* Send Button — circular style matching the composer send button */}\n      <TouchableOpacity\n        style={[\n          styles.sendButtonCircle,\n          {\n            backgroundColor: isSendEnabled\n              ? (theme.color.primary as string)\n              : (theme.color.background4 as string),\n          },\n          !isSendEnabled && styles.buttonDisabled,\n        ]}\n        onPress={handleSend}\n        disabled={!isSendEnabled}\n      >\n        <Icon\n          name=\"send-fill\"\n          height={mergedStyle.sendButtonStyle?.iconSize ?? 20}\n          width={mergedStyle.sendButtonStyle?.iconSize ?? 20}\n          color={\n            isSendEnabled\n              ? (theme.color.primaryButtonIcon as string)\n              : (theme.color.iconSecondary as string)\n          }\n          icon={sendIcon}\n        />\n      </TouchableOpacity>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  buttonContainer: {\n    justifyContent: 'center',\n    alignItems: 'center',\n    minWidth: 24,\n    minHeight: 24,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  waveformContainer: {\n    flex: 1,\n  },\n  duration: {\n    // Gap handles spacing between elements\n  },\n  recordingIndicator: {\n    width: 12,\n    height: 12,\n    borderRadius: 6,\n  },\n  sendButtonCircle: {\n    width: 32,\n    height: 32,\n    borderRadius: 16,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n});\n\nexport default CometChatInlineAudioRecorder;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatInlineAudioRecorder/index.ts",
    "content": "/**\n * CometChatInlineAudioRecorder - Public Exports\n * \n * This module exports the inline audio recorder component and its associated types\n * for use in the CometChat React Native UI Kit.\n * \n * @validates Requirements 8.3\n */\n\n// Main component exports\nexport { CometChatInlineAudioRecorder, formatDuration } from './CometChatInlineAudioRecorder';\nexport { default } from './CometChatInlineAudioRecorder';\n\n// Type exports\nexport type {\n  CometChatInlineAudioRecorderProps,\n  CometChatInlineAudioRecorderStyle,\n  AudioWaveformVisualizerProps,\n  WaveformStyle,\n  ButtonStyle,\n  RecorderState,\n  UseAudioRecorderReturn,\n} from './types';\n\n// Style exports\nexport {\n  getInlineAudioRecorderStyle,\n  getInlineAudioRecorderStyleLight,\n  getInlineAudioRecorderStyleDark,\n} from './style';\n\n// Internal component exports (for advanced usage)\nexport { AudioWaveformVisualizer } from './AudioWaveformVisualizer';\nexport { useAudioRecorder } from './useAudioRecorder';\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatInlineAudioRecorder/style.ts",
    "content": "import { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { CometChatInlineAudioRecorderStyle } from \"./types\";\n\n/**\n * Returns the default style configuration for the CometChatInlineAudioRecorder component\n * using theme colors from CometChatThemeHelper.\n * \n * This function provides default styling that integrates with the CometChat theme system,\n * ensuring consistent appearance across the UI Kit.\n * \n * @param color - Theme color configuration from CometChatTheme\n * @param spacing - Theme spacing configuration from CometChatTheme\n * @param typography - Theme typography configuration from CometChatTheme\n * @returns Default style configuration for the inline audio recorder\n * \n * @validates Requirements 8.3, 8.4\n */\nexport const getInlineAudioRecorderStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatInlineAudioRecorderStyle => {\n  return {\n    backgroundColor: color.background1 as string,\n    borderRadius: spacing.radius.r2,\n    recordingIndicatorColor: color.error as string,\n    containerStyle: {\n      flexDirection: 'row',\n      alignItems: 'center',\n      paddingHorizontal: 12,\n      paddingVertical: 12,\n      gap: 12, // Add spacing between elements to match composer layout\n    },\n    durationTextStyle: {\n      ...typography.caption1.medium,\n      color: color.textPrimary as string,\n      minWidth: 45,\n      textAlign: 'center',\n    },\n    deleteButtonStyle: {\n      iconColor: color.iconSecondary as string,\n      iconSize: 24,\n    },\n    sendButtonStyle: {\n      iconColor: color.primary as string,\n      iconSize: 24,\n    },\n    recordPlayButtonStyle: {\n      iconColor: color.primary as string,\n      iconSize: 24,\n    },\n    pauseButtonStyle: {\n      iconColor: color.error as string,\n      iconSize: 24,\n    },\n    micButtonStyle: {\n      iconColor: color.primary as string,\n      iconSize: 24,\n    },\n    waveformStyle: {\n      recordingBarColor: color.primary as string,\n      playedBarColor: color.primary as string,\n      unplayedBarColor: color.neutral400 as string,\n      barWidth: 3,\n      barSpacing: 2,\n      minBarHeight: 4,\n      maxBarHeight: 32, // Increased to fill more vertical space\n      height: 40, // Match the minInputHeight of the text input\n    },\n  };\n};\n\n/**\n * Returns the dark theme style configuration for the CometChatInlineAudioRecorder component.\n * \n * This function extends the light theme styles with dark mode specific overrides,\n * using deepMerge to combine the base styles with dark mode adjustments.\n * \n * @param color - Theme color configuration from CometChatTheme (dark mode)\n * @param spacing - Theme spacing configuration from CometChatTheme\n * @param typography - Theme typography configuration from CometChatTheme\n * @returns Dark theme style configuration for the inline audio recorder\n * \n * @validates Requirements 8.3, 8.4\n */\nexport const getInlineAudioRecorderStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatInlineAudioRecorderStyle => {\n  return deepMerge(getInlineAudioRecorderStyleLight(color, spacing, typography), {\n    backgroundColor: color.background1 as string,\n    durationTextStyle: {\n      color: color.textPrimary as string,\n    },\n    waveformStyle: {\n      unplayedBarColor: color.neutral500 as string,\n    },\n  });\n};\n\n/**\n * Merges custom style configuration with default theme-based styles.\n * \n * This function is the primary entry point for getting the complete style configuration\n * for the CometChatInlineAudioRecorder component. It takes the theme parameters and\n * an optional custom style prop, merging them using deepMerge to produce the final\n * style configuration.\n * \n * Usage:\n * ```typescript\n * const theme = useTheme();\n * const mergedStyle = getInlineAudioRecorderStyle(\n *   theme.color,\n *   theme.spacing,\n *   theme.typography,\n *   customStyleProp\n * );\n * ```\n * \n * @param color - Theme color configuration from CometChatTheme\n * @param spacing - Theme spacing configuration from CometChatTheme\n * @param typography - Theme typography configuration from CometChatTheme\n * @param customStyle - Optional custom style configuration to merge with defaults\n * @param isDarkMode - Whether to use dark mode styles (default: false)\n * @returns Complete merged style configuration for the inline audio recorder\n * \n * @validates Requirements 8.3, 8.4\n */\nexport const getInlineAudioRecorderStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"],\n  customStyle?: CometChatInlineAudioRecorderStyle,\n  isDarkMode: boolean = false\n): CometChatInlineAudioRecorderStyle => {\n  // Get the appropriate base style based on theme mode\n  const baseStyle = isDarkMode\n    ? getInlineAudioRecorderStyleDark(color, spacing, typography)\n    : getInlineAudioRecorderStyleLight(color, spacing, typography);\n\n  // If no custom style provided, return the base style\n  if (!customStyle) {\n    return baseStyle;\n  }\n\n  // Merge custom style with base style using deepMerge\n  return deepMerge(baseStyle, customStyle);\n};\n\nexport {\n  getInlineAudioRecorderStyleLight as getInlineAudioRecorderStylesLight,\n  getInlineAudioRecorderStyleDark as getInlineAudioRecorderStylesDark,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatInlineAudioRecorder/types.ts",
    "content": "import { ImageSourcePropType, ImageStyle, TextStyle, ViewStyle } from \"react-native\";\nimport { JSX } from \"react\";\n\n/**\n * RecorderState union type representing all possible states of the inline audio recorder.\n * \n * States:\n * - idle: Initial state, no recording in progress\n * - recording: Actively recording audio\n * - paused: Recording is paused, can be resumed\n * - completed: Recording finished, ready for playback or send\n * - playing: Playing back the recorded audio\n * - error: An error occurred during recording or playback\n * \n * @validates Requirements 9.1\n */\nexport type RecorderState = \n  | 'idle'\n  | 'recording'\n  | 'paused'\n  | 'completed'\n  | 'playing'\n  | 'error';\n\n/**\n * Represents a single audio segment in segment-based recording.\n * Each segment is a separate audio file that can be previewed and merged.\n * \n * @validates Requirements 11.7\n */\nexport interface AudioSegment {\n  /** Unique identifier for the segment */\n  id: string;\n  /** File path to the segment audio file */\n  filePath: string;\n  /** Duration of this segment in milliseconds */\n  duration: number;\n  /** Amplitude values recorded during this segment */\n  amplitudes: number[];\n  /** Timestamp when segment was created */\n  createdAt: number;\n}\n\n/**\n * State for managing multiple audio segments.\n * Used for WhatsApp-style preview-while-paused functionality.\n * \n * @validates Requirements 11.7\n */\nexport interface SegmentManagerState {\n  /** List of all recorded segments */\n  segments: AudioSegment[];\n  /** Currently recording segment (null if not recording) */\n  currentSegment: AudioSegment | null;\n  /** Total duration across all segments */\n  totalDuration: number;\n  /** Combined amplitudes from all segments for waveform display */\n  allAmplitudes: number[];\n}\n\n/**\n * Style configuration for individual buttons in the recorder.\n * \n * @validates Requirements 8.3\n */\nexport interface ButtonStyle {\n  /** Icon tint color */\n  iconColor?: string;\n  /** Background color */\n  backgroundColor?: string;\n  /** Icon size */\n  iconSize?: number;\n  /** Custom icon */\n  icon?: JSX.Element | ImageSourcePropType;\n  /** Icon image style */\n  iconStyle?: ImageStyle;\n  /** Container style */\n  containerStyle?: ViewStyle;\n}\n\n/**\n * Style configuration for the waveform visualizer.\n * \n * @validates Requirements 8.3\n */\nexport interface WaveformStyle {\n  /** Color for bars during recording */\n  recordingBarColor?: string;\n  /** Color for played portion during playback */\n  playedBarColor?: string;\n  /** Color for unplayed portion during playback */\n  unplayedBarColor?: string;\n  /** Width of each bar */\n  barWidth?: number;\n  /** Spacing between bars */\n  barSpacing?: number;\n  /** Minimum bar height */\n  minBarHeight?: number;\n  /** Maximum bar height */\n  maxBarHeight?: number;\n  /** Container height */\n  height?: number;\n  /** Container style */\n  containerStyle?: ViewStyle;\n}\n\n/**\n * Style configuration for the CometChatInlineAudioRecorder component.\n * Supports theming through CometChatThemeHelper for default styling.\n * \n * @validates Requirements 8.3, 8.4\n */\nexport interface CometChatInlineAudioRecorderStyle {\n  /** Container background color */\n  backgroundColor?: string;\n  /** Container border style */\n  border?: ViewStyle;\n  /** Container border radius */\n  borderRadius?: number;\n  /** Container style */\n  containerStyle?: ViewStyle;\n  /** Waveform style configuration */\n  waveformStyle?: WaveformStyle;\n  /** Duration text style */\n  durationTextStyle?: TextStyle;\n  /** Delete button style */\n  deleteButtonStyle?: ButtonStyle;\n  /** Send button style */\n  sendButtonStyle?: ButtonStyle;\n  /** Record/Play button style */\n  recordPlayButtonStyle?: ButtonStyle;\n  /** Pause button style */\n  pauseButtonStyle?: ButtonStyle;\n  /** Mic button style */\n  micButtonStyle?: ButtonStyle;\n  /** Recording indicator color (red pulsing dot) */\n  recordingIndicatorColor?: string;\n}\n\n/**\n * Props for the AudioWaveformVisualizer component.\n * Displays animated waveform bars during recording and playback.\n * \n * @validates Requirements 2.1, 2.2, 2.3, 2.4, 2.5, 4.2, 5.1, 5.2, 5.3, 5.5\n */\nexport interface AudioWaveformVisualizerProps {\n  /** Whether waveform should animate (during recording) */\n  isAnimating: boolean;\n  /** Whether audio is currently playing */\n  isPlaying: boolean;\n  /** Playback progress (0.0 to 1.0) */\n  playbackProgress: number;\n  /** Amplitude values for visualization */\n  amplitudes: number[];\n  /** Callback when user taps/drags to seek (called on release) */\n  onSeek?: (progress: number) => void;\n  /** Callback when user starts dragging (for smooth UI updates) */\n  onSeekStart?: (progress: number) => void;\n  /** Callback when user finishes dragging (for smooth UI updates) */\n  onSeekEnd?: (progress: number) => void;\n  /** Whether seeking is allowed (disabled during recording) */\n  allowSeeking: boolean;\n  /** Style configuration */\n  style?: WaveformStyle;\n}\n\n/**\n * Props for the CometChatInlineAudioRecorder component.\n * Main component that orchestrates the inline recording experience.\n * \n * @validates Requirements 1.1, 1.2, 1.5, 3.2, 6.1, 6.2, 7.1, 7.3, 8.2, 8.3, 8.5\n */\nexport interface CometChatInlineAudioRecorderProps {\n  /** Callback when user sends the recorded audio */\n  onSubmit: (filePath: string) => void;\n  /** Callback when user cancels/deletes the recording */\n  onCancel: () => void;\n  /** Custom style configuration */\n  style?: CometChatInlineAudioRecorderStyle;\n  /** Custom delete button icon */\n  deleteIcon?: JSX.Element | ImageSourcePropType;\n  /** Custom send button icon */\n  sendIcon?: JSX.Element | ImageSourcePropType;\n  /** Custom record/play button icon */\n  recordIcon?: JSX.Element | ImageSourcePropType;\n  /** Custom pause button icon */\n  pauseIcon?: JSX.Element | ImageSourcePropType;\n  /** Custom mic button icon */\n  micIcon?: JSX.Element | ImageSourcePropType;\n  /** Custom play button icon */\n  playIcon?: JSX.Element | ImageSourcePropType;\n}\n\n/**\n * Return type for the useAudioRecorder custom hook.\n * Encapsulates all recording logic and native module interaction.\n * \n * @validates Requirements 9.1, 9.2, 9.3, 10.1, 10.5, 11.7\n */\nexport interface UseAudioRecorderReturn {\n  /** Current recorder state */\n  state: RecorderState;\n  /** Current recording/playback duration in milliseconds */\n  duration: number;\n  /** Current playback position in milliseconds */\n  currentPosition: number;\n  /** Recorded file path (available after recording stops) */\n  filePath: string | null;\n  /** Error message if any */\n  error: string | null;\n  /** Amplitude values for waveform visualization */\n  amplitudes: number[];\n  /** List of recorded segments */\n  segments: AudioSegment[];\n  /** Whether there are multiple segments (user has paused and continued) */\n  hasMultipleSegments: boolean;\n  /** Start recording */\n  startRecording: () => Promise<void>;\n  /** Pause recording (finalizes current segment for preview) */\n  pauseRecording: () => Promise<void>;\n  /** Resume recording (legacy - use continueRecording for segment-based) */\n  resumeRecording: () => Promise<void>;\n  /** Continue recording (starts new segment after pause) */\n  continueRecording: () => Promise<void>;\n  /** Stop recording and finalize all segments */\n  stopRecording: () => Promise<string | null>;\n  /** Start playback of all segments */\n  startPlayback: () => Promise<void>;\n  /** Start playback preview while paused (plays segments without stopping recording) */\n  startPlaybackPreview: () => Promise<void>;\n  /** Pause playback */\n  pausePlayback: () => Promise<void>;\n  /** Resume playback */\n  resumePlayback: () => Promise<void>;\n  /** Seek to position (0.0 to 1.0) */\n  seekTo: (progress: number) => Promise<void>;\n  /** Seek to position and start playback (for waveform tap) */\n  seekAndPlay: (progress: number) => Promise<void>;\n  /** Cancel and cleanup all segments */\n  cancel: () => Promise<void>;\n  /** Computed state check: is currently recording */\n  isRecording: boolean;\n  /** Computed state check: is currently paused */\n  isPaused: boolean;\n  /** Computed state check: is currently playing */\n  isPlaying: boolean;\n  /** Computed state check: recording is completed */\n  isCompleted: boolean;\n  /** Computed state check: has a recording (duration > 0) */\n  hasRecording: boolean;\n  /** Can continue recording (in paused state) */\n  canContinueRecording: boolean;\n}\n\n/**\n * Action types for the useAudioRecorder reducer.\n * Used for predictable state management across child components.\n * \n * @validates Requirements 9.5, 11.7\n */\nexport type RecorderAction =\n  | { type: 'START_RECORDING' }\n  | { type: 'PAUSE_RECORDING' }\n  | { type: 'RESUME_RECORDING' }\n  | { type: 'STOP_RECORDING'; filePath: string }\n  | { type: 'START_PLAYBACK' }\n  | { type: 'PAUSE_PLAYBACK' }\n  | { type: 'RESUME_PLAYBACK' }\n  | { type: 'PLAYBACK_COMPLETE' }\n  | { type: 'SEEK'; position: number }\n  | { type: 'UPDATE_DURATION'; duration: number }\n  | { type: 'UPDATE_POSITION'; position: number }\n  | { type: 'ADD_AMPLITUDE'; amplitude: number }\n  | { type: 'SET_ERROR'; error: string }\n  | { type: 'RESET' }\n  // Segment-related actions\n  | { type: 'FINALIZE_SEGMENT'; segment: AudioSegment }\n  | { type: 'START_NEW_SEGMENT' }\n  | { type: 'CLEAR_SEGMENTS' };\n\n/**\n * State shape for the useAudioRecorder reducer.\n * \n * @validates Requirements 9.1, 9.3, 11.7\n */\nexport interface RecorderReducerState {\n  /** Current recorder state */\n  state: RecorderState;\n  /** Current recording/playback duration in milliseconds */\n  duration: number;\n  /** Current playback position in milliseconds */\n  currentPosition: number;\n  /** Recorded file path */\n  filePath: string | null;\n  /** Error message if any */\n  error: string | null;\n  /** Amplitude values for waveform visualization */\n  amplitudes: number[];\n  /** List of recorded segments */\n  segments: AudioSegment[];\n  /** Whether we're in preview mode (playing from paused state) */\n  isPreviewMode?: boolean;\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatInlineAudioRecorder/useAudioRecorder.ts",
    "content": "import { useReducer, useEffect, useRef, useCallback, useMemo } from 'react';\nimport { NativeModules, Platform, PermissionsAndroid, Alert, Linking, NativeEventEmitter } from 'react-native';\nimport {\n  RecorderAction,\n  RecorderReducerState,\n  UseAudioRecorderReturn,\n  AudioSegment,\n} from './types';\n\n// Create event emitter for FileManager\nconst FileManagerEmitter = new NativeEventEmitter(NativeModules.FileManager);\n\n/**\n * Initial state for the audio recorder reducer.\n * @validates Requirements 9.1, 11.7\n */\nconst initialState: RecorderReducerState = {\n  state: 'idle',\n  duration: 0,\n  currentPosition: 0,\n  filePath: null,\n  error: null,\n  amplitudes: [],\n  segments: [],\n  isPreviewMode: false,\n};\n\n/**\n * Reducer function for managing audio recorder state.\n * Implements predictable state management with valid state transitions.\n * \n * @validates Requirements 9.1, 9.2, 9.5, 11.7\n */\nfunction recorderReducer(\n  state: RecorderReducerState,\n  action: RecorderAction\n): RecorderReducerState {\n  switch (action.type) {\n    case 'START_RECORDING':\n      // Only allow starting recording from idle or error states\n      if (state.state !== 'idle' && state.state !== 'error') return state;\n      return {\n        ...state,\n        state: 'recording',\n        error: null,\n        amplitudes: [],\n        duration: 0,\n        currentPosition: 0,\n        segments: [],\n      };\n\n    case 'PAUSE_RECORDING':\n      if (state.state !== 'recording') return state;\n      return {\n        ...state,\n        state: 'paused',\n      };\n\n    case 'RESUME_RECORDING':\n      if (state.state !== 'paused') return state;\n      return {\n        ...state,\n        state: 'recording',\n      };\n\n    case 'STOP_RECORDING':\n      if (state.state !== 'recording' && state.state !== 'paused') return state;\n      return {\n        ...state,\n        state: 'completed',\n        filePath: action.filePath,\n      };\n\n    case 'START_PLAYBACK':\n      if (state.state !== 'completed' && state.state !== 'paused') return state;\n      return {\n        ...state,\n        state: 'playing',\n        currentPosition: 0,\n        isPreviewMode: state.state === 'paused', // Track if we started from paused (preview mode)\n      };\n\n    case 'PAUSE_PLAYBACK':\n      if (state.state !== 'playing') return state;\n      return {\n        ...state,\n        // Return to paused if we were in preview mode, otherwise completed\n        state: state.isPreviewMode ? 'paused' : 'completed',\n        isPreviewMode: false,\n      };\n\n    case 'RESUME_PLAYBACK':\n      if (state.state !== 'completed') return state;\n      return {\n        ...state,\n        state: 'playing',\n        isPreviewMode: false,\n      };\n\n    case 'PLAYBACK_COMPLETE':\n      if (state.state !== 'playing') return state;\n      return {\n        ...state,\n        // Return to paused if we were in preview mode, otherwise completed\n        state: state.isPreviewMode ? 'paused' : 'completed',\n        currentPosition: state.duration,\n        isPreviewMode: false,\n      };\n\n    case 'SEEK':\n      return {\n        ...state,\n        currentPosition: action.position,\n      };\n\n    case 'UPDATE_DURATION':\n      return {\n        ...state,\n        duration: action.duration,\n      };\n\n    case 'UPDATE_POSITION':\n      return {\n        ...state,\n        currentPosition: action.position,\n      };\n\n    case 'ADD_AMPLITUDE':\n      return {\n        ...state,\n        amplitudes: [...state.amplitudes, action.amplitude],\n      };\n\n    case 'SET_ERROR':\n      return {\n        ...state,\n        state: 'error',\n        error: action.error,\n      };\n\n    case 'RESET':\n      return initialState;\n\n    // Segment-related actions\n    case 'FINALIZE_SEGMENT':\n      return {\n        ...state,\n        segments: [...state.segments, action.segment],\n      };\n\n    case 'START_NEW_SEGMENT':\n      return {\n        ...state,\n        state: 'recording',\n      };\n\n    case 'CLEAR_SEGMENTS':\n      return {\n        ...state,\n        segments: [],\n      };\n\n    default:\n      return state;\n  }\n}\n\n/**\n * Custom hook for managing audio recording and playback.\n * Wraps the FileManager native module with React state management.\n * \n * Features:\n * - State management with useReducer for predictable state transitions\n * - Duration timer that increments during recording\n * - Simulated amplitude generation for waveform visualization\n * - Cleanup on unmount to release native resources\n * \n * @validates Requirements 9.1, 9.2, 9.3, 10.1, 10.5\n */\nexport function useAudioRecorder(): UseAudioRecorderReturn {\n  const [reducerState, dispatch] = useReducer(recorderReducer, initialState);\n  \n  // Refs for timers and intervals\n  const durationTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n  const amplitudeTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n  const playbackTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n  const recordingStartTimeRef = useRef<number>(0);\n  const pausedDurationRef = useRef<number>(0);\n  \n  // Seek operation tracking to prevent race conditions\n  const seekOperationIdRef = useRef<number>(0);\n  const isSeekingRef = useRef<boolean>(false);\n  const seekDebounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const pendingSeekRef = useRef<{ progress: number; operationId: number } | null>(null);\n  \n  // Debug: Track last requested seek position to detect unexpected resets\n  const lastRequestedPositionRef = useRef<number>(0);\n  const lastRequestedProgressRef = useRef<number>(0);\n\n  /**\n   * Cleanup function to release all native audio resources.\n   * Called on unmount and when canceling recording.\n   * \n   * @validates Requirements 10.5\n   */\n  const cleanup = useCallback(() => {\n    // Clear all timers\n    if (durationTimerRef.current) {\n      clearInterval(durationTimerRef.current);\n      durationTimerRef.current = null;\n    }\n    if (amplitudeTimerRef.current) {\n      clearInterval(amplitudeTimerRef.current);\n      amplitudeTimerRef.current = null;\n    }\n    if (playbackTimerRef.current) {\n      clearInterval(playbackTimerRef.current);\n      playbackTimerRef.current = null;\n    }\n    if (seekDebounceTimerRef.current) {\n      clearTimeout(seekDebounceTimerRef.current);\n      seekDebounceTimerRef.current = null;\n    }\n\n    // Release native resources\n    NativeModules.FileManager.releaseMediaResources(() => {\n      // Resources released\n    });\n  }, []);\n\n  /**\n   * Cleanup on unmount.\n   * @validates Requirements 10.5\n   */\n  useEffect(() => {\n    return () => {\n      cleanup();\n    };\n  }, [cleanup]);\n\n  /**\n   * Duration timer effect - increments duration during recording state.\n   * Replicates the EXACT logic from the old Timer component in CometChatMediaRecorder/Timer.tsx:\n   * - Uses a local ref to track time in milliseconds\n   * - Increments by 1000ms every second when recording (not paused)\n   * - Freezes (stops incrementing) when paused - time value is preserved\n   * - Resets to 0 when going back to idle state\n   * \n   * The old Timer component:\n   * - Used `time` state in seconds, incremented by 1 every 1000ms\n   * - We use milliseconds for consistency with the rest of the codebase\n   * \n   * @validates Requirements 1.4, 3.1\n   */\n  const timerValueRef = useRef<number>(0);\n  \n  useEffect(() => {\n    if (reducerState.state === 'recording') {\n      // Not paused -> start counting (increment every 1 second like old Timer)\n      // This matches: intervalRef.current = setInterval(() => { setTime((prev) => prev + 1); }, 1000);\n      durationTimerRef.current = setInterval(() => {\n        timerValueRef.current += 1000; // Increment by 1000ms (1 second)\n        dispatch({ type: 'UPDATE_DURATION', duration: timerValueRef.current });\n      }, 1000);\n    } else if (reducerState.state === 'paused') {\n      // Paused -> clear interval, so time is frozen (but value preserved in timerValueRef)\n      // This matches the old Timer's behavior when paused=true\n      if (durationTimerRef.current) {\n        clearInterval(durationTimerRef.current);\n        durationTimerRef.current = null;\n      }\n      // Note: timerValueRef.current is NOT reset here - time is frozen but preserved\n    } else if (reducerState.state === 'idle') {\n      // Reset timer when going back to idle (like resetKey changing in old Timer)\n      timerValueRef.current = 0;\n      if (durationTimerRef.current) {\n        clearInterval(durationTimerRef.current);\n        durationTimerRef.current = null;\n      }\n    } else {\n      // For other states (completed, playing, error), just stop the interval\n      // but preserve the duration value\n      if (durationTimerRef.current) {\n        clearInterval(durationTimerRef.current);\n        durationTimerRef.current = null;\n      }\n    }\n\n    // Cleanup when unmounting or when effect re-runs\n    return () => {\n      if (durationTimerRef.current) {\n        clearInterval(durationTimerRef.current);\n        durationTimerRef.current = null;\n      }\n    };\n  }, [reducerState.state]);\n\n  /**\n   * Amplitude listening effect - receives real-time amplitude data from native module.\n   * The native module streams audio amplitude values (0.0 to 1.0) during recording.\n   * Uses WhatsApp-style amplitude processing for natural waveform visualization.\n   * \n   * @validates Requirements 2.1, 2.5\n   */\n  useEffect(() => {\n    let subscription: { remove: () => void } | null = null;\n    \n    if (reducerState.state === 'recording') {\n      // Listen for native amplitude events\n      subscription = FileManagerEmitter.addListener('audioAmplitude', (event: { amplitude: number }) => {\n        const amplitude = event.amplitude;\n        \n        // WhatsApp-style amplitude processing:\n        // - Use the raw amplitude directly (already normalized 0-1 from native)\n        // - Apply minimal amplification to make quiet sounds visible\n        // - Keep the natural variation for authentic waveform look\n        \n        // Minimum floor so bars are always visible, max cap at 1.0\n        // Use a gentler curve: sqrt for low values, linear for higher\n        let visualAmplitude: number;\n        if (amplitude < 0.05) {\n          // Very quiet - show minimal bar\n          visualAmplitude = 0.1 + amplitude * 2;\n        } else if (amplitude < 0.3) {\n          // Quiet to moderate - gentle boost\n          visualAmplitude = 0.2 + amplitude * 1.5;\n        } else {\n          // Moderate to loud - mostly linear\n          visualAmplitude = 0.35 + amplitude * 0.65;\n        }\n        \n        // Clamp to valid range\n        const clampedAmplitude = Math.max(0.1, Math.min(1.0, visualAmplitude));\n        \n        dispatch({ type: 'ADD_AMPLITUDE', amplitude: clampedAmplitude });\n      });\n    }\n\n    return () => {\n      if (subscription) {\n        subscription.remove();\n      }\n    };\n  }, [reducerState.state]);\n\n  /**\n   * Playback position timer effect - updates current position during playback.\n   * Uses native getPlaybackPosition for accurate sync with actual audio playback.\n   * Also updates duration from native to ensure waveform matches actual audio length.\n   * Ignores position updates during active seeking to prevent race conditions.\n   * \n   * @validates Requirements 4.3\n   */\n  useEffect(() => {\n    let statusSubscription: { remove: () => void } | null = null;\n    \n    if (reducerState.state === 'playing') {\n      // Listen for playback complete event from native\n      statusSubscription = FileManagerEmitter.addListener('status', (event: { state: string }) => {\n        // Ignore playback complete during seeking - it might be stale\n        if (event.state === 'playbackComplete' && !isSeekingRef.current) {\n          dispatch({ type: 'PLAYBACK_COMPLETE' });\n          if (playbackTimerRef.current) {\n            clearInterval(playbackTimerRef.current);\n            playbackTimerRef.current = null;\n          }\n        }\n      });\n      \n      // Poll native for actual playback position and duration\n      playbackTimerRef.current = setInterval(() => {\n        // Skip position updates during active seeking to prevent race conditions\n        if (isSeekingRef.current) {\n          return;\n        }\n        \n        NativeModules.FileManager.getPlaybackPosition((result: string) => {\n          // Double-check we're not seeking when the callback returns\n          if (isSeekingRef.current) {\n            return;\n          }\n          \n          try {\n            const response = JSON.parse(result);\n            if (response.success && typeof response.position === 'number') {\n              const position = response.position;\n              const nativeDuration = response.duration;\n              \n              // Update duration from native if available and different\n              // This ensures waveform matches actual audio length\n              if (typeof nativeDuration === 'number' && nativeDuration > 0 && nativeDuration !== reducerState.duration) {\n                dispatch({ type: 'UPDATE_DURATION', duration: nativeDuration });\n              }\n              \n              // Use native duration for completion check if available\n              const effectiveDuration = (typeof nativeDuration === 'number' && nativeDuration > 0) \n                ? nativeDuration \n                : reducerState.duration;\n              \n              // Check if playback is complete (only if not seeking)\n              if (position >= effectiveDuration && !isSeekingRef.current) {\n                dispatch({ type: 'PLAYBACK_COMPLETE' });\n                if (playbackTimerRef.current) {\n                  clearInterval(playbackTimerRef.current);\n                  playbackTimerRef.current = null;\n                }\n              } else {\n                dispatch({ type: 'UPDATE_POSITION', position });\n              }\n            }\n          } catch (e) {\n            // Fallback to time-based estimation if native call fails\n          }\n        });\n      }, 100);\n    } else {\n      if (playbackTimerRef.current) {\n        clearInterval(playbackTimerRef.current);\n        playbackTimerRef.current = null;\n      }\n    }\n\n    return () => {\n      if (playbackTimerRef.current) {\n        clearInterval(playbackTimerRef.current);\n        playbackTimerRef.current = null;\n      }\n      if (statusSubscription) {\n        statusSubscription.remove();\n      }\n    };\n  }, [reducerState.state, reducerState.duration]);\n\n  /**\n   * Check and request microphone permission.\n   * Reuses existing permission handling patterns from CometChatMediaRecorder.\n   * \n   * @validates Requirements 10.4\n   */\n  const checkMicrophonePermission = useCallback(async (): Promise<boolean> => {\n    if (Platform.OS === 'ios') {\n      // iOS handles permission in native module\n      return true;\n    }\n\n    // Android permission check\n    const hasPermission = await PermissionsAndroid.check(\n      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO\n    );\n\n    if (hasPermission) {\n      return true;\n    }\n\n    // Request permission\n    const granted = await PermissionsAndroid.request(\n      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO\n    );\n\n    return granted === PermissionsAndroid.RESULTS.GRANTED;\n  }, []);\n\n  /**\n   * Show permission alert with option to open settings.\n   */\n  const showPermissionAlert = useCallback(() => {\n    Alert.alert(\n      '',\n      'Microphone permission is required to record audio. Please enable it in settings.',\n      [\n        {\n          style: 'cancel',\n          text: 'Cancel',\n        },\n        {\n          style: 'default',\n          text: 'Settings',\n          onPress: () => {\n            Linking.openSettings();\n          },\n        },\n      ]\n    );\n  }, []);\n\n  /**\n   * Start recording audio.\n   * Wraps FileManager.startRecording native call.\n   * \n   * @validates Requirements 10.1\n   */\n  const startRecording = useCallback(async (): Promise<void> => {\n    try {\n      const hasPermission = await checkMicrophonePermission();\n      \n      if (!hasPermission) {\n        showPermissionAlert();\n        dispatch({ type: 'SET_ERROR', error: 'Microphone permission denied' });\n        return;\n      }\n\n      // Reset state for new recording\n      pausedDurationRef.current = 0;\n      recordingStartTimeRef.current = 0;\n      // Reset timer value for new recording (like resetKey in old Timer)\n      timerValueRef.current = 0;\n\n      return new Promise((resolve, reject) => {\n        NativeModules.FileManager.startRecording((result: string) => {\n          try {\n            const response = JSON.parse(result);\n            \n            if (response.granted === false) {\n              showPermissionAlert();\n              dispatch({ type: 'SET_ERROR', error: 'Microphone permission denied' });\n              reject(new Error('Microphone permission denied'));\n              return;\n            }\n\n            if (response.success === false) {\n              dispatch({ type: 'SET_ERROR', error: response.error || 'Failed to start recording' });\n              reject(new Error(response.error || 'Failed to start recording'));\n              return;\n            }\n\n            dispatch({ type: 'START_RECORDING' });\n            resolve();\n          } catch (parseError) {\n            // If parsing fails, assume success (some platforms return non-JSON)\n            dispatch({ type: 'START_RECORDING' });\n            resolve();\n          }\n        });\n      });\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : 'Failed to start recording';\n      dispatch({ type: 'SET_ERROR', error: errorMessage });\n      throw error;\n    }\n  }, [checkMicrophonePermission, showPermissionAlert, reducerState.state]);\n\n  /**\n   * Pause recording.\n   * Wraps FileManager.finalizeSegment native call for segment-based recording.\n   * This finalizes the current segment so it can be previewed.\n   * Note: Android API < 24 does not support true pause.\n   * \n   * @validates Requirements 10.1, 10.3, 11.1\n   */\n  const pauseRecording = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'recording') {\n      return;\n    }\n\n    try {\n      // Save current amplitudes for this segment\n      const currentAmplitudes = [...reducerState.amplitudes];\n      const segmentDuration = reducerState.duration - getTotalSegmentsDuration();\n      \n      return new Promise((resolve, reject) => {\n        NativeModules.FileManager.finalizeSegment((result: string) => {\n          try {\n            const response = JSON.parse(result);\n            \n            if (response.success === false) {\n              // Fallback to legacy pause\n              NativeModules.FileManager.pauseRecording()\n                .then(() => {\n                  pausedDurationRef.current = reducerState.duration;\n                  dispatch({ type: 'PAUSE_RECORDING' });\n                  resolve();\n                })\n                .catch((err: Error) => {\n                  dispatch({ type: 'SET_ERROR', error: err.message });\n                  reject(err);\n                });\n              return;\n            }\n            \n            // Create segment from finalized recording\n            const segment: AudioSegment = {\n              id: `segment-${Date.now()}`,\n              filePath: response.segmentPath,\n              duration: segmentDuration > 0 ? segmentDuration : (response.duration * 1000) || 0,\n              amplitudes: currentAmplitudes.slice(\n                reducerState.segments.reduce((acc, s) => acc + s.amplitudes.length, 0)\n              ),\n              createdAt: Date.now(),\n            };\n            \n            dispatch({ type: 'FINALIZE_SEGMENT', segment });\n            pausedDurationRef.current = reducerState.duration;\n            dispatch({ type: 'PAUSE_RECORDING' });\n            resolve();\n          } catch (parseError) {\n            // Fallback to legacy pause\n            NativeModules.FileManager.pauseRecording()\n              .then(() => {\n                pausedDurationRef.current = reducerState.duration;\n                dispatch({ type: 'PAUSE_RECORDING' });\n                resolve();\n              })\n              .catch((err: Error) => {\n                dispatch({ type: 'SET_ERROR', error: err.message });\n                reject(err);\n              });\n          }\n        });\n      });\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : 'Failed to pause recording';\n      dispatch({ type: 'SET_ERROR', error: errorMessage });\n      throw error;\n    }\n  }, [reducerState.state, reducerState.duration, reducerState.amplitudes, reducerState.segments]);\n\n  /**\n   * Helper function to get total duration of all finalized segments.\n   */\n  const getTotalSegmentsDuration = useCallback((): number => {\n    return reducerState.segments.reduce((acc, segment) => acc + segment.duration, 0);\n  }, [reducerState.segments]);\n\n  /**\n   * Resume recording.\n   * Wraps FileManager.resumeRecording native call.\n   * \n   * @validates Requirements 10.1, 10.3\n   */\n  const resumeRecording = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'paused') {\n      return;\n    }\n\n    try {\n      await NativeModules.FileManager.resumeRecording();\n      \n      // Reset the recording start time for the new segment\n      recordingStartTimeRef.current = Date.now();\n      \n      dispatch({ type: 'RESUME_RECORDING' });\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : 'Failed to resume recording';\n      dispatch({ type: 'SET_ERROR', error: errorMessage });\n      throw error;\n    }\n  }, [reducerState.state]);\n\n  /**\n   * Continue recording by starting a new segment.\n   * This is the WhatsApp-style flow: pause -> preview -> continue recording.\n   * Creates a new audio file for the next segment.\n   * \n   * @validates Requirements 11.2, 3.5\n   */\n  const continueRecording = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'paused') {\n      return;\n    }\n\n    try {\n      return new Promise((resolve, reject) => {\n        NativeModules.FileManager.startNewSegment((result: string) => {\n          try {\n            const response = JSON.parse(result);\n            \n            if (response.success === false) {\n              // Fallback to legacy resume\n              NativeModules.FileManager.resumeRecording()\n                .then(() => {\n                  recordingStartTimeRef.current = Date.now();\n                  dispatch({ type: 'RESUME_RECORDING' });\n                  resolve();\n                })\n                .catch((err: Error) => {\n                  dispatch({ type: 'SET_ERROR', error: err.message });\n                  reject(err);\n                });\n              return;\n            }\n            \n            // Reset the recording start time for the new segment\n            recordingStartTimeRef.current = Date.now();\n            \n            dispatch({ type: 'START_NEW_SEGMENT' });\n            resolve();\n          } catch (parseError) {\n            // Fallback to legacy resume\n            NativeModules.FileManager.resumeRecording()\n              .then(() => {\n                recordingStartTimeRef.current = Date.now();\n                dispatch({ type: 'RESUME_RECORDING' });\n                resolve();\n              })\n              .catch((err: Error) => {\n                dispatch({ type: 'SET_ERROR', error: err.message });\n                reject(err);\n              });\n          }\n        });\n      });\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : 'Failed to continue recording';\n      dispatch({ type: 'SET_ERROR', error: errorMessage });\n      throw error;\n    }\n  }, [reducerState.state, reducerState.segments.length]);\n\n  /**\n   * Stop recording and finalize all segments.\n   * If multiple segments exist, merges them into a single audio file.\n   * Wraps FileManager.releaseMediaResources and mergeSegments native calls.\n   * \n   * @validates Requirements 10.1, 11.4\n   */\n  const stopRecording = useCallback(async (): Promise<string | null> => {\n    if (reducerState.state !== 'recording' && reducerState.state !== 'paused') {\n      return null;\n    }\n\n    // Calculate final duration before stopping\n    let finalDuration = reducerState.duration;\n    if (reducerState.state === 'recording' && recordingStartTimeRef.current > 0) {\n      finalDuration = pausedDurationRef.current + (Date.now() - recordingStartTimeRef.current);\n    }\n\n    return new Promise((resolve) => {\n      NativeModules.FileManager.releaseMediaResources((result: string) => {\n        try {\n          const response = JSON.parse(result);\n          \n          let filePath = response.file || response.path || null;\n          \n          // Check if we have multiple segments to merge\n          const allSegmentPaths = [...reducerState.segments.map(s => s.filePath)];\n          if (filePath && reducerState.state === 'recording') {\n            // Add the current recording as the last segment\n            allSegmentPaths.push(filePath);\n          }\n          \n          if (allSegmentPaths.length > 1) {\n            // Merge all segments\n            NativeModules.FileManager.mergeSegments(allSegmentPaths, (mergeResult: string) => {\n              try {\n                const mergeResponse = JSON.parse(mergeResult);\n                if (mergeResponse.success && mergeResponse.mergedPath) {\n                  filePath = mergeResponse.mergedPath;\n                }\n              } catch (mergeParseError) {\n                // Keep original filePath\n              }\n              \n              finishStopRecording(filePath, finalDuration, response, resolve);\n            });\n          } else {\n            finishStopRecording(filePath, finalDuration, response, resolve);\n          }\n        } catch (parseError) {\n          dispatch({ type: 'SET_ERROR', error: 'Failed to parse recording result' });\n          resolve(null);\n        }\n      });\n    });\n  }, [reducerState.state, reducerState.duration, reducerState.segments]);\n\n  /**\n   * Helper function to finish stop recording process.\n   */\n  const finishStopRecording = useCallback((\n    filePath: string | null,\n    finalDuration: number,\n    response: { duration?: number },\n    resolve: (value: string | null) => void\n  ) => {\n    if (filePath) {\n      // Update duration from native if available (iOS provides this)\n      if (response.duration && response.duration > 0) {\n        const nativeDuration = response.duration * 1000;\n        dispatch({ type: 'UPDATE_DURATION', duration: nativeDuration });\n      } else {\n        // Use our calculated duration\n        dispatch({ type: 'UPDATE_DURATION', duration: finalDuration });\n      }\n      dispatch({ type: 'STOP_RECORDING', filePath });\n      resolve(filePath);\n    } else {\n      dispatch({ type: 'SET_ERROR', error: 'No file path returned' });\n      resolve(null);\n    }\n  }, []);\n\n  /**\n   * Start playback of recorded audio.\n   * Wraps FileManager.playAudio native call.\n   * \n   * @validates Requirements 10.1\n   */\n  const startPlayback = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'completed') {\n      return;\n    }\n\n    if (!reducerState.filePath) {\n      return;\n    }\n\n    return new Promise((resolve, reject) => {\n      NativeModules.FileManager.playAudio((result: string) => {\n        try {\n          const response = JSON.parse(result);\n          \n          if (response.success === false) {\n            dispatch({ type: 'SET_ERROR', error: response.error || 'Failed to start playback' });\n            reject(new Error(response.error || 'Failed to start playback'));\n            return;\n          }\n\n          // Reset tracking refs since we're starting from 0\n          lastRequestedPositionRef.current = 0;\n          lastRequestedProgressRef.current = 0;\n          \n          dispatch({ type: 'START_PLAYBACK' });\n          resolve();\n        } catch (parseError) {\n          // Assume success if parsing fails\n          dispatch({ type: 'START_PLAYBACK' });\n          resolve();\n        }\n      });\n    });\n  }, [reducerState.state, reducerState.filePath]);\n\n  /**\n   * Start playback preview while paused.\n   * Plays the finalized segments without stopping the recording session.\n   * For multiple segments, merges them first for gapless playback.\n   * This allows the user to preview what they've recorded while still being able to continue.\n   * \n   * @validates Requirements 3.4, 4.1\n   */\n  const startPlaybackPreview = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'paused') {\n      return;\n    }\n\n    // Get all segment paths to play\n    const segmentPaths = reducerState.segments.map(s => s.filePath);\n\n    if (segmentPaths.length === 0) {\n      return;\n    }\n\n    // Reset tracking refs since we're starting from 0\n    lastRequestedPositionRef.current = 0;\n    lastRequestedProgressRef.current = 0;\n\n    return new Promise((resolve, reject) => {\n      if (segmentPaths.length === 1) {\n        // Single segment - use playAudio directly with the segment path\n        NativeModules.FileManager.playAudio((result: string) => {\n          try {\n            const response = JSON.parse(result);\n            if (response.success === false) {\n              dispatch({ type: 'SET_ERROR', error: response.error || 'Failed to start playback' });\n              reject(new Error(response.error || 'Failed to start playback'));\n              return;\n            }\n            dispatch({ type: 'START_PLAYBACK' });\n            resolve();\n          } catch (parseError) {\n            dispatch({ type: 'START_PLAYBACK' });\n            resolve();\n          }\n        });\n      } else {\n        // Multiple segments - merge first for gapless playback, then play\n        NativeModules.FileManager.mergeSegments(segmentPaths, (mergeResult: string) => {\n          try {\n            const mergeResponse = JSON.parse(mergeResult);\n            if (mergeResponse.success && mergeResponse.mergedPath) {\n              // Play the merged file\n              NativeModules.FileManager.playAudio((playResult: string) => {\n                try {\n                  const playResponse = JSON.parse(playResult);\n                  if (playResponse.success === false) {\n                    dispatch({ type: 'SET_ERROR', error: playResponse.error || 'Failed to start playback' });\n                    reject(new Error(playResponse.error || 'Failed to start playback'));\n                    return;\n                  }\n                  dispatch({ type: 'START_PLAYBACK' });\n                  resolve();\n                } catch (parseError) {\n                  dispatch({ type: 'START_PLAYBACK' });\n                  resolve();\n                }\n              });\n            } else {\n              // Fallback to sequential playback if merge fails\n              NativeModules.FileManager.playSegments(segmentPaths, (result: string) => {\n                try {\n                  const response = JSON.parse(result);\n                  if (response.success === false) {\n                    dispatch({ type: 'SET_ERROR', error: response.error || 'Failed to start playback' });\n                    reject(new Error(response.error || 'Failed to start playback'));\n                    return;\n                  }\n                  dispatch({ type: 'START_PLAYBACK' });\n                  resolve();\n                } catch (parseError) {\n                  dispatch({ type: 'START_PLAYBACK' });\n                  resolve();\n                }\n              });\n            }\n          } catch (parseError) {\n            // Fallback to sequential playback\n            NativeModules.FileManager.playSegments(segmentPaths, (result: string) => {\n              dispatch({ type: 'START_PLAYBACK' });\n              resolve();\n            });\n          }\n        });\n      }\n    });\n  }, [reducerState.state, reducerState.segments]);\n\n  /**\n   * Pause playback.\n   * Wraps FileManager.pausePlaying native call.\n   * \n   * @validates Requirements 10.1\n   */\n  const pausePlayback = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'playing') {\n      return;\n    }\n\n    return new Promise((resolve) => {\n      NativeModules.FileManager.pausePlaying((result: string) => {\n        dispatch({ type: 'PAUSE_PLAYBACK' });\n        resolve();\n      });\n    });\n  }, [reducerState.state]);\n\n  /**\n   * Resume playback.\n   * Wraps FileManager.resumePlaying native call.\n   * Now works on both iOS and Android for instant resume from paused position.\n   * \n   * @validates Requirements 10.1\n   */\n  const resumePlayback = useCallback(async (): Promise<void> => {\n    if (reducerState.state !== 'completed') {\n      return;\n    }\n\n    return new Promise((resolve) => {\n      // Both iOS and Android now support resumePlaying for instant resume\n      NativeModules.FileManager.resumePlaying((result: string) => {\n        dispatch({ type: 'RESUME_PLAYBACK' });\n        resolve();\n      });\n    });\n  }, [reducerState.state]);\n\n  /**\n   * Seek to a specific position in the recording.\n   * Position is expressed as a progress value from 0.0 to 1.0.\n   * Calls native seekTo for actual audio seeking during playback.\n   * Uses debouncing and operation tracking to prevent race conditions.\n   * \n   * @validates Requirements 5.1, 5.2, 5.3\n   */\n  const seekTo = useCallback(async (progress: number): Promise<void> => {\n    // Clamp progress to valid range\n    const clampedProgress = Math.max(0, Math.min(1, progress));\n    const position = Math.floor(clampedProgress * reducerState.duration);\n    \n    // Track last requested position for reset detection\n    lastRequestedPositionRef.current = position;\n    lastRequestedProgressRef.current = clampedProgress;\n    \n    // Increment operation ID to track this seek\n    const operationId = ++seekOperationIdRef.current;\n    isSeekingRef.current = true;\n    \n    // Update UI position immediately for smooth feedback\n    dispatch({ type: 'SEEK', position });\n    \n    // If currently playing, debounce the native seek call\n    if (reducerState.state === 'playing') {\n      // Clear any pending debounced seek\n      if (seekDebounceTimerRef.current) {\n        clearTimeout(seekDebounceTimerRef.current);\n      }\n      \n      // Store the pending seek\n      pendingSeekRef.current = { progress: clampedProgress, operationId };\n      \n      // Debounce the actual native call by 100ms\n      return new Promise((resolve) => {\n        seekDebounceTimerRef.current = setTimeout(() => {\n          // Check if this is still the latest seek operation\n          if (operationId !== seekOperationIdRef.current) {\n            resolve();\n            return;\n          }\n          \n          const finalPosition = Math.floor(clampedProgress * reducerState.duration);\n          \n          NativeModules.FileManager.seekTo(finalPosition, (result: string) => {\n            // Only clear seeking flag if this is still the latest operation\n            if (operationId === seekOperationIdRef.current) {\n              // Clear seeking flag immediately since native has confirmed seek is complete\n              isSeekingRef.current = false;\n              pendingSeekRef.current = null;\n            }\n            resolve();\n          });\n        }, 100);\n      });\n    }\n    \n    // Not playing, just update position and clear seeking flag\n    setTimeout(() => {\n      if (operationId === seekOperationIdRef.current) {\n        isSeekingRef.current = false;\n      }\n    }, 50);\n  }, [reducerState.duration, reducerState.state]);\n\n  /**\n   * Seek and start playback from a specific position.\n   * Used when tapping on waveform in preview/paused mode.\n   * Uses debouncing and operation tracking to prevent race conditions.\n   * \n   * @validates Requirements 5.1, 5.2\n   */\n  const seekAndPlay = useCallback(async (progress: number): Promise<void> => {\n    // Clamp progress to valid range\n    const clampedProgress = Math.max(0, Math.min(1, progress));\n    const position = Math.floor(clampedProgress * reducerState.duration);\n    \n    // Track last requested position for reset detection\n    lastRequestedPositionRef.current = position;\n    lastRequestedProgressRef.current = clampedProgress;\n    \n    // Increment operation ID to track this seek\n    const operationId = ++seekOperationIdRef.current;\n    isSeekingRef.current = true;\n    \n    // Update UI position immediately\n    dispatch({ type: 'SEEK', position });\n    \n    // Clear any pending debounced seek\n    if (seekDebounceTimerRef.current) {\n      clearTimeout(seekDebounceTimerRef.current);\n      seekDebounceTimerRef.current = null;\n    }\n    \n    // For single segment in completed state, use playFromPosition with debouncing\n    if (reducerState.state === 'completed' && reducerState.filePath) {\n      // Store the pending seek\n      pendingSeekRef.current = { progress: clampedProgress, operationId };\n      \n      return new Promise((resolve, reject) => {\n        // Debounce by 150ms to allow rapid taps to settle\n        seekDebounceTimerRef.current = setTimeout(() => {\n          // Check if this is still the latest seek operation\n          if (operationId !== seekOperationIdRef.current) {\n            isSeekingRef.current = false;\n            resolve();\n            return;\n          }\n          \n          const finalPosition = Math.floor(clampedProgress * reducerState.duration);\n          \n          NativeModules.FileManager.playFromPosition(finalPosition, (result: string) => {\n            // Check if this is still the latest operation before updating state\n            if (operationId !== seekOperationIdRef.current) {\n              resolve();\n              return;\n            }\n            \n            try {\n              const response = JSON.parse(result);\n              if (response.success) {\n                dispatch({ type: 'START_PLAYBACK' });\n                // Clear seeking flag immediately since native has confirmed seek is complete\n                isSeekingRef.current = false;\n                pendingSeekRef.current = null;\n                resolve();\n              } else {\n                isSeekingRef.current = false;\n                pendingSeekRef.current = null;\n                reject(new Error(response.error));\n              }\n            } catch (e) {\n              dispatch({ type: 'START_PLAYBACK' });\n              isSeekingRef.current = false;\n              pendingSeekRef.current = null;\n              resolve();\n            }\n          });\n        }, 150);\n      });\n    }\n    \n    // For preview mode (paused with segments), merge segments first then play from position\n    if (reducerState.state === 'paused' && reducerState.segments.length > 0) {\n      const segmentPaths = reducerState.segments.map(s => s.filePath);\n      const finalPosition = Math.floor(clampedProgress * reducerState.duration);\n      \n      // Store the pending seek\n      pendingSeekRef.current = { progress: clampedProgress, operationId };\n      \n      return new Promise((resolve, reject) => {\n        // Debounce by 150ms to allow rapid taps to settle\n        seekDebounceTimerRef.current = setTimeout(() => {\n          // Check if this is still the latest seek operation\n          if (operationId !== seekOperationIdRef.current) {\n            isSeekingRef.current = false;\n            resolve();\n            return;\n          }\n          \n          const seekPosition = Math.floor(clampedProgress * reducerState.duration);\n          \n          if (segmentPaths.length === 1) {\n            // Single segment - play directly from position\n            NativeModules.FileManager.playFromPosition(seekPosition, (result: string) => {\n              if (operationId !== seekOperationIdRef.current) {\n                resolve();\n                return;\n              }\n              try {\n                const response = JSON.parse(result);\n                if (response.success) {\n                  dispatch({ type: 'START_PLAYBACK' });\n                  // Clear seeking flag immediately since native has confirmed seek is complete\n                  isSeekingRef.current = false;\n                  pendingSeekRef.current = null;\n                  resolve();\n                } else {\n                  isSeekingRef.current = false;\n                  pendingSeekRef.current = null;\n                  reject(new Error(response.error));\n                }\n              } catch (e) {\n                dispatch({ type: 'START_PLAYBACK' });\n                isSeekingRef.current = false;\n                pendingSeekRef.current = null;\n                resolve();\n              }\n            });\n          } else {\n            // Multiple segments - merge first, then play from position\n            NativeModules.FileManager.mergeSegments(segmentPaths, (mergeResult: string) => {\n              if (operationId !== seekOperationIdRef.current) {\n                resolve();\n                return;\n              }\n              try {\n                const mergeResponse = JSON.parse(mergeResult);\n                if (mergeResponse.success && mergeResponse.mergedPath) {\n                  // Now play from the requested position\n                  NativeModules.FileManager.playFromPosition(seekPosition, (playResult: string) => {\n                    if (operationId !== seekOperationIdRef.current) {\n                      resolve();\n                      return;\n                    }\n                    try {\n                      const playResponse = JSON.parse(playResult);\n                      if (playResponse.success) {\n                        dispatch({ type: 'START_PLAYBACK' });\n                        // Clear seeking flag immediately since native has confirmed seek is complete\n                        isSeekingRef.current = false;\n                        pendingSeekRef.current = null;\n                        resolve();\n                      } else {\n                        isSeekingRef.current = false;\n                        pendingSeekRef.current = null;\n                        reject(new Error(playResponse.error));\n                      }\n                    } catch (e) {\n                      dispatch({ type: 'START_PLAYBACK' });\n                      isSeekingRef.current = false;\n                      pendingSeekRef.current = null;\n                      resolve();\n                    }\n                  });\n                } else {\n                  // Merge failed, fall back to starting from beginning\n                  isSeekingRef.current = false;\n                  pendingSeekRef.current = null;\n                  // Reset the last requested position since we're starting from 0\n                  lastRequestedPositionRef.current = 0;\n                  lastRequestedProgressRef.current = 0;\n                  NativeModules.FileManager.playSegments(segmentPaths, (result: string) => {\n                    dispatch({ type: 'START_PLAYBACK' });\n                    resolve();\n                  });\n                }\n              } catch (e) {\n                // Merge parse error, fall back to starting from beginning\n                isSeekingRef.current = false;\n                pendingSeekRef.current = null;\n                lastRequestedPositionRef.current = 0;\n                lastRequestedProgressRef.current = 0;\n                NativeModules.FileManager.playSegments(segmentPaths, (result: string) => {\n                  dispatch({ type: 'START_PLAYBACK' });\n                  resolve();\n                });\n              }\n            });\n          }\n        }, 150);\n      });\n    }\n    \n    // Clear seeking flag for other cases\n    isSeekingRef.current = false;\n    pendingSeekRef.current = null;\n  }, [reducerState.state, reducerState.duration, reducerState.filePath, reducerState.segments]);\n\n  /**\n   * Cancel recording and cleanup all resources.\n   * Deletes all segment files and releases native resources.\n   * \n   * @validates Requirements 7.2, 7.4, 10.5, 11.5\n   */\n  const cancel = useCallback(async (): Promise<void> => {\n    // Clear all timers\n    if (durationTimerRef.current) {\n      clearInterval(durationTimerRef.current);\n      durationTimerRef.current = null;\n    }\n    if (amplitudeTimerRef.current) {\n      clearInterval(amplitudeTimerRef.current);\n      amplitudeTimerRef.current = null;\n    }\n    if (playbackTimerRef.current) {\n      clearInterval(playbackTimerRef.current);\n      playbackTimerRef.current = null;\n    }\n    if (seekDebounceTimerRef.current) {\n      clearTimeout(seekDebounceTimerRef.current);\n      seekDebounceTimerRef.current = null;\n    }\n\n    // Reset refs\n    pausedDurationRef.current = 0;\n    recordingStartTimeRef.current = 0;\n    timerValueRef.current = 0;\n    seekOperationIdRef.current = 0;\n    isSeekingRef.current = false;\n    pendingSeekRef.current = null;\n    lastRequestedPositionRef.current = 0;\n    lastRequestedProgressRef.current = 0;\n\n    // Delete all segment files if any\n    const segmentPaths = reducerState.segments.map(s => s.filePath);\n    if (segmentPaths.length > 0) {\n      return new Promise((resolve) => {\n        NativeModules.FileManager.deleteSegments(segmentPaths, (deleteSegmentsResult: string) => {\n          // Also delete the main file and release resources\n          NativeModules.FileManager.deleteFile((deleteResult: string) => {\n            NativeModules.FileManager.releaseMediaResources((releaseResult: string) => {\n              dispatch({ type: 'CLEAR_SEGMENTS' });\n              dispatch({ type: 'RESET' });\n              resolve();\n            });\n          });\n        });\n      });\n    }\n\n    // No segments, just delete the main file\n    return new Promise((resolve) => {\n      NativeModules.FileManager.deleteFile((deleteResult: string) => {\n        // Release media resources\n        NativeModules.FileManager.releaseMediaResources((releaseResult: string) => {\n          dispatch({ type: 'RESET' });\n          resolve();\n        });\n      });\n    });\n  }, [reducerState.state, reducerState.filePath, reducerState.segments]);\n\n  /**\n   * Computed properties for state checks.\n   * @validates Requirements 9.3, 11.7\n   */\n  const isRecording = useMemo(() => reducerState.state === 'recording', [reducerState.state]);\n  const isPaused = useMemo(() => reducerState.state === 'paused', [reducerState.state]);\n  const isPlaying = useMemo(() => reducerState.state === 'playing', [reducerState.state]);\n  const isCompleted = useMemo(() => reducerState.state === 'completed', [reducerState.state]);\n  const hasRecording = useMemo(() => reducerState.duration > 0, [reducerState.duration]);\n  const hasMultipleSegments = useMemo(() => reducerState.segments.length > 1, [reducerState.segments.length]);\n  const canContinueRecording = useMemo(() => reducerState.state === 'paused', [reducerState.state]);\n\n  return {\n    // State\n    state: reducerState.state,\n    duration: reducerState.duration,\n    currentPosition: reducerState.currentPosition,\n    filePath: reducerState.filePath,\n    error: reducerState.error,\n    amplitudes: reducerState.amplitudes,\n    segments: reducerState.segments,\n    hasMultipleSegments,\n\n    // Actions\n    startRecording,\n    pauseRecording,\n    resumeRecording,\n    continueRecording,\n    stopRecording,\n    startPlayback,\n    startPlaybackPreview,\n    pausePlayback,\n    resumePlayback,\n    seekTo,\n    seekAndPlay,\n    cancel,\n\n    // Computed properties\n    isRecording,\n    isPaused,\n    isPlaying,\n    isCompleted,\n    hasRecording,\n    canContinueRecording,\n  };\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatLinkConfirmPopup/CometChatLinkConfirmPopup.tsx",
    "content": "import React, { useState, useEffect, useMemo } from \"react\";\nimport {\n  Modal,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\nimport {\n  getLinkConfirmPopupStyleLight,\n  getLinkConfirmPopupStyleDark,\n} from \"./style\";\n\nexport interface CometChatLinkConfirmPopupInterface {\n  /** Whether the popup is visible */\n  visible: boolean;\n  /** The URL of the tapped link */\n  url: string;\n  /** Initial display text for edit mode */\n  initialEditText?: string;\n  /** Initial URL for edit mode */\n  initialEditUrl?: string;\n  /** When true, opens directly in edit mode with \"Insert Link\" title.\n   *  The confirm view (Edit/Remove) is skipped entirely. */\n  insertMode?: boolean;\n  /** Called when the popup is dismissed (backdrop press or cancel) */\n  onDismiss: () => void;\n  /** Called when the user saves an edited link */\n  onEdit: (newUrl: string, newText: string) => void;\n  /** Called when the user removes the link */\n  onRemove: () => void;\n}\n\nexport const CometChatLinkConfirmPopup = (props: CometChatLinkConfirmPopupInterface) => {\n  const {\n    visible,\n    url,\n    initialEditText = \"\",\n    initialEditUrl = \"\",\n    insertMode = false,\n    onDismiss,\n    onEdit,\n    onRemove,\n  } = props;\n\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n  const styles = useMemo(\n    () =>\n      theme.mode === \"dark\"\n        ? getLinkConfirmPopupStyleDark(theme.color, theme.spacing, theme.typography)\n        : getLinkConfirmPopupStyleLight(theme.color, theme.spacing, theme.typography),\n    [theme]\n  );\n\n  const [editMode, setEditMode] = useState(false);\n  const [editUrl, setEditUrl] = useState(initialEditUrl || url);\n  const [editText, setEditText] = useState(initialEditText);\n\n  // Sync state when popup opens with new data\n  useEffect(() => {\n    if (visible) {\n      setEditMode(insertMode);\n      setEditUrl(initialEditUrl || url);\n      setEditText(initialEditText);\n    }\n  }, [visible, url, initialEditUrl, initialEditText, insertMode]);\n\n  const handleDismiss = () => {\n    setEditMode(false);\n    onDismiss();\n  };\n\n  const handleSave = () => {\n    if (editUrl.trim()) {\n      onEdit(editUrl.trim(), editText.trim() || editUrl.trim());\n    }\n  };\n\n  return (\n    <Modal\n      visible={visible}\n      transparent\n      animationType=\"fade\"\n      onRequestClose={handleDismiss}\n    >\n      <TouchableOpacity\n        style={styles.overlayStyle}\n        activeOpacity={1}\n        onPress={handleDismiss}\n      >\n        <TouchableOpacity\n          activeOpacity={1}\n          style={styles.containerStyle}\n        >\n          {editMode ? (\n            <>\n              <Text style={styles.titleTextStyle}>\n                {insertMode ? t(\"INSERT_LINK\") : t(\"EDIT_LINK\")}\n              </Text>\n              <TextInput\n                testID=\"link-tap-edit-text-input\"\n                accessibilityLabel=\"link-tap-edit-text-input\"\n                placeholder={t(\"LINK_TEXT\")}\n                placeholderTextColor={theme.color.textTertiary as string}\n                value={editText}\n                onChangeText={setEditText}\n                style={styles.inputStyle}\n                autoFocus\n              />\n              <TextInput\n                testID=\"link-tap-edit-url-input\"\n                accessibilityLabel=\"link-tap-edit-url-input\"\n                placeholder={t(\"URL\")}\n                placeholderTextColor={theme.color.textTertiary as string}\n                value={editUrl}\n                onChangeText={setEditUrl}\n                style={styles.inputStyle}\n                keyboardType=\"url\"\n                autoCapitalize=\"none\"\n              />\n              <View style={styles.buttonRowStyle}>\n                <TouchableOpacity\n                  testID=\"link-tap-edit-cancel\"\n                  style={styles.cancelButtonStyle}\n                  onPress={handleDismiss}\n                >\n                  <Text style={styles.cancelButtonTextStyle}>{ t(\"CANCEL\") }</Text>\n                </TouchableOpacity>\n                <TouchableOpacity\n                  testID=\"link-tap-edit-confirm\"\n                  disabled={!editUrl.trim()}\n                  style={[\n                    styles.confirmButtonStyle,\n                    !editUrl.trim() && { opacity: 0.4 },\n                  ]}\n                  onPress={handleSave}\n                >\n                  <Text style={styles.confirmButtonTextStyle}>\n                    {insertMode ? t(\"INSERT\") : t(\"SAVE\")}\n                  </Text>\n                </TouchableOpacity>\n              </View>\n            </>\n          ) : (\n            <>\n              <View style={styles.confirmSectionStyle}>\n                <Text style={styles.titleTextStyle}>{ t(\"LINK\") }</Text>\n                <Text\n                  testID=\"link-tap-url-display\"\n                  style={styles.urlTextStyle}\n                  numberOfLines={2}\n                >\n                  {url}\n                </Text>\n              </View>\n              <View\n                style={[styles.buttonRowStyle, styles.confirmSectionDividerStyle]}\n              >\n                <TouchableOpacity\n                  testID=\"link-tap-edit\"\n                  style={styles.cancelButtonStyle}\n                  onPress={() => setEditMode(true)}\n                >\n                  <Text style={styles.cancelButtonTextStyle}>{ t(\"EDIT\") }</Text>\n                </TouchableOpacity>\n                <TouchableOpacity\n                  testID=\"link-tap-remove\"\n                  style={styles.removeButtonStyle}\n                  onPress={onRemove}\n                >\n                  <Text style={styles.removeButtonTextStyle}>{ t(\"REMOVE\") }</Text>\n                </TouchableOpacity>\n              </View>\n            </>\n          )}\n        </TouchableOpacity>\n      </TouchableOpacity>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatLinkConfirmPopup/index.ts",
    "content": "export {\n  CometChatLinkConfirmPopup,\n} from \"./CometChatLinkConfirmPopup\";\n\nexport type {\n  CometChatLinkConfirmPopupInterface,\n} from \"./CometChatLinkConfirmPopup\";\n\nexport type { LinkConfirmPopupStyle } from \"./style\";\nexport {\n  getLinkConfirmPopupStyleLight,\n  getLinkConfirmPopupStyleDark,\n} from \"./style\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatLinkConfirmPopup/style.ts",
    "content": "import { StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\n\nexport type LinkConfirmPopupStyle = {\n  overlayStyle: ViewStyle;\n  containerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  urlTextStyle: TextStyle;\n  inputStyle: TextStyle;\n  buttonRowStyle: ViewStyle;\n  cancelButtonStyle: ViewStyle;\n  cancelButtonTextStyle: TextStyle;\n  confirmButtonStyle: ViewStyle;\n  confirmButtonTextStyle: TextStyle;\n  removeButtonStyle: ViewStyle;\n  removeButtonTextStyle: TextStyle;\n  confirmSectionStyle: ViewStyle;\n  confirmSectionDividerStyle: ViewStyle;\n};\n\nexport const getLinkConfirmPopupStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): LinkConfirmPopupStyle => {\n  return StyleSheet.create({\n    overlayStyle: {\n      flex: 1,\n      backgroundColor: \"rgba(0,0,0,0.4)\",\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    containerStyle: {\n      backgroundColor: color.background1,\n      borderWidth: 1,\n      borderColor: color.borderLight,\n      borderRadius: spacing.radius.r4,\n      padding: spacing.padding.p6,\n      width: \"85%\",\n      maxWidth: 400,\n      gap: spacing.padding.p3,\n      shadowColor: \"#101828\",\n      shadowOffset: { width: 0, height: 12 },\n      shadowOpacity: 0.08,\n      shadowRadius: 16,\n      elevation: 8,\n    },\n    titleTextStyle: {\n      color: color.textPrimary,\n      ...typography.heading2.medium,\n    },\n    urlTextStyle: {\n      color: color.textHighlight,\n      ...typography.body.regular,\n    },\n    inputStyle: {\n      borderBottomWidth: 1,\n      borderBottomColor: color.borderDefault,\n      paddingVertical: spacing.padding.p2,\n      color: color.textPrimary,\n      ...typography.body.regular,\n    },\n    buttonRowStyle: {\n      flexDirection: \"row\",\n      gap: spacing.padding.p2,\n      paddingTop: spacing.padding.p3,\n    },\n    cancelButtonStyle: {\n      flex: 1,\n      height: spacing.spacing.s10,\n      borderWidth: 1,\n      borderColor: color.borderDark,\n      borderRadius: spacing.radius.r2,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    cancelButtonTextStyle: {\n      color: color.textPrimary,\n      ...typography.button.medium,\n    },\n    confirmButtonStyle: {\n      flex: 1,\n      height: spacing.spacing.s10,\n      backgroundColor: color.primary,\n      borderRadius: spacing.radius.r2,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    confirmButtonTextStyle: {\n      color: color.primaryButtonIcon,\n      ...typography.button.medium,\n    },\n    removeButtonStyle: {\n      flex: 1,\n      height: spacing.spacing.s10,\n      backgroundColor: color.error,\n      borderRadius: spacing.radius.r2,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    removeButtonTextStyle: {\n      color: color.primaryButtonIcon,\n      ...typography.button.medium,\n    },\n    confirmSectionStyle: {\n      gap: spacing.padding.p2,\n    },\n    confirmSectionDividerStyle: {\n      borderTopWidth: 1,\n      borderTopColor: color.borderLight,\n    },\n  });\n};\n\nexport const getLinkConfirmPopupStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): LinkConfirmPopupStyle => {\n  return <LinkConfirmPopupStyle>(\n    deepMerge(getLinkConfirmPopupStyleLight(color, spacing, typography), {\n      containerStyle: {\n        backgroundColor: color.background2,\n      },\n    })\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatList/CometChatList.tsx",
    "content": "import React, { JSX, useCallback, useEffect, useImperativeHandle, useRef, useState } from \"react\";\nimport {\n  ActivityIndicator,\n  ColorValue,\n  FlatList,\n  GestureResponderEvent,\n  ImageSourcePropType,\n  ImageStyle,\n  Platform,\n  StyleProp,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { LOADING, NO_DATA_FOUND, SOMETHING_WRONG } from \"../../constants/UIKitConstants\";\n\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { CometChatUIKit } from \"../../CometChatUiKit\";\nimport { DeepPartial } from \"../../helper/types\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CometChatListItem } from \"../CometChatListItem\";\nimport {\n  CometChatStatusIndicatorInterface,\n  StatusIndicatorStyles,\n} from \"../CometChatStatusIndicator\";\nimport Header from \"./Header\";\nimport styles from \"./styles\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\nexport interface CometChatListActionsInterface {\n  updateList: (prop: any) => void;\n  updateAndMoveToFirst: (item: any) => void;\n  addItemToList: (item: any, position?: number) => void;\n  removeItemFromList: (itemId: string | number) => void;\n  getListItem: (itemId: string | number) => any;\n  getSelectedItems: () => Array<any>;\n  getAllListItems: () => Array<any>;\n  reload: () => void;\n}\n\nexport interface CometChatListStylesInterface {\n  containerStyle: ViewStyle;\n  onlineStatusColor?: ColorValue;\n  separatorColor?: string;\n  loadingIconTint?: ColorValue;\n  sectionHeaderTextStyle?: TextStyle;\n  confirmSelectionStyle: {\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ImageStyle;\n  };\n  selectionCancelStyle: {\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ImageStyle;\n  };\n  titleSeparatorStyle: ViewStyle;\n  searchStyle?: {\n    textStyle: TextStyle;\n    placehodlerTextStyle?: TextStyle;\n    containerStyle: ViewStyle;\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle: ImageStyle;\n  };\n  titleStyle: TextStyle;\n  titleViewStyle?: ViewStyle;\n  backButtonIcon?: ImageSourcePropType | JSX.Element;\n  backButtonIconStyle: ImageStyle;\n  itemStyle: {\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n    containerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    statusIndicatorStyle?: Partial<StatusIndicatorStyles>;\n    headViewContainerStyle?: StyleProp<ViewStyle>;\n    titleSubtitleContainerStyle?: StyleProp<ViewStyle>;\n    trailingViewContainerStyle?: StyleProp<ViewStyle>;\n  };\n  emptyStateStyle: Partial<{\n    titleStyle: TextStyle;\n    subTitleStyle: TextStyle;\n    containerStyle: ViewStyle;\n    icon: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ViewStyle;\n  }>;\n  errorStateStyle: Partial<{\n    titleStyle: TextStyle;\n    subTitleStyle: TextStyle;\n    containerStyle: ViewStyle;\n    icon: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ViewStyle;\n  }>;\n  headerContainerStyle?: ViewStyle;\n  backButtonIconContainerStyle: ViewStyle;\n}\n\nexport interface CometChatListProps {\n  ItemView?: (item: any) => JSX.Element;\n  LeadingView?: (item: any) => JSX.Element;\n  TitleView?: (item: any) => JSX.Element;\n  SubtitleView?: (item: any) => JSX.Element;\n  TrailingView?: (item: any) => JSX.Element;\n  disableUsersPresence?: boolean;\n  AppBarOptions?: React.FC;\n  searchPlaceholderText?: string;\n  hideBackButton?: boolean;\n  selectionMode?: \"none\" | \"single\" | \"multiple\";\n  onSelection?: (list: any) => void;\n  onSubmit?: (list: any) => void;\n  hideSearch?: boolean;\n  hideHeader?: boolean;\n  title?: string;\n  EmptyView?: React.FC;\n  emptyStateText?: string;\n  errorStateText?: string;\n  ErrorView?: React.FC;\n  LoadingView?: React.FC;\n  requestBuilder?: any;\n  searchRequestBuilder?: any;\n  hideError?: boolean;\n  onItemPress?: (user: any) => void;\n  onItemLongPress?: (user: any, e: GestureResponderEvent) => void;\n  onError?: (error: CometChat.CometChatException) => void;\n  onBack?: (() => void) | undefined;\n  listItemKey: \"uid\" | \"guid\" | \"conversationId\";\n  listStyle?: DeepPartial<CometChatListStylesInterface>;\n  hideSubmitButton?: boolean;\n  statusIndicatorType?: (item: any) => CometChatStatusIndicatorInterface[\"type\"] | null;\n  hideStickyHeader?: boolean;\n  /**\n   * Called once the list has been fetched or updated.\n   * Returns the final array of items currently in the list.\n   */\n  onListFetched?: (fetchedList: any[]) => void;\n  /**\n   * Custom search view component to display instead of the default search input.\n   */\n  SearchView?: () => JSX.Element;\n  /**\n   * Callback triggered when the search bar is clicked or focused.\n   */\n  onSearchBarClicked?: () => void;\n}\n\nlet lastCall: any;\nlet lastReject: Function;\n\n/**\n * @class Users is a component useful for displaying the header and users in a list\n * @description This component displays a header and list of users with subtitle,avatar,status\n * @Version 1.0.0\n * @author CometChat\n *\n */\n\nexport const CometChatList = React.forwardRef<CometChatListActionsInterface, CometChatListProps>(\n  (props, ref) => {\n    const connectionListenerId = \"connectionListener_\" + new Date().getTime();\n    const theme = useTheme();\n    const { t } = useCometChatTranslation();\n    const [isLoadingMore, setIsLoadingMore] = useState(false);\n    const [hasMoreData, setHasMoreData] = useState(true);\n\n    const {\n      LeadingView,\n      TitleView,\n      SubtitleView,\n      TrailingView,\n      disableUsersPresence = false,\n      ItemView,\n      AppBarOptions,\n      searchPlaceholderText,\n      hideBackButton,\n      selectionMode = \"none\",\n      onSelection = () => {},\n      onSubmit,\n      hideSearch = false,\n      title = \"Title\",\n      EmptyView,\n      emptyStateText = t(\"NO_USERS_FOUND\"),\n      errorStateText = t(\"SOMETHING_WRONG\"),\n      ErrorView,\n      LoadingView,\n      requestBuilder,\n      hideHeader,\n      searchRequestBuilder = undefined,\n      hideError = false,\n      onItemPress = () => {},\n      onItemLongPress = () => {},\n      onError,\n      onBack,\n      listItemKey = \"uid\",\n      listStyle,\n      hideSubmitButton,\n      statusIndicatorType,\n      hideStickyHeader = false,\n      SearchView,\n      onSearchBarClicked,\n    } = props;\n\n    // functions which can be accessed by parents\n    useImperativeHandle(ref, () => {\n      return {\n        updateList,\n        addItemToList,\n        removeItemFromList,\n        getListItem,\n        updateAndMoveToFirst,\n        getSelectedItems,\n        getAllListItems,\n        reload,\n      };\n    });\n\n    const [searchInput, setSearchInput] = useState(\n      requestBuilder\n        ? requestBuilder.searchKeyword\n          ? requestBuilder.searchKeyword\n          : \"\"\n        : searchRequestBuilder\n          ? searchRequestBuilder.searchKeyword\n            ? searchRequestBuilder.searchKeyword\n            : \"\"\n          : \"\"\n    );\n\n    const searchInputRef = useRef(\n      requestBuilder\n        ? requestBuilder.searchKeyword\n          ? requestBuilder.searchKeyword\n          : \"\"\n        : searchRequestBuilder\n          ? searchRequestBuilder.searchKeyword\n            ? searchRequestBuilder.searchKeyword\n            : \"\"\n          : \"\"\n    );\n\n    const [selectedItems, setSelectedItems] = useState<{ [key: string]: any }>({});\n    const [shouldSelect, setShouldSelect] = useState(\n      props.selectionMode === \"single\" || props.selectionMode === \"multiple\"\n    );\n\n    const listHandlerRef = useRef<any>(null);\n    const initialRunRef = useRef(true);\n    const selectionEffectDidMountRef = useRef(false);\n\n    const [list, setList] = useState<any[]>([]);\n    const [dataLoadingStatus, setDataLoadingStatus] = useState<string>(LOADING);\n\n    useEffect(() => {\n      // Skip calling onSelection on initial mount; only trigger after a user-driven change\n      if (!selectionEffectDidMountRef.current) {\n        selectionEffectDidMountRef.current = true;\n        return;\n      }\n      if (props.onSelection) {\n        const selectedArray = Object.values(selectedItems);\n        props.onSelection(selectedArray);\n      }\n    }, [selectedItems]);\n\n    // Debounced search handler\n    const searchHandler = (searchText: string) => {\n      setSearchInput(searchText);\n      setHasMoreData(true);\n\n      // Decide which builder to use (searchRequestBuilder preferred)\n      let builder = searchRequestBuilder || requestBuilder;\n\n      if (!builder) {\n        // no builder available, nothing to do\n        return;\n      }\n\n      // If a separate searchRequestBuilder exists and searchText present,\n      // set the search keyword on it; otherwise use the main requestBuilder.\n      let builtRequest;\n      if (searchRequestBuilder && searchText) {\n        builtRequest = searchRequestBuilder.setSearchKeyword(searchText).build();\n      } else if (requestBuilder) {\n        builtRequest = requestBuilder.setSearchKeyword(searchText).build();\n      } else {\n        // fallback: if there's no search keyword, build base builder\n        builtRequest = builder.build();\n      }\n\n      // important: store the built request so fetchNext uses the same instance\n      listHandlerRef.current = builtRequest;\n\n      // Then call getList with the built request\n      getSearch(builtRequest);\n    };\n\n    const getSearch = (builtReq: any) => {\n      getList(builtReq)\n        .then((newlist: any) => {\n          setDataLoadingStatus(NO_DATA_FOUND);\n          setList(newlist);\n        })\n        .catch((error) => {\n          if (error && error[\"message\"] == \"Promise cancelled\") {\n            // Handle promise cancellation if necessary\n          } else {\n            setDataLoadingStatus(SOMETHING_WRONG);\n            errorHandler(error);\n          }\n        });\n    };\n\n    const getSelectedItems = () => {\n      let markedItems: any[] = [];\n      Object.keys(selectedItems).forEach((item) => {\n        const listItem = getListItem(item);\n        if (listItem) markedItems.push(listItem);\n      });\n      return markedItems;\n    };\n\n    useEffect(() => {\n      CometChat.addConnectionListener(\n        connectionListenerId,\n        new CometChat.ConnectionListener({\n          onConnected: () => {\n            if (requestBuilder) {\n              if (searchInputRef.current)\n                listHandlerRef.current = requestBuilder\n                  .setSearchKeyword(searchInputRef.current)\n                  .build();\n              else listHandlerRef.current = requestBuilder.build();\n            }\n            getList(listHandlerRef.current)\n              .then((newlist: any[]) => {\n                setDataLoadingStatus(NO_DATA_FOUND);\n                setList(newlist);\n              })\n              .catch((error) => {\n                if (error && error[\"message\"] === \"Promise cancelled\") {\n                  // Handle promise cancellation if necessary\n                } else {\n                  setDataLoadingStatus(SOMETHING_WRONG);\n                  errorHandler(error);\n                }\n              });\n          },\n          inConnecting: () => {\n            console.log(\"ConnectionListener => In connecting\");\n          },\n          onDisconnected: () => {\n            console.log(\"ConnectionListener => On Disconnected\");\n          },\n        })\n      );\n      return () => {\n        CometChat.removeConnectionListener(connectionListenerId);\n      };\n    }, []);\n\n    useEffect(() => {\n      if (initialRunRef.current === true) {\n        if (requestBuilder) {\n          if (searchInput)\n            listHandlerRef.current = requestBuilder.setSearchKeyword(searchInput).build();\n          else listHandlerRef.current = requestBuilder.build();\n        }\n        initialRunRef.current = false;\n        handleList(false);\n      }\n    }, []);\n\n    useEffect(() => {\n      searchInputRef.current = searchInput;\n    }, [searchInput]);\n\n    /**\n     * Updates the list of users to be displayed\n     * @param\n     */\n    const updateList = (item: any) => {\n      let newList = [...list];\n      let itemKey = newList.findIndex((u) => u[listItemKey] === item[listItemKey]);\n      if (itemKey > -1) {\n        newList.splice(itemKey, 1, item);\n        if (newList.length === 0) setDataLoadingStatus(NO_DATA_FOUND);\n        setList(newList);\n      }\n    };\n\n    /**\n     * This will move item to first location if item doesn't exist then add it to first location.\n     * @param item\n     */\n    const updateAndMoveToFirst = (item: any) => {\n      let newList = [...list];\n      let itemKey = newList.findIndex((u) => u[listItemKey] === item[listItemKey]);\n      if (itemKey > -1) {\n        newList.splice(itemKey, 1);\n      }\n      setList([item, ...newList]);\n    };\n\n    const addItemToList = (item: any, position?: number) => {\n      setList((prev: any[]) => {\n        if (position !== undefined) {\n          if (position === 0) return [item, ...prev];\n          if (position >= prev.length) return [...prev, item];\n          else return [...prev.slice(0, position - 1), item, ...prev.slice(position)];\n        }\n        return [...prev, item];\n      });\n    };\n\n    const removeItemFromList = (uid: string | number) => {\n      setList((prev: any[]) => {\n        let newList = prev.filter((item: any) => item[listItemKey] !== uid);\n        if (newList.length === 0) setDataLoadingStatus(NO_DATA_FOUND);\n        return newList;\n      });\n      if (ItemView === undefined && shouldSelect) {\n        let newSelectedItems = { ...selectedItems };\n        if (Object.keys(selectedItems).includes(uid.toString())) {\n          delete newSelectedItems[uid];\n          setSelectedItems(newSelectedItems);\n        }\n      }\n    };\n\n    const getListItem = (itemId: string | number): any => {\n      return list.find((item: any) => item[listItemKey] === itemId);\n    };\n\n    /**\n     * Get all list items\n     */\n    const getAllListItems = (): any[] => {\n      return list;\n    };\n\n    /**\n     * Handle list fetching with pagination\n     * @param {boolean} throughKeyword - Pass true if wants to set only new users.\n     */\n    const handleList = (throughKeyword?: boolean) => {\n      // Prevent multiple fetches\n      if (isLoadingMore || (!throughKeyword && !hasMoreData)) return;\n      setIsLoadingMore(true);\n\n      // If we're resetting due to a new keyword, create a fresh builder instance\n      if (throughKeyword) {\n        // prefer searchRequestBuilder if provided\n        const baseBuilder = searchRequestBuilder || requestBuilder;\n        if (baseBuilder) {\n          const keyword = searchInputRef.current ?? \"\";\n          if (searchRequestBuilder && keyword) {\n            listHandlerRef.current = searchRequestBuilder.setSearchKeyword(keyword).build();\n          } else if (requestBuilder) {\n            listHandlerRef.current = requestBuilder.setSearchKeyword(keyword).build();\n          } else {\n            listHandlerRef.current = baseBuilder.build();\n          }\n        }\n        // reset pagination flags\n        setHasMoreData(true);\n      }\n\n      getList(listHandlerRef.current)\n        .then((newlist: any[] = []) => {\n          let finalList: any[] = [];\n\n          if (throughKeyword || list.length === 0) {\n            // If we're resetting the list or there is no existing list\n            if (throughKeyword) setHasMoreData(true);\n            else if (newlist.length === 0) setHasMoreData(false);\n\n            finalList = newlist;\n            if (finalList.length === 0) {\n              setDataLoadingStatus(NO_DATA_FOUND);\n            } else {\n              setDataLoadingStatus(\"\");\n            }\n            setList(finalList);\n          } else {\n            // Append to existing list\n            finalList = [...list, ...newlist];\n            setList(finalList);\n\n            // When the backend returns nothing more, mark the end of data\n            if (newlist.length === 0) setHasMoreData(false);\n          }\n\n          // If we *did* get results but less than a full \"page\", also stop further loads\n          if (newlist.length === 0) setHasMoreData(false);\n\n          props.onListFetched?.(finalList);\n          setIsLoadingMore(false);\n        })\n        .catch((error) => {\n          if (error && error[\"message\"] === \"Promise cancelled\") {\n            // Handle promise cancellation if necessary\n          } else {\n            setDataLoadingStatus(SOMETHING_WRONG);\n            errorHandler(error);\n          }\n          setIsLoadingMore(false);\n        });\n    };\n\n    /**\n     * Reloads the list by fetching fresh data (matches Calls tab behavior)\n     */\n    const reload = () => {\n      // Prevent multiple reloads if a load operation is already in progress\n      if (isLoadingMore) return;\n\n      setDataLoadingStatus(LOADING);\n      setHasMoreData(true);\n      if (requestBuilder) {\n        if (searchInputRef.current)\n          listHandlerRef.current = requestBuilder.setSearchKeyword(searchInputRef.current).build();\n        else listHandlerRef.current = requestBuilder.build();\n      }\n      handleList(true);\n    };\n\n    const renderFooter = useCallback(() => {\n      if (!isLoadingMore || !hasMoreData) return null;\n      return (\n        <View style={styles.footerContainer}>\n          <ActivityIndicator size='small' color={theme.color.primary} />\n        </View>\n      );\n    }, [isLoadingMore, hasMoreData]);\n\n    /**\n     * Handle errors\n     */\n    const errorHandler = (errorCode: any) => {\n      onError && onError(errorCode);\n      // CometChatUserEvents.emit(CometChatUserEvents.onUserError, errorCode);\n    };\n\n    /**\n     * Handle item selection based on selection mode\n     */\n    const handleSelection = useCallback(\n      (listItem: any) => {\n        if (selectionMode === \"none\") return;\n\n        const itemKey = listItem.value[listItemKey];\n\n        setSelectedItems((prev: any) => {\n          let newState = { ...prev };\n          if (selectionMode === \"multiple\") {\n            if (newState[itemKey]) {\n              delete newState[itemKey];\n            } else {\n              newState[itemKey] = listItem.value;\n            }\n          } else if (selectionMode === \"single\") {\n            if (newState[itemKey]) {\n              delete newState[itemKey];\n            } else {\n              newState = { [itemKey]: listItem.value };\n            }\n          }\n          // Notify parent about selection change\n\n          return newState;\n        });\n      },\n      [selectionMode, onSelection, listItemKey]\n    );\n\n    /**\n     * Handle Cancel action\n     */\n    const handleCancelSelection = () => {\n      setSelectedItems({});\n    };\n\n    /**\n     * Handle Confirm action\n     */\n    const handleConfirmSelection = () => {\n      onSubmit && onSubmit(Object.values(selectedItems));\n      // Optionally, you might want to clear the selection after confirmation\n      setSelectedItems({});\n    };\n\n    const onListItemPress = (item: any) => {\n      if (shouldSelect) {\n        handleSelection(item);\n      } else {\n        onItemPress(item.value);\n      }\n    };\n\n    const onListItemLongPress = (item: any, e: GestureResponderEvent) => {\n      handleSelection(item);\n      onItemLongPress(item.value, e);\n    };\n\n    const selectedCount = Object.keys(selectedItems).length;\n\n    const renderItemView = useCallback(\n      ({ item, index }: any) => {\n        if (item.header) {\n          if (hideStickyHeader) return null;\n          const headerLetter = item.value;\n\n          return (\n            <View key={`header_${headerLetter}_${index}`}>\n              <Text style={[styles.headerLetterStyle, listStyle?.sectionHeaderTextStyle]}>\n                {headerLetter}\n              </Text>\n            </View>\n          );\n        }\n        return (\n          <CometChatListItem\n            statusIndicatorType={statusIndicatorType ? statusIndicatorType(item.value) : null}\n            id={item.value[listItemKey]}\n            avatarName={item.value.name}\n            selected={!!selectedItems[item.value[listItemKey]]}\n            shouldSelect={shouldSelect}\n            LeadingView={LeadingView && LeadingView(item.value)}\n            TitleView={TitleView && TitleView(item.value)}\n            title={\n              item.value.uid &&\n              item.value.uid === CometChatUIKit.loggedInUser!.getUid() &&\n              item.value.name === CometChatUIKit.loggedInUser!.getName()\n                ? t(\"YOU\")\n                : item.value.name\n            }\n            containerStyle={[listStyle?.itemStyle?.containerStyle]}\n            titleStyle={listStyle?.itemStyle?.titleStyle}\n            headViewContainerStyle={\n              (listStyle?.itemStyle?.headViewContainerStyle as StyleProp<ViewStyle>) ??\n              ({ marginHorizontal: 9 } as StyleProp<ViewStyle>)\n            }\n            titleSubtitleContainerStyle={\n              (listStyle?.itemStyle?.titleSubtitleContainerStyle as StyleProp<ViewStyle>) ??\n              ({} as StyleProp<ViewStyle>)\n            }\n            trailingViewContainerStyle={\n              (listStyle?.itemStyle?.trailingViewContainerStyle as StyleProp<ViewStyle>) ??\n              ({} as StyleProp<ViewStyle>)\n            }\n            avatarURL={item.value.avatar || undefined}\n            avatarStyle={listStyle?.itemStyle?.avatarStyle}\n            SubtitleView={SubtitleView ? SubtitleView(item.value) : undefined}\n            TrailingView={TrailingView ? TrailingView(item.value) : undefined}\n            onPress={() => {\n              onListItemPress(item);\n            }}\n            // onLongPress={() => {\n            //   onListItemLongPress(item);\n            // }}\n            onLongPress={(id: any, e: any) => {\n              // const listItem = getListItem(id);\n              onListItemLongPress(item, e);\n            }}\n          />\n        );\n      },\n      [\n        listItemKey,\n        selectedItems,\n        shouldSelect,\n        listStyle,\n        SubtitleView,\n        TitleView,\n        TrailingView,\n        disableUsersPresence,\n        theme,\n        onListItemPress,\n        onListItemLongPress,\n        hideStickyHeader,\n        LeadingView,\n      ]\n    );\n\n    /**\n     * Gets the list of users\n     */\n    const getList = (reqBuilder: any): Promise<any[]> => {\n      const promise = new Promise<any[]>((resolve, reject) => {\n        const cancel = () => {\n          clearTimeout(lastCall);\n          lastReject(new Error(\"Promise cancelled\"));\n        };\n        if (lastCall) {\n          cancel();\n        }\n\n        lastCall = setTimeout(() => {\n          reqBuilder\n            ?.fetchNext()\n            .then((listItems: any) => {\n              resolve(listItems);\n            })\n            .catch((error: CometChat.CometChatException) => {\n              reject(error);\n            });\n        }, 500);\n        lastReject = reject;\n      });\n      return promise;\n    };\n\n    /**\n     * Returns a container of users if exists else returns the corresponding decorator message\n     */\n    const getMessageContainer = useCallback(() => {\n      let messageContainer: JSX.Element = <></>;\n      if (list.length === 0 && dataLoadingStatus.toLowerCase() === NO_DATA_FOUND) {\n        messageContainer = EmptyView ? (\n          <EmptyView />\n        ) : (\n          <View style={[styles.msgContainerStyle, listStyle?.emptyStateStyle?.containerStyle]}>\n            <Text style={[styles.msgTxtStyle, listStyle?.emptyStateStyle?.titleStyle]}>\n              {emptyStateText}\n            </Text>\n          </View>\n        );\n      } else if (!hideError && dataLoadingStatus.toLowerCase() === SOMETHING_WRONG) {\n        messageContainer = ErrorView ? (\n          <ErrorView />\n        ) : (\n          <View style={[styles.msgContainerStyle, listStyle?.errorStateStyle?.containerStyle]}>\n            {listStyle?.errorStateStyle?.icon && (\n              <Icon name='error' size={24} color={theme.color.error} />\n            )}\n            <Text style={[styles.msgTxtStyle, listStyle?.errorStateStyle?.titleStyle]}>\n              {errorStateText}\n            </Text>\n          </View>\n        );\n      } else {\n        let currentLetter = \"\";\n        const listWithHeaders: any[] = [];\n        if (list.length) {\n          list.forEach((listItem: any) => {\n            const chr = listItem?.name && listItem.name[0].toUpperCase();\n            if (!hideStickyHeader && chr !== currentLetter && !ItemView) {\n              currentLetter = chr;\n              listWithHeaders.push({\n                value: currentLetter,\n                header: true,\n              });\n            }\n            listWithHeaders.push({ value: listItem, header: false });\n          });\n\n          messageContainer = (\n            <View style={styles.listContainerStyle}>\n              <FlatList\n                data={listWithHeaders}\n                extraData={{ selectedItems, theme, list, selectionMode }}\n                renderItem={\n                  ItemView\n                    ? ({ item, index, separators }) => {\n                        return ItemView(item?.value);\n                      }\n                    : renderItemView\n                }\n                keyExtractor={(item, index) => {\n                  const itemValue = { ...item.value };\n                  let key = itemValue[listItemKey] ? `${itemValue[listItemKey]}` : undefined;\n                  if (!key) {\n                    //section header is also an item in the list\n                    if (itemValue[0] && itemValue[0].length === 1) {\n                      key = itemValue[0] + \"_\" + index;\n                    }\n                  }\n                  return key ?? index + \"\";\n                }}\n                onMomentumScrollEnd={(event) => {\n                  const contentOffsetY = event.nativeEvent.contentOffset.y;\n                  const contentHeight = event.nativeEvent.contentSize.height;\n                  const layoutHeight = event.nativeEvent.layoutMeasurement.height;\n\n                  if (contentOffsetY + layoutHeight >= contentHeight - 10) {\n                    handleList();\n                  }\n                }}\n                showsVerticalScrollIndicator={false}\n                ListFooterComponent={renderFooter}\n                keyboardShouldPersistTaps='always'\n                keyboardDismissMode={Platform.OS === 'ios' ? 'on-drag' : 'none'}\n              />\n            </View>\n          );\n        }\n      }\n\n      return messageContainer;\n    }, [list, selectedItems, theme, dataLoadingStatus, isLoadingMore, hasMoreData, shouldSelect]);\n\n    /**\n     * Handle the rendering based on loading status\n     */\n    // if (list.length === 0 && dataLoadingStatus.toLowerCase() === LOADING) {\n    // if (LoadingView) return <LoadingView />;\n    // } else {\n    return (\n      <View style={{ flex: 1 }}>\n        <Header\n          hideHeader={hideHeader ?? hideHeader}\n          backButtonIconContainerStyle={listStyle?.backButtonIconContainerStyle}\n          backButtonIcon={listStyle?.backButtonIcon}\n          backButtonIconStyle={listStyle?.backButtonIconStyle}\n          hideBackButton={hideBackButton}\n          containerStyle={listStyle?.headerContainerStyle}\n          titleSeparatorStyle={listStyle?.titleSeparatorStyle}\n          titleViewStyle={listStyle?.titleViewStyle}\n          onBack={onBack}\n          title={title}\n          titleStyle={listStyle?.titleStyle}\n          AppBarOptions={AppBarOptions}\n          shouldSelect={shouldSelect}\n          hideSubmitButton={hideSubmitButton}\n          onCancel={handleCancelSelection}\n          onConfirm={handleConfirmSelection}\n          hideSearch={hideSearch}\n          searchPlaceholderText={searchPlaceholderText}\n          searchHandler={searchHandler}\n          searchInput={searchInput}\n          onSubmitEditing={() => searchHandler(searchInput)}\n          selectionCancelStyle={listStyle?.selectionCancelStyle}\n          confirmSelectionStyle={listStyle?.confirmSelectionStyle}\n          searchStyle={listStyle?.searchStyle}\n          selectedCount={selectedCount}\n          SearchView={SearchView}\n          onSearchBarClicked={onSearchBarClicked}\n        />\n        <View style={styles.container}>{getMessageContainer()}</View>\n        {list.length === 0 && dataLoadingStatus.toLowerCase() === LOADING ? (\n          LoadingView ? (\n            <LoadingView />\n          ) : (\n            <View style={styles.msgContainerStyle}>\n              <ActivityIndicator\n                size={theme.spacing.spacing.s10}\n                color={listStyle?.loadingIconTint || theme.color.iconSecondary}\n              />\n            </View>\n          )\n        ) : null}\n        {/* Add a fallback like `null` if nothing needs to be rendered */}\n      </View>\n    );\n  }\n  // }\n);\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatList/Header.tsx",
    "content": "import React, { JSX, useRef } from \"react\";\nimport {\n  ImageSourcePropType,\n  ImageStyle,\n  Pressable,\n  StyleProp,\n  Text,\n  TextInput,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\nimport styles from \"./styles\";\n\n/**\n * Props for the Header component.\n */\ninterface HeaderProps {\n  /**\n   * Style for the separator view between the back button and title.\n   */\n  titleSeparatorStyle?: ViewStyle;\n  /**\n   * Count of selected items (used in selection mode).\n   */\n  selectedCount?: number;\n  /**\n   * If true, hides the back button.\n   */\n  hideBackButton?: boolean;\n  /**\n   * Callback function invoked when the back button is pressed.\n   */\n  onBack?: () => void;\n  /**\n   * Custom component for additional AppBar options.\n   */\n  AppBarOptions?: React.FC;\n  /**\n   * If true, indicates that the list is in selection mode.\n   */\n  shouldSelect?: boolean;\n  /**\n   * If true, hides the submit (confirm) button in selection mode.\n   */\n  hideSubmitButton?: boolean;\n  /**\n   * Callback function invoked when the cancel button is pressed in selection mode.\n   */\n  onCancel?: () => void;\n  /**\n   * Callback function invoked when the confirm button is pressed in selection mode.\n   */\n  onConfirm?: () => void;\n  /**\n   * If true, hides the search box.\n   */\n  hideSearch?: boolean;\n  /**\n   * Callback function to handle changes in the search input.\n   */\n  searchHandler?: (text: string) => void;\n  /**\n   * Current search input value.\n   */\n  searchInput?: string;\n  /**\n   * Callback function invoked when submitting the search (e.g., via keyboard's search button).\n   */\n  onSubmitEditing?: () => void;\n  /**\n   * Back button icon, can be an image source or a JSX element.\n   */\n  backButtonIcon?: ImageSourcePropType | JSX.Element;\n  /**\n   * Custom style for the back button icon.\n   */\n  backButtonIconStyle?: ImageStyle;\n  /**\n   * Title text to display in the header.\n   */\n  title?: string;\n  /**\n   * Custom style for the title text.\n   */\n  titleStyle?: StyleProp<TextStyle>;\n  /**\n   * Custom style for the container wrapping the title.\n   */\n  titleViewStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the header container.\n   */\n  containerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Placeholder text for the search input.\n   */\n  searchPlaceholderText?: string;\n  confirmSelectionStyle?: {\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ImageStyle;\n  };\n  selectionCancelStyle?: {\n    icon?: ImageSourcePropType | JSX.Element;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ImageStyle;\n  };\n  /**\n   * Custom style for the search component.\n   */\n  searchStyle?: Partial<{\n    textStyle: TextStyle;\n    placehodlerTextStyle?: TextStyle;\n    containerStyle: ViewStyle;\n    icon: ImageSourcePropType | JSX.Element;\n    iconStyle: ImageStyle;\n  }>;\n  /**\n   * Custom style for the container of the back button icon.\n   */\n  backButtonIconContainerStyle?: ViewStyle;\n  /**\n   * If true, hides the entire header.\n   */\n  hideHeader?: boolean;\n  /**\n   * Custom search view component to display instead of the default search input.\n   */\n  SearchView?: () => JSX.Element;\n  /**\n   * Callback triggered when the search bar is clicked or focused.\n   */\n  onSearchBarClicked?: () => void;\n}\n\n/**\n * Header component renders the top portion of the list with a title, back button, selection actions,\n * AppBar options, and a search box.\n *\n *   Props to customize the header.\n *   The rendered header component.\n */\nexport default function Header({\n  selectedCount = 0,\n  hideBackButton = true,\n  onBack = () => {},\n  AppBarOptions,\n  shouldSelect = false,\n  hideSubmitButton = false,\n  onCancel = () => {},\n  onConfirm = () => {},\n  hideSearch = false,\n  searchHandler = () => {},\n  searchInput = \"\",\n  onSubmitEditing = () => {},\n  backButtonIcon,\n  backButtonIconStyle = {},\n  title = \"\",\n  titleStyle = {},\n  titleViewStyle,\n  containerStyle,\n  searchPlaceholderText = \"Search\",\n  confirmSelectionStyle,\n  selectionCancelStyle,\n  titleSeparatorStyle = {},\n  searchStyle,\n  hideHeader = false,\n  backButtonIconContainerStyle = { marginLeft: 10 },\n  SearchView,\n  onSearchBarClicked,\n}: HeaderProps) {\n  const theme = useTheme();\n  const inputRef = useRef<TextInput>(null);\n  return (\n    <View style={[styles.listBaseHeaderStyle, { width: '100%' }]}>\n      {/* Header section with border */}\n      {!hideHeader && (\n        <View style={[styles.upperContainer, containerStyle]}>\n          <View style={{ flexDirection: \"row\", ...titleSeparatorStyle }}>\n            {/* Render back button and title if not in selection mode */}\n            {!shouldSelect && (!hideBackButton || title.length !== 0) && (\n              <View style={styles.headerLeftContainer}>\n                {!hideBackButton && (\n                  <TouchableOpacity onPress={onBack}>\n                    <Icon\n                      containerStyle={backButtonIconContainerStyle}\n                      name='arrow-back-fill'\n                      size={backButtonIconStyle.width}\n                      height={backButtonIconStyle.height}\n                      width={backButtonIconStyle.width}\n                      color={backButtonIconStyle.tintColor}\n                      icon={backButtonIcon}\n                      imageStyle={backButtonIconStyle}\n                    />\n                  </TouchableOpacity>\n                )}\n                {title.length !== 0 && (\n                  <View style={titleViewStyle}>\n                    <Text\n                      ellipsizeMode='tail'\n                      numberOfLines={1}\n                      allowFontScaling={true}                \n                      style={[{ color: theme.color.textPrimary }, titleStyle]}\n                    >\n                      {title}\n                    </Text>\n                  </View>\n                )}\n              </View>\n            )}\n\n            {/* Render selection mode actions: cancel button and selected count */}\n            {shouldSelect && (\n              <View\n                style={[\n                  { paddingHorizontal: theme.spacing.spacing.s4 },\n                  styles.headerLeftContainer,\n                ]}\n              >\n                <TouchableOpacity onPress={onCancel}>\n                  <Icon\n                    name='close'\n                    size={selectionCancelStyle?.iconStyle?.width || 24}\n                    height={selectionCancelStyle?.iconStyle?.height || 24}\n                    width={selectionCancelStyle?.iconStyle?.width || 24}\n                    color={selectionCancelStyle?.iconStyle?.tintColor || theme.color.iconPrimary}\n                    icon={selectionCancelStyle?.icon}\n                    imageStyle={selectionCancelStyle?.iconStyle}\n                    containerStyle={selectionCancelStyle?.iconContainerStyle}\n                  />\n                </TouchableOpacity>\n                <Text\n                  style={[styles.selectedCountText, titleStyle, theme.typography.heading2.medium]}\n                >\n                  {selectedCount}\n                </Text>\n              </View>\n            )}\n\n            {/* Render AppBar options or the selection confirm (tick) icon */}\n            <View style={styles.headerRightContainer}>\n              {!shouldSelect && AppBarOptions && <AppBarOptions />}\n              {selectedCount > 0 && shouldSelect && !hideSubmitButton && (\n                <TouchableOpacity onPress={onConfirm} style={{ paddingHorizontal: 20 }}>\n                  <Icon\n                    name='list-item-check'\n                    size={confirmSelectionStyle?.iconStyle?.width || 24}\n                    height={confirmSelectionStyle?.iconStyle?.height || 24}\n                    width={confirmSelectionStyle?.iconStyle?.width || 24}\n                    color={confirmSelectionStyle?.iconStyle?.tintColor || theme.color.iconPrimary}\n                    icon={confirmSelectionStyle?.icon}\n                    imageStyle={confirmSelectionStyle?.iconStyle}\n                    containerStyle={confirmSelectionStyle?.iconContainerStyle}\n                  />\n                </TouchableOpacity>\n              )}\n            </View>\n          </View>\n        </View>\n      )}\n\n      {/* Render search box without border */}\n      {!hideSearch && (\n        <View style={{\n          alignSelf: 'stretch',\n          width: '100%',\n          paddingVertical: theme.spacing.spacing.s3\n        }}>\n          {SearchView ? (\n            <View >\n              <SearchView />\n            </View>\n          ) : (\n          \n            <Pressable\n              onPress={onSearchBarClicked}\n              disabled={!onSearchBarClicked}\n            >\n              <View\n                pointerEvents={onSearchBarClicked ? 'none' : 'auto'}\n                style={[\n                  {\n                    backgroundColor: theme.color.background3,\n                    borderRadius: theme.spacing.radius.max,\n                    paddingHorizontal: theme.spacing.spacing.s3,\n                    paddingVertical: theme.spacing.spacing.s3,\n                    marginTop: theme.spacing.spacing.s2,\n                    flexDirection: 'row' as const,\n                    alignItems: 'center' as const,\n                    gap: theme.spacing.spacing.s2,\n                    minHeight: 48,\n                    alignSelf: 'stretch',\n                  },\n                  searchStyle?.containerStyle,\n                ]}\n              >\n                <Icon\n                  name='search-fill'\n                  size={searchStyle?.iconStyle?.width || 24}\n                  height={searchStyle?.iconStyle?.height || 24}\n                  width={searchStyle?.iconStyle?.width || 24}\n                  color={searchStyle?.iconStyle?.tintColor || theme.color.iconSecondary}\n                  icon={searchStyle?.icon}\n                  imageStyle={searchStyle?.iconStyle}\n                />\n                <TextInput\n                  ref={inputRef}\n                  placeholder={searchPlaceholderText}\n                  placeholderTextColor={\n                    searchStyle?.placehodlerTextStyle?.color || theme.color.textTertiary\n                  }\n                  onChangeText={searchHandler}\n                  returnKeyType='search'\n                  value={searchInput}\n                  editable={!onSearchBarClicked}\n                  onSubmitEditing={() => {\n                    onSubmitEditing?.();\n                    inputRef.current?.focus();\n                  }}\n                  submitBehavior='submit'\n                  numberOfLines={1}\n                  allowFontScaling={false}\n                  style={[\n                    {\n                      flex: 1,\n                      color: theme.color.textPrimary,\n                      fontSize: 16,\n                      paddingVertical: 4, \n                      ...theme.typography.heading4.regular,\n                    },\n                    searchStyle?.textStyle,\n                  ]}\n                />\n              </View>\n            </Pressable>\n          )}\n        </View>\n      )}\n    </View>\n  );\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatList/index.ts",
    "content": "import {\n  CometChatList,\n  CometChatListActionsInterface,\n  CometChatListProps,\n  CometChatListStylesInterface,\n} from \"./CometChatList\";\nexport {\n  CometChatList,\n};\n\nexport type {\n  CometChatListActionsInterface,\n  CometChatListStylesInterface,\n  CometChatListProps,\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatList/resources/index.ts",
    "content": "import SPINNER from \"./Spinner.png\";\nimport CHECK from \"./check.png\";\nimport CHECK_MARK from \"./checkmark.png\";\nexport const ICONS = {\n  SPINNER,\n  CHECK,\n  CHECK_MARK,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatList/styles.ts",
    "content": "import { StyleSheet } from \"react-native\";\n\nconst styles = StyleSheet.create({\n  containerStyle: {\n    flex: 1,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    position: \"relative\",\n    alignSelf: \"center\",\n  },\n  container: {\n    flex: 1,\n    width: \"100%\",\n  },\n  listBaseHeaderStyle: {\n    alignItems: \"flex-start\",\n    justifyContent: \"center\",\n    width: \"100%\",\n    borderRadius: 0,\n    paddingHorizontal: 16,\n    padding: 2,\n  },\n  upperContainer: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    width: \"100%\",\n  },\n  closeButton: {\n    marginRight: 10,\n  },\n  headerRightContainer: {\n    flexDirection: \"row\",\n    justifyContent: \"flex-end\",\n    alignItems: \"center\",\n    flex: 1,\n  },\n  headerLeftContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    minHeight: 44, // minHeight for accessibility\n    paddingVertical:8,\n  },\n  selectedCountText: {\n    paddingLeft: 10,\n  },\n  searchBox: {\n    height: 40,\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    overflow: \"hidden\",\n  },\n  titleStyle: {\n    fontSize: 20,\n    fontWeight: \"600\",\n    color: \"#000\",\n  },\n  backButtonStyle: {\n    width: 20,\n    height: 20,\n    marginRight: 10,\n  },\n  searchTextStyle: {\n    flex: 1,\n    padding: 5,\n    fontSize: 17,\n    fontWeight: \"400\",\n    marginLeft: 8,\n    alignSelf: \"center\",\n  },\n  searchButtonStyle: {\n    width: 16,\n    height: 16,\n    marginLeft: 11,\n    marginTop: 10,\n    marginBottom: 10,\n  },\n  msgContainerStyle: {\n    flex: 1,\n    overflow: \"hidden\",\n    width: \"100%\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  msgTxtStyle: {\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    alignSelf: \"center\",\n    textAlign: \"center\",\n    width: \"100%\",\n  },\n  headerLetterStyle: {\n    minHeight: 24, //  minHeight for accessibility\n    fontWeight: \"500\",\n    fontFamily: undefined,\n    fontSize: 13,\n    marginTop: 8,\n    paddingVertical: 4,\n  },\n  dividerStyle: {\n    height: 1,\n    width: \"100%\",\n    marginLeft: 16,\n    marginRight: 16,\n  },\n  listContainerStyle: {\n    width: \"100%\",\n    height: \"100%\",\n    flex: 1,\n  },\n  footerContainer: {\n    paddingVertical: 20,\n    flex:1,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n});\n\nexport default styles;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatListItem/CometChatListItem.tsx",
    "content": "import React, { ReactNode, useCallback, useState } from \"react\";\nimport {\n  GestureResponderEvent,\n  ImageSourcePropType,\n  StyleProp,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CometChatAvatar } from \"../CometChatAvatar\";\nimport {\n  CometChatStatusIndicator,\n  CometChatStatusIndicatorInterface,\n} from \"../CometChatStatusIndicator\";\nimport { Style } from \"./style\";\nimport { DeepPartial } from \"../../helper/types\";\n\nexport interface CometChatListItemInterface {\n  /**\n   * Unique identifier for the list item.\n   */\n  id: string | number;\n  /**\n   * URL for the avatar image.\n   */\n  avatarURL?: ImageSourcePropType;\n  /**\n   * Fallback name for the avatar if image is not available.\n   */\n  avatarName?: string;\n  /**\n   * Custom style for the avatar.\n   */\n  avatarStyle?: CometChatTheme[\"avatarStyle\"];\n  /**\n   * Title text to display.\n   */\n  title?: string;\n  /**\n   * Custom style for the title text.\n   */\n  titleStyle?: StyleProp<TextStyle>;\n  /**\n   * Custom style for the item container.\n   */\n  containerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Specifies the type for the status indicator.\n   */\n  statusIndicatorType?: CometChatStatusIndicatorInterface[\"type\"] | null;\n  /**\n   * Custom node to display at the beginning of the item.\n   */\n  LeadingView?: ReactNode;\n  /**\n   * Custom node to display as the title view.\n   */\n  TitleView?: ReactNode;\n  /**\n   * Custom node to display as the subtitle view.\n   */\n  SubtitleView?: ReactNode;\n  /**\n   * Custom node to display at the end of the item.\n   */\n  TrailingView?: ReactNode;\n  /**\n   * Color for the separator.\n   */\n  separatorColor?: string;\n  /**\n   * Callback function triggered on item press.\n   */\n  onPress?: Function;\n  /**\n   * Callback function triggered on item long press.\n   */\n  onLongPress?: Function;\n  /**\n   * Indicates if the item is selectable.\n   */\n  shouldSelect?: boolean;\n  /**\n   * Custom style for the container of the left (head) view.\n   */\n  headViewContainerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the container of the trailing view.\n   */\n  trailingViewContainerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the container wrapping title and subtitle.\n   */\n  titleSubtitleContainerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Indicates if the item is selected.\n   */\n  selected?: boolean;\n  statusIndicatorStyle?: DeepPartial<CometChatTheme['statusIndicatorStyle']>\n}\n\n/**\n * CometChatListItem renders a single item in the list.\n * It displays an avatar with an optional status indicator, title, subtitle, and trailing view.\n *\n * @param {CometChatListItemInterface} props - Props for the list item.\n * @returns {JSX.Element} The rendered list item.\n */\nexport const CometChatListItem = React.memo((props: CometChatListItemInterface) => {\n  const [translate, setTranslate] = useState(0);\n  const theme = useTheme();\n\n  const {\n    id,\n    avatarURL,\n    avatarName,\n    title,\n    LeadingView,\n    SubtitleView,\n    TrailingView,\n    headViewContainerStyle,\n    trailingViewContainerStyle,\n    titleSubtitleContainerStyle,\n    onPress,\n    onLongPress,\n    statusIndicatorType,\n    titleStyle,\n    containerStyle,\n    avatarStyle,\n    selected = false,\n    shouldSelect,\n    TitleView,\n    statusIndicatorStyle\n  } = props;\n\n  let cancelClick = false;\n\n  const CheckBoxView = useCallback(() => {\n    return (\n      <>\n        {shouldSelect && (\n          <View\n            style={{\n              width: 24,\n              height: 24,\n              borderWidth: 1.6,\n              borderColor: theme.color.borderDefault,\n              backgroundColor: selected ? theme.color.primary : theme.color.background1,\n              borderRadius: 5,\n              marginRight: 12,\n              justifyContent: \"center\",\n              alignItems: \"center\",\n              zIndex: 2,\n            }}\n          >\n            {selected && (\n              <Icon\n                name='list-item-check'\n                size={16}\n                height={16}\n                width={16}\n                color={theme.color.iconWhite}\n              />\n            )}\n          </View>\n        )}\n      </>\n    );\n  }, [selected, shouldSelect]);\n\n  /**\n   * Renders the title view.\n   *\n   *  The title view.\n   */\n  const getTitleView = () => {\n    if (TitleView) return TitleView;\n    return (\n      <Text numberOfLines={1} ellipsizeMode='tail' style={titleStyle}>\n        {title}\n      </Text>\n    );\n  };\n\n  /**\n   * Handles the press event on the list item.\n   */\n  const clickHandler = () => {\n    if (!cancelClick) {\n      if (onPress && typeof onPress === \"function\") {\n        onPress(id);\n      }\n    }\n  };\n\n  /**\n   * Handles the long press event on the list item.\n   *\n   * @param {GestureResponderEvent} e - The long press event.\n   */\n  const longPressHandler = useCallback(\n    (e: GestureResponderEvent) => {\n      if (!cancelClick) {\n        if (onLongPress && typeof onLongPress === \"function\") {\n          onLongPress(id, e);\n        }\n      }\n    },\n    [onLongPress, id]\n  );\n\n  /**\n   * Renders the trailing view of the list item.\n   *\n   * @returns {JSX.Element | null} The trailing view if provided.\n   */\n  const getTrailingView = () => {\n    if (TrailingView)\n      return (\n        <View style={[Style.tailViewStyle, trailingViewContainerStyle ?? {}]}>{TrailingView}</View>\n      );\n    return null;\n  };\n\n  let ListComponent =\n    (onPress && typeof onPress === \"function\") || (onLongPress && typeof onLongPress === \"function\")\n      ? TouchableOpacity\n      : View;\n  let listComponentProps =\n    (onPress && typeof onPress === \"function\") || (onLongPress && typeof onLongPress === \"function\")\n      ? {\n          activeOpacity: 1,\n          onPress: clickHandler,\n          onLongPress: longPressHandler,\n        }\n      : {};\n\n  const getLeadingView = useCallback(() => {\n    return (\n      <View style={[{ flexDirection: \"row\", alignItems: \"center\" }, headViewContainerStyle]}>\n        <CheckBoxView />\n        {LeadingView ? (\n          LeadingView\n        ) : avatarURL || avatarName ? (\n          <>\n            <CometChatAvatar image={avatarURL} name={avatarName!} style={avatarStyle} />\n            <CometChatStatusIndicator type={statusIndicatorType} style={statusIndicatorStyle} />\n          </>\n        ) : null}\n      </View>\n    );\n  }, [\n    avatarURL,\n    avatarName,\n    statusIndicatorType,\n    avatarStyle,\n    headViewContainerStyle,\n    theme,\n    selected,\n    shouldSelect,\n    statusIndicatorStyle\n  ]);\n\n  return (\n    <ListComponent\n      {...listComponentProps}\n      style={[containerStyle, selected && { backgroundColor: theme.color.background4 }]}\n    >\n      {getLeadingView()}\n      <View style={[Style.rightContainerStyle]}>\n        <View\n          style={[\n            Style.middleViewStyle,\n            { padding: 0, paddingLeft: 0 },\n            titleSubtitleContainerStyle ?? {},\n          ]}\n        >\n          {getTitleView()}\n          {SubtitleView}\n        </View>\n        {getTrailingView()}\n      </View>\n    </ListComponent>\n  );\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatListItem/index.ts",
    "content": "import { CometChatListItem, CometChatListItemInterface } from \"./CometChatListItem\";\nexport { CometChatListItem};\nexport type {CometChatListItemInterface};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatListItem/style.ts",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const Style = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    // width: \"100%\",\n    // alignItems: \"center\",\n    // flex: 1,\n  },\n  rightContainerStyle: {\n    flexDirection: \"row\",\n    flex: 1,\n    alignItems: \"flex-start\",\n  },\n  defaultStatusStyle: {\n    position: \"absolute\",\n    end: 0,\n    top: \"60%\",\n  },\n  middleViewStyle: {\n    flexDirection: \"column\",\n    flex: 1,\n    justifyContent: \"center\",\n    overflow: \"hidden\",\n  },\n\n  titleTextStyle: {\n    opacity: 1,\n\n    textAlign: \"left\",\n  },\n  tailViewStyle: {\n    marginHorizontal: 6,\n    alignItems: \"flex-end\",\n    justifyContent: \"center\",\n    flexShrink: 0,\n  },\n  optionStyle: {\n    position: \"absolute\",\n    end: 0,\n    height: \"100%\",\n  },\n  optionStyleContainer: {\n    height: \"100%\",\n  },\n  rightActionButtonStyle: {\n    height: \"95%\",\n    alignSelf: \"center\",\n    width: 64,\n    justifyContent: \"center\",\n  },\n  optionButtonViewStyle: {\n    height: \"100%\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  optionButtonImageStyle: {\n    height: 18,\n    width: 18,\n  },\n  optionTitleStyle: { fontWeight: \"500\", fontSize: 17 },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/AnimatingMic.tsx",
    "content": "import React, { JSX, useEffect, useMemo, useRef } from \"react\";\nimport {\n  Animated,\n  Easing,\n  ImageSourcePropType,\n  ImageStyle,\n  StyleSheet,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { defaultSpacing } from \"../../../theme/default\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\n\ntype MediarecorderAnimationStyle = {\n  innerAnimationContainerStyle: ViewStyle;\n  outerAnimationContainerStyle: ViewStyle;\n  animatedIconStyle?: {\n    icon?: JSX.Element | ImageSourcePropType;\n    iconStyle?: ImageStyle;\n    containerStyle?: ViewStyle;\n  };\n};\n\nexport const AnimatingMic = ({\n  isAnimating = false,\n  style,\n}: {\n  isAnimating?: boolean;\n  style?: MediarecorderAnimationStyle;\n}) => {\n  const theme = useTheme();\n  const scaleAnim = useRef(new Animated.Value(0.8)).current;\n  const animationStyle: MediarecorderAnimationStyle = useMemo(() => {\n    return deepMerge(\n      theme.mediaRecorderStyle?.animationStyle ?? ({} as MediarecorderAnimationStyle),\n      style ?? ({} as MediarecorderAnimationStyle)\n    );\n  }, [theme, style]);\n\n  useEffect(() => {\n    if (isAnimating === false) return () => {};\n    Animated.loop(\n      Animated.sequence([\n        Animated.timing(scaleAnim, {\n          toValue: 1.2,\n          duration: 1000,\n          easing: Easing.inOut(Easing.ease),\n          useNativeDriver: true,\n        }),\n        Animated.timing(scaleAnim, {\n          toValue: 0.8,\n          duration: 1000,\n          easing: Easing.inOut(Easing.ease),\n          useNativeDriver: true,\n        }),\n      ])\n    ).start();\n    return () => scaleAnim.resetAnimation();\n  }, [scaleAnim, isAnimating]);\n\n  return (\n    <View style={styles.container}>\n      <Animated.View\n        style={[\n          styles.pulseCircleOuter,\n          true ? { backgroundColor: theme.color.extendedPrimary50 } : {},\n          { transform: [{ scale: scaleAnim }] },\n          animationStyle.outerAnimationContainerStyle,\n        ]}\n      />\n      <Animated.View\n        style={[\n          styles.pulseCircle,\n          true\n            ? {\n                backgroundColor: theme.color.extendedPrimary100,\n              }\n            : {},\n          { transform: [{ scale: scaleAnim }] },\n          animationStyle.innerAnimationContainerStyle,\n        ]}\n      />\n      <Icon\n        name='mic-fill'\n        size={48}\n        color={animationStyle.animatedIconStyle?.iconStyle?.tintColor ?? theme.color.staticWhite}\n        icon={animationStyle.animatedIconStyle?.icon}\n        containerStyle={[\n          styles.micContainer,\n          {\n            backgroundColor: theme.color.primary,\n          },\n          animationStyle.animatedIconStyle?.containerStyle,\n        ]}\n        imageStyle={[animationStyle.animatedIconStyle?.iconStyle]}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: 32,\n  },\n  pulseCircle: {\n    position: \"absolute\",\n    width: 100,\n    height: 100,\n    borderRadius: defaultSpacing.radius.max,\n  },\n  pulseCircleOuter: {\n    position: \"absolute\",\n    width: 120,\n    height: 120,\n    borderRadius: defaultSpacing.radius.max,\n  },\n  micContainer: {\n    width: 80,\n    height: 80,\n    borderRadius: defaultSpacing.radius.max,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/CometChatAudioPlayer.tsx",
    "content": "import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { NativeModules, Text, TouchableOpacity, View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\nimport { AnimatedAudioWaves } from \"../CometChatAudioBubble/AnimatedAudioWaves\";\n\nexport interface CometChatAudioPlayerInterface {\n  totalDuration: number;\n}\n\nexport const CometChatAudioPlayer = ({ totalDuration }: CometChatAudioPlayerInterface) => {\n  const [status, setStatus] = useState<\"playing\" | \"paused\" | \"stopped\">(\"stopped\");\n  const [currentTime, setCurrentTime] = useState(0);\n  const audioBubbleStyles =\n    useTheme().messageListStyles.outgoingMessageBubbleStyles?.audioBubbleStyles;\n\n  const intervalRef = useRef<NodeJS.Timeout | null>(null);\n\n  const displayDuration = useCallback(\n    () => (\n      <Text style={audioBubbleStyles?.playProgressTextStyle}>\n        {`${String(Math.floor((currentTime || 0) / 60)).padStart(2, \"0\")}:${String(\n          Math.floor((currentTime || 0) % 60)\n        ).padStart(2, \"0\")}`}\n        /\n        {`${String(Math.floor((totalDuration || 0) / 60)).padStart(2, \"0\")}:${String(\n          Math.floor((totalDuration || 0) % 60)\n        ).padStart(2, \"0\")}`}\n      </Text>\n    ),\n    [currentTime, totalDuration]\n  );\n\n  function clearTimeInterval() {\n    if (intervalRef.current !== null) {\n      clearInterval(intervalRef.current);\n      intervalRef.current = null;\n    }\n  }\n\n  const playAudio = (type: \"play\" | \"resume\") => {\n    NativeModules.FileManager[type === \"play\" ? \"playAudio\" : \"resumePlaying\"]((filepath) => {\n      console.log(\"Filepath _playRecorderAudio\", filepath);\n      intervalRef.current = setInterval(() => {\n        setCurrentTime((prev) => {\n          if (prev >= totalDuration) {\n            setStatus(\"stopped\");\n            stopAudio();\n            return prev;\n          }\n          return prev + 1;\n        });\n      }, 1000);\n    });\n  };\n\n  const pauseAudio = () => {\n    NativeModules.FileManager.pausePlaying((filepath) => {\n      clearTimeInterval();\n      console.log(\"Filepath _pauseRecorderAudio\", filepath);\n    });\n  };\n\n  const stopAudio = () => {\n    NativeModules.FileManager.releaseMediaResources((filepath) => {\n      clearTimeInterval();\n      setCurrentTime(0);\n      console.log(\"Filepath _pauseRecorderAudio\", filepath);\n    });\n  };\n\n  useEffect(() => {\n    return clearTimeInterval;\n  }, []);\n\n  return (\n    <View style={audioBubbleStyles?.playViewContainerStyle}>\n      <TouchableOpacity\n        style={audioBubbleStyles?.playIconContainerStyle}\n        onPress={() => {\n          setStatus((prev) => {\n            if (prev === \"playing\") {\n              pauseAudio();\n              return \"paused\";\n            } else if (prev === \"paused\") {\n              playAudio(\"resume\");\n              return \"playing\";\n            } else {\n              playAudio(\"play\");\n              return \"playing\";\n            }\n          });\n        }}\n      >\n        <Icon\n          name={status === \"playing\" ? \"pause-fill\" : \"play-arrow-fill\"}\n          height={audioBubbleStyles?.playIconStyle?.height}\n          width={audioBubbleStyles?.playIconStyle?.width}\n          color={audioBubbleStyles?.playIconStyle?.tintColor}\n          imageStyle={audioBubbleStyles?.playIconStyle}\n          // icon={status === \"playing\" ? pauseIcon : playIcon}\n        />\n      </TouchableOpacity>\n\n      <View style={{ flex: 1 }}>\n        <AnimatedAudioWaves\n          waveStyle={audioBubbleStyles?.waveStyle}\n          waveContainerStyle={audioBubbleStyles?.waveContainerStyle}\n          isAnimating={status === \"playing\"}\n        />\n        {displayDuration()}\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/CometChatAudioPreview/CometChatAudioPreview.tsx",
    "content": "import React, { JSX, useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n  EmitterSubscription,\n  ImageSourcePropType,\n  ImageStyle,\n  NativeEventEmitter,\n  NativeModules,\n  Platform,\n  StyleProp,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { Icon } from \"../../../icons/Icon\";\nimport { AnimatedAudioWaves } from \"../../CometChatAudioBubble/AnimatedAudioWaves\";\n\nconst { SoundPlayer } = NativeModules;\nconst eventEmitter = new NativeEventEmitter(SoundPlayer);\nlet listener: EmitterSubscription | undefined;\nlet interval: ReturnType<typeof setTimeout>;\n\nexport interface CometChatAudioBubbleInterface {\n  /**\n   * url of audio\n   */\n  audioUrl: string;\n  /**\n   * custom icon for play\n   */\n  playIcon?: ImageSourcePropType | JSX.Element;\n  /**\n   * custom icon for pause\n   */\n  pauseIcon?: ImageSourcePropType | JSX.Element;\n  /**\n   * pass function to handle custom play/pause logic.\n   * one parameters will be received audioUrl\n   */\n  onPress?: Function;\n  playViewContainerStyle?: StyleProp<ViewStyle>;\n  playIconStyle?: ImageStyle;\n  playIconContainerStyle?: StyleProp<ViewStyle>;\n  waveStyle?: StyleProp<ViewStyle>;\n  waveContainerStyle?: StyleProp<ViewStyle>;\n  playProgressTextStyle?: TextStyle;\n}\n\nexport const CometChatAudioPreview = ({\n  audioUrl,\n  onPress,\n  playIcon,\n  pauseIcon,\n  playViewContainerStyle = {},\n  playIconStyle = {},\n  playIconContainerStyle = {},\n  waveStyle = {},\n  waveContainerStyle = {},\n  playProgressTextStyle = {},\n}: CometChatAudioBubbleInterface) => {\n  const [status, setStatus] = useState<\"playing\" | \"paused\" | \"loading\" | \"\">(\"\");\n  const [duration, setDuration] = useState(0);\n  const [currentTime, setCurrentTime] = useState(0);\n\n  useEffect(() => {\n    if (audioUrl) {\n      SoundPlayer.prepareMediaPlayer(audioUrl, (s: string) => {\n        try {\n          setDuration(JSON.parse(s).duration);\n        } catch (e) {\n          console.log(\"prepareMediaPlayer parse error\", e);\n        }\n      });\n    }\n\n    // release player on unmount (all platforms) — keeps behaviour consistent with AudioBubble\n    return () => {\n      SoundPlayer.releaseMediaPlayer();\n    };\n  }, [audioUrl]);\n\n  useEffect(() => {\n    listener = eventEmitter.addListener(\"soundPlayStatus\", (data) => {\n      if (audioUrl === data.url) {\n        // playback ended for this url — reset to idle, clear interval and reset time\n        setStatus(\"\");\n        clearInterval(interval);\n        setCurrentTime(0);\n      }\n    });\n\n    return () => {\n      listener?.remove();\n      clearInterval(interval);\n    };\n  }, [audioUrl]);\n\n  const playPauseAudio = () => {\n    if (onPress) {\n      onPress(audioUrl);\n      return;\n    }\n\n    if (status === \"playing\") {\n      SoundPlayer.pause((s: string) => {\n        try {\n          const json = JSON.parse(s);\n          if (json[\"success\"] === true) {\n            setStatus(\"paused\");\n            clearInterval(interval);\n          }\n        } catch (ex) {\n          console.log(ex);\n        }\n      });\n      return;\n    }\n    if (status === \"paused\") {\n      SoundPlayer.resume();\n      if (Platform.OS == \"ios\") {\n        setStatus(\"playing\");\n      }\n      // Get total duration/position when resume\n      SoundPlayer.getPosition((info: string) => {\n        if (info) {\n          if (Platform.OS == \"android\") {\n            setStatus(\"playing\");\n          }\n          setCurrentTime(JSON.parse(info).position);\n        }\n      });\n\n      // Start tracking the current time\n      interval = setInterval(() => {\n        SoundPlayer.getPosition((info: string) => {\n          if (info) {\n            setCurrentTime(JSON.parse(info).position);\n          }\n        });\n      }, 500);\n      return;\n    }\n    if (audioUrl) {\n      setStatus(\"loading\");\n      SoundPlayer.play(audioUrl, (s: string) => {\n        try {\n          const json = JSON.parse(s);\n          if (json[\"success\"] == true) {\n            setStatus(\"playing\");\n            // Get total duration when audio starts playing\n            SoundPlayer.getPosition((info: string) => {\n              if (info) {\n                setCurrentTime(JSON.parse(info).position);\n              }\n            });\n\n            // Start tracking the current time\n            interval = setInterval(() => {\n              SoundPlayer.getPosition((info: string) => {\n                try {\n                  if (info) {\n                    setCurrentTime(JSON.parse(info).position);\n                  }\n                } catch (e) {}\n              });\n            }, 500);\n          }\n        } catch (ex) {\n          console.log(ex);\n        }\n      });\n    }\n  };\n\n  const pressTime = useRef<number | null>(0);\n\n  const handleTouchStart = () => {\n    pressTime.current = Date.now();\n  };\n\n  const handleTouchEnd = () => {\n    if (pressTime.current === null && Platform.OS === \"ios\") return;\n    const endTime = Date.now();\n    const pressDuration = endTime - (pressTime.current ?? 0);\n    if (pressDuration < 500) {\n      playPauseAudio();\n    }\n  };\n\n  const onTouchMove = () => {\n    if (Platform.OS === \"ios\") {\n      pressTime.current = null;\n    }\n  };\n\n  const displayDuration = useCallback(\n    () => (\n      <Text style={playProgressTextStyle}>\n        {`${String(Math.floor((currentTime || 0) / 60)).padStart(2, \"0\")}:${String(\n          Math.floor((currentTime || 0) % 60)\n        ).padStart(2, \"0\")}`}\n        /\n        {`${String(Math.floor((duration || 0) / 60)).padStart(2, \"0\")}:${String(\n          Math.floor((duration || 0) % 60)\n        ).padStart(2, \"0\")}`}\n      </Text>\n    ),\n    [currentTime, duration]\n  );\n\n  return (\n    <View style={playViewContainerStyle}>\n      <View\n        style={playIconContainerStyle}\n        onTouchStart={handleTouchStart}\n        onTouchEnd={handleTouchEnd}\n        onTouchMove={onTouchMove}\n      >\n        <Icon\n          name={status === \"playing\" ? \"pause-fill\" : \"play-arrow-fill\"}\n          height={playIconStyle?.height}\n          width={playIconStyle?.width}\n          color={playIconStyle?.tintColor}\n          imageStyle={playIconStyle}\n          icon={status === \"playing\" ? pauseIcon : playIcon}\n        />\n      </View>\n\n      <View style={{ flex: 1, flexDirection: \"row\", alignItems: \"center\", width: \"100%\" }}>\n        <AnimatedAudioWaves\n          waveStyle={waveStyle}\n          waveContainerStyle={waveContainerStyle}\n          isAnimating={status === \"playing\"}\n        />\n        {displayDuration()}\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/CometChatMediaRecorder.tsx",
    "content": "import React, { useEffect, useMemo, useRef } from \"react\";\nimport {\n  ActivityIndicator,\n  Alert,\n  Linking,\n  NativeModules,\n  PermissionsAndroid,\n  Platform,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\nimport { AnimatingMic } from \"./AnimatingMic\";\nimport { Timer } from \"./Timer\";\nimport { CometChatAudioPreview } from \"./CometChatAudioPreview/CometChatAudioPreview\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\nexport interface CometChatMediaRecorderInterface {\n  onClose?: Function;\n  onPlay?: Function;\n  onPause?: Function;\n  onStop?: Function;\n  onSend?: Function;\n  onStart?: Function;\n  style?: CometChatTheme[\"mediaRecorderStyle\"];\n}\n\ntype RecordingState = \"initial\" | \"recording\" | \"paused\" | \"stopped\" | \"loading\";\n\nexport const CometChatMediaRecorder = (props: CometChatMediaRecorderInterface) => {\n  const { onClose, onPause, onPlay, onSend, onStop, onStart, style } = props;\n  const [time, setTime] = React.useState(0);\n  const [recordedFile, setRecordedFile] = React.useState(\"\");\n  const [recordingState, setRecordedState] = React.useState<RecordingState>(\"initial\");\n  const recordingTimeRef = useRef({\n    recordingStartedAt: 0,\n    recordingTotalDuration: 0,\n  });\n\n  const theme = useTheme();\n  const {t}=useCometChatTranslation()\n  const mergedStyle = useMemo(() => {\n    return deepMerge(theme.mediaRecorderStyle, style ?? {});\n  }, [theme, style]);\n\n  const _audioBubbleStyle = theme.messageListStyles.outgoingMessageBubbleStyles?.audioBubbleStyles;\n\n  useEffect(() => {\n    setRecordedState(\"loading\"); // Set loading state before recording starts\n    recordingInitiator(); // Automatically start recording\n  }, []);\n\n  useEffect(() => {\n    return () => {\n      if (Platform.OS === \"android\") {\n        PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO).then((res) => {\n          if (!res) return;\n          deleteFile();\n        });\n      } else {\n        deleteFile();\n      }\n    };\n  }, []);\n\n  function deleteFile() {\n    NativeModules.FileManager.deleteFile((success: any) => console.log(\"Filepath delete\", success));\n    NativeModules.FileManager.releaseMediaResources((result: any) => {});\n    setRecordedFile(\"\");\n    // Stop playing audio here\n  }\n\n  function permissionAlert() {\n    Alert.alert('', t(\"MICROPHONE_PERMISSION\"), [\n      {\n        style: \"cancel\",\n        text: t(\"CANCEL\"),\n      },\n      {\n        style: \"default\",\n        text: t(\"SETTINGS\"),\n        onPress: () => {\n          Linking.openSettings();\n        },\n      },\n    ]);\n  }\n\n  const recordingInitiator = async (): Promise<void> => {\n    let microphonePermission = Platform.select({\n      ios: true,\n      android: await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO),\n    })!;\n\n    if (microphonePermission) {\n      NativeModules.FileManager.startRecording((filepath: any) => {\n        recordingTimeRef.current.recordingStartedAt = Date.now();\n        recordingTimeRef.current.recordingTotalDuration = 0;\n        setRecordedState(\"recording\");\n        if (Platform.OS === \"ios\") {\n          try {\n            let resObj = JSON.parse(filepath);\n            if (resObj?.granted === false) {\n              permissionAlert();\n              onClose && onClose();\n            }\n          } catch (error) {\n            permissionAlert();\n            onClose && onClose();\n          }\n        }\n      });\n    } else {\n      permissionAlert();\n      onClose && onClose();\n    }\n  };\n\n  const _onStop = () => {\n    NativeModules.FileManager.releaseMediaResources((result: any) => {\n      setRecordedState(\"stopped\");\n      recordingTimeRef.current.recordingTotalDuration +=\n        Date.now() - recordingTimeRef.current.recordingStartedAt;\n      if (Platform.OS === \"ios\") {\n        recordingTimeRef.current.recordingTotalDuration = JSON.parse(result).duration * 1000;\n      }\n      setRecordedFile(JSON.parse(result)?.file);\n      onStop && onStop(JSON.parse(result)?.file);\n    });\n  };\n\n  const startRecording = () => {\n    // TODO: Set time to 0\n    setRecordedState(\"loading\");\n    setRecordedFile(\"\");\n    NativeModules.FileManager.deleteFile((success: any) => {\n      NativeModules.FileManager.startRecording((result: any) => {\n        setRecordedState(\"recording\");\n      });\n    });\n    onStart && onStart();\n  };\n\n  // const _onPause = () => {\n  //   NativeModules.FileManager.pausePlaying((filepath) => {\n  //     console.log(\"Filepath onRecorderAudioPaused\", filepath);\n  //     onPause && onPause();\n  //     setRecordedPlaying(false);\n  //     // clearTimeout(stopRecordingIntervalId);\n  //     console.log(\"timeout cleared\", stopRecordingIntervalId);\n  //   });\n  // };\n\n  // const _onClose = () => {\n  //   _onPause();\n  //   setRecordedFile(\"\");\n  //   setRecordedPlaying(false);\n  //   NativeModules.FileManager.releaseMediaResources((filepath) => {\n  //     console.log(\"Filepath onClose\", filepath);\n  //   });\n  //   onClose && onClose();\n  // };\n\n  const _onDelete = () => {\n    // Stop playing\n    setRecordedFile(\"\");\n    setRecordedState(\"initial\");\n    NativeModules.FileManager.releaseMediaResources((filepath: any) => {\n      //console.log(\"Filepath onClose\", filepath);\n    });\n    onClose && onClose();\n  };\n\n  const _onSend = () => {\n    NativeModules.FileManager.releaseMediaResources((result: any) => {\n      //console.log(\"Filepath _stopRecorderAudio\", result);\n    });\n    onSend && onSend(recordedFile);\n    onClose && onClose();\n  };\n\n  const pressableIconStyle = useMemo(() => {\n    return {\n      padding: 8,\n      backgroundColor: theme.color.background1,\n      shadowColor: \"#000\",\n      shadowOffset: {\n        width: 0,\n        height: 2,\n      },\n      shadowOpacity: 0.23,\n      shadowRadius: 2.62,\n\n      elevation: 4,\n    };\n  }, [theme]);\n\n  return (\n    <View\n      style={{\n        paddingHorizontal: theme.spacing.padding.p5,\n        backgroundColor: theme.color.background1,\n      }}\n    >\n      {recordingState !== \"stopped\" && (\n        <AnimatingMic\n          isAnimating={recordingState === \"recording\"}\n          style={mergedStyle?.animationStyle}\n        />\n      )}\n      {(recordingState === \"recording\" || recordingState === \"paused\") && (\n        <Timer\n          startTime={\n            recordingTimeRef.current && recordingTimeRef.current.recordingStartedAt\n              ? recordingTimeRef.current.recordingStartedAt\n              : Date.now()\n          }\n          paused={[\"paused\"].includes(recordingState)}\n        />\n      )}\n      {recordingState === \"stopped\" && (\n        <View\n          style={{\n            backgroundColor: theme.color.primary,\n            padding: theme.spacing.padding.p2,\n            borderRadius: theme.spacing.radius.r4,\n            width: \"100%\",\n            marginBottom: theme.spacing.padding.p5,\n          }}\n        >\n          <CometChatAudioPreview\n            audioUrl={recordedFile}\n            playViewContainerStyle={{\n              width: \"100%\",\n              height: 32,\n              gap: theme.spacing.padding.p3,\n              flexDirection: \"row\",\n            }}\n            playIconStyle={_audioBubbleStyle?.playIconStyle}\n            playIconContainerStyle={_audioBubbleStyle?.playIconContainerStyle}\n            waveStyle={_audioBubbleStyle?.waveStyle}\n            waveContainerStyle={{\n              flexDirection: \"row\",\n              alignItems: \"center\",\n              height: 30,\n              overflow: \"hidden\",\n              width: \"100%\",\n              flex: 1,\n              marginRight: theme.spacing.margin.m2,\n            }}\n            playProgressTextStyle={_audioBubbleStyle?.playProgressTextStyle}\n          />\n        </View>\n      )}\n      <View\n        style={{\n          paddingVertical: 20,\n          flexDirection: \"row\",\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          gap: 20,\n        }}\n      >\n        {[\"paused\", \"stopped\", \"recording\"].includes(recordingState) && (\n          <TouchableOpacity onPress={_onDelete}>\n            <Icon\n              name='delete-fill'\n              height={theme.spacing.spacing.s6}\n              width={theme.spacing.spacing.s6}\n              color={\n                mergedStyle?.deleteIconStyle?.iconStyle?.tintColor ?? theme.color.iconSecondary\n              }\n              icon={mergedStyle?.deleteIconStyle?.icon}\n              containerStyle={mergedStyle?.deleteIconStyle?.containerStyle}\n            />\n          </TouchableOpacity>\n        )}\n        <TouchableOpacity\n          disabled={recordingState === \"loading\"}\n          onPress={() => {\n            if (recordingState === \"initial\") {\n              setRecordedState(\"loading\");\n              recordingInitiator();\n            } else if (recordingState === \"recording\") {\n              NativeModules.FileManager.pauseRecording().then((result: any) => {\n                setRecordedState(\"paused\");\n                recordingTimeRef.current.recordingTotalDuration +=\n                  Date.now() - recordingTimeRef.current.recordingStartedAt;\n\n              });\n              // _onStop();\n            } else if (recordingState === \"paused\") {\n              NativeModules.FileManager.resumeRecording().then((result: any) => {\n                setRecordedState(\"recording\");\n              });\n            } else if (recordingState === \"stopped\") {\n              _onSend();\n            }\n          }}\n        >\n          {recordingState === \"loading\" ? (\n            <ActivityIndicator size='small' color={theme.color.primary} />\n          ) : (\n            <Icon\n              name={\n                recordingState === \"initial\" || recordingState === \"paused\"\n                  ? \"mic-fill\"\n                  : recordingState === \"stopped\"\n                  ? \"send-fill\"\n                  : \"pause-fill\"\n              }\n              height={theme.spacing.spacing.s8}\n              width={theme.spacing.spacing.s8}\n              color={\n                recordingState === \"initial\" || recordingState === \"paused\"\n                  ? mergedStyle?.recordIconStyle?.iconStyle?.tintColor\n                  : recordingState === \"stopped\"\n                  ? mergedStyle?.sendIconStyle?.iconStyle?.tintColor ?? theme.color.primary\n                  : mergedStyle?.pauseIconStyle?.iconStyle?.tintColor ?? theme.color.error\n              }\n              icon={\n                recordingState === \"initial\" || recordingState === \"paused\"\n                  ? mergedStyle?.recordIconStyle?.icon\n                  : recordingState === \"stopped\"\n                  ? mergedStyle?.sendIconStyle?.icon\n                  : mergedStyle?.pauseIconStyle?.icon\n              }\n              containerStyle={\n                recordingState === \"initial\" || recordingState === \"paused\"\n                  ? mergedStyle?.recordIconStyle?.containerStyle\n                  : recordingState === \"stopped\"\n                  ? mergedStyle?.sendIconStyle?.containerStyle\n                  : mergedStyle?.pauseIconStyle?.containerStyle\n              }\n              imageStyle={\n                recordingState === \"initial\" || recordingState === \"paused\"\n                  ? mergedStyle?.recordIconStyle?.iconStyle\n                  : recordingState === \"stopped\"\n                  ? mergedStyle?.sendIconStyle?.iconStyle\n                  : mergedStyle?.pauseIconStyle?.iconStyle\n              }\n            />\n          )}\n        </TouchableOpacity>\n        {(recordingState === \"recording\" || recordingState === \"paused\") && (\n          <TouchableOpacity\n            onPress={() => {\n              setRecordedState(\"loading\");\n              _onStop();\n            }}\n          >\n            <Icon\n              name='stop-fill'\n              height={theme.spacing.spacing.s6}\n              width={theme.spacing.spacing.s6}\n              color={mergedStyle?.stopIconStyle?.iconStyle?.tintColor ?? theme.color.iconSecondary}\n              icon={mergedStyle?.stopIconStyle?.icon}\n              containerStyle={mergedStyle?.stopIconStyle?.containerStyle}\n              imageStyle={mergedStyle?.stopIconStyle?.iconStyle}\n            />\n          </TouchableOpacity>\n        )}\n        {recordingState === \"stopped\" && (\n          <TouchableOpacity\n            onPress={() => {\n              startRecording();\n            }}\n          >\n            <Icon\n              name='mic-fill'\n              height={theme.spacing.spacing.s6}\n              width={theme.spacing.spacing.s6}\n              color={mergedStyle?.recordIconStyle?.iconStyle?.tintColor ?? theme.color.iconSecondary}\n              icon={mergedStyle?.recordIconStyle?.icon}\n              containerStyle={mergedStyle?.recordIconStyle?.containerStyle}\n              imageStyle={mergedStyle?.recordIconStyle?.iconStyle}\n            />\n          </TouchableOpacity>\n        )}\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/Timer.tsx",
    "content": "import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport { StyleSheet, Text, View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\n\ninterface TimerProps {\n  startTime: number;\n  resetKey?: any;\n  paused: boolean;\n}\n\nexport const Timer = ({ resetKey, paused }: TimerProps) => {\n  const theme = useTheme();\n  const [time, setTime] = useState(0);\n\n  // We'll store our interval here so we can clear it whenever \"paused\" changes\n  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n  /**\n   * If you need to reset the timer to 0 whenever `resetKey` changes,\n   * for example when user starts a new recording, do it here.\n   */\n  useEffect(() => {\n    // Whenever `resetKey` changes, reset the timer to 0\n    setTime(0);\n  }, [resetKey]);\n\n  /**\n   * Start an interval to increment `time` every second if NOT paused.\n   * If paused, clear the interval so time does not continue incrementing.\n   */\n  useEffect(() => {\n    if (!paused) {\n      // Not paused -> start counting\n      intervalRef.current = setInterval(() => {\n        setTime((prev) => prev + 1);\n      }, 1000);\n    } else {\n      // Paused -> clear interval, so time is frozen\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n        intervalRef.current = null;\n      }\n    }\n\n    // Cleanup when unmounting or when effect re-runs\n    return () => {\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n        intervalRef.current = null;\n      }\n    };\n  }, [paused]);\n\n  // Format HH:MM:SS\n  const formatTime = useCallback((timeInSeconds: number) => {\n    const hours = String(Math.floor(timeInSeconds / 3600)).padStart(2, \"0\");\n    const minutes = String(Math.floor((timeInSeconds % 3600) / 60)).padStart(2, \"0\");\n    const seconds = String(timeInSeconds % 60).padStart(2, \"0\");\n    return `${hours}:${minutes}:${seconds}`;\n  }, []);\n\n  return (\n    <View style={styles.container}>\n      <Text\n        style={[\n          theme.typography.heading4.regular,\n          {\n            color: theme.color.textPrimary,\n          },\n        ]}\n      >\n        {formatTime(time)}\n      </Text>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/index.ts",
    "content": "import { CometChatMediaRecorder, CometChatMediaRecorderInterface } from \"./CometChatMediaRecorder\";\nexport { CometChatMediaRecorder };\n\nexport type { CometChatMediaRecorderInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMediaRecorder/style.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { DeepPartial } from \"../../helper/types\";\n\nexport const Style = StyleSheet.create({\n  imageStyle: {},\n  buttonStyle: {},\n  buttonContainer: {\n    flex: 1,\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n  },\n  soundBarContainer: {\n    marginBottom: 20,\n    flexDirection: \"row\",\n  },\n  timerContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n  soundBar: {\n    width: 5,\n    height: 10,\n    borderRadius: 5,\n    marginHorizontal: 1,\n  },\n});\n\nconst commonIconContainerStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n) => {\n  return {\n    padding: 8,\n    backgroundColor: color.background1,\n    borderWidth: 1,\n    borderColor: color.borderLight,\n    borderRadius: spacing.radius.max,\n    shadowColor: \"#000\",\n    shadowOffset: {\n      width: 0,\n      height: 2,\n    },\n    shadowOpacity: 0.23,\n    shadowRadius: 2.62,\n\n    elevation: 4,\n  };\n};\n\nexport const getMediaRecorderStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CometChatTheme[\"mediaRecorderStyle\"]> => {\n  return {\n    recordIconStyle: {\n      containerStyle: commonIconContainerStyle(color, spacing, typography),\n    },\n    playIconStyle: {\n      containerStyle: commonIconContainerStyle(color, spacing, typography),\n    },\n    pauseIconStyle: {\n      containerStyle: commonIconContainerStyle(color, spacing, typography),\n    },\n    deleteIconStyle: {\n      containerStyle: commonIconContainerStyle(color, spacing, typography),\n    },\n    stopIconStyle: {\n      containerStyle: commonIconContainerStyle(color, spacing, typography),\n    },\n    sendIconStyle: {\n      containerStyle: commonIconContainerStyle(color, spacing, typography),\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMessageBubble/CometChatMessageBubble.tsx",
    "content": "import React, { memo, useState, useCallback, useMemo, JSX } from \"react\";\nimport { View, ViewProps } from \"react-native\";\nimport { BubbleStyles } from \"../../../theme/type\";\nimport { MessageBubbleAlignmentType } from \"../../base/Types\";\n\n/**\n * Props for the CometChatMessageBubble component.\n */\nexport interface CometChatMessageBubbleInterface {\n  /**\n   * The unique identifier of the message bubble.\n   *\n   * @type {string}\n   */\n  id: string;\n  /**\n   * The leading view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  LeadingView?: JSX.Element | null;\n  /**\n   * The header view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  HeaderView?: JSX.Element | null;\n  /**\n   * The status info view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  StatusInfoView?: JSX.Element | null;\n  /**\n   * The reply view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  ReplyView?: JSX.Element | null;\n  /**\n   * The bottom view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  BottomView?: JSX.Element | null;\n  /**\n   * The content view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  ContentView?: JSX.Element | null;\n  /**\n   * The thread view of the message bubble.\n   *\n   * @type {JSX.Element | null}\n   */\n  ThreadView?: JSX.Element | null;\n  /**\n   * The footer view of the message bubble. Can be a JSX element or a function returning a JSX element.\n   *\n   * @type {JSX.Element | ((params: { maxContentWidth: number }) => JSX.Element) | null}\n   */\n  FooterView?: JSX.Element | ((params: { maxContentWidth: number }) => JSX.Element) | null;\n  /**\n   * The alignment of the message bubble.\n   *\n   * @type {MessageBubbleAlignmentType}\n   */\n  alignment?: MessageBubbleAlignmentType;\n  /**\n   * Custom styles for the message bubble.\n   *\n   * @type {BubbleStyles}\n   */\n  style?: BubbleStyles;\n}\n\n/**\n * CometChatMessageBubble renders a customizable message bubble with optional header, content, footer,\n * and additional views such as leading, bottom, and thread views. The component adjusts its alignment\n * based on the provided `alignment` prop.\n *\n * @param {CometChatMessageBubbleInterface} props - Props for configuring the message bubble.\n * @returns {JSX.Element} The rendered message bubble.\n */\nexport const CometChatMessageBubble = memo(\n  ({\n    HeaderView,\n    StatusInfoView,\n    ReplyView,\n    ContentView,\n    FooterView,\n    LeadingView,\n    BottomView,\n    ThreadView,\n    alignment,\n    id,\n    style,\n  }: CometChatMessageBubbleInterface) => {\n    const [_width, setWidth] = useState<number | undefined>();\n\n    /**\n     * Handles layout changes by updating the component's width.\n     *\n     * @param {any} event - The layout event.\n     */\n    const handleLayout = useCallback(\n      (event: any) => {\n        const { width: newWidth } = event.nativeEvent.layout;\n        // Update width only if it has changed\n        if (newWidth !== _width) {\n          setWidth(newWidth);\n        }\n      },\n      [_width]\n    );\n\n    const alignItems = useMemo(() => {\n      return alignment === \"right\" ? \"flex-end\" : alignment === \"left\" ? \"flex-start\" : alignment;\n    }, [alignment]);\n\n    return (\n      <View\n        style={{\n          width: \"100%\",\n          alignItems: alignItems,\n        }}\n      >\n        <View style={{ flexDirection: \"row\" } as ViewProps}>\n          {LeadingView && LeadingView}\n          <View style={{ marginStart: 4, maxWidth: \"80%\" } as ViewProps}>\n            {HeaderView && HeaderView}\n            <View style={{ ...style?.containerStyle, padding: 0, paddingHorizontal: 0, paddingVertical: 0, paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0 }} onLayout={handleLayout}>\n              {ReplyView && ReplyView}\n              <View style={{\n                padding: style?.containerStyle?.padding,\n                paddingHorizontal: style?.containerStyle?.paddingHorizontal,\n                paddingVertical: style?.containerStyle?.paddingVertical,\n                paddingTop: style?.containerStyle?.paddingTop,\n                paddingBottom: style?.containerStyle?.paddingBottom,\n                paddingLeft: style?.containerStyle?.paddingLeft,\n                paddingRight: style?.containerStyle?.paddingRight\n              }}>\n                {ContentView && ContentView}\n                {StatusInfoView && StatusInfoView}\n                {BottomView && BottomView}\n              </View>\n            </View>\n            {_width && (\n              <View style={{ maxWidth: _width }}>\n                {FooterView\n                  ? typeof FooterView === \"function\"\n                    ? FooterView({ maxContentWidth: _width }) // Call function if FooterView is a function\n                    : FooterView // Render JSX element directly\n                  : null}\n              </View>\n            )}\n            {ThreadView && ThreadView}\n          </View>\n        </View>\n      </View>\n    );\n  }\n);\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMessageBubble/index.ts",
    "content": "import { CometChatMessageBubble, CometChatMessageBubbleInterface } from \"./CometChatMessageBubble\";\n\nexport { CometChatMessageBubble };\nexport type {CometChatMessageBubbleInterface};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMessageBubble/style.ts",
    "content": ""
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMessageInput/CometChatMessageInput.tsx",
    "content": "import React, { JSX, RefObject } from \"react\";\nimport {\n  ColorValue,\n  NativeSyntheticEvent,\n  TextInput,\n  TextInputSelectionChangeEventData,\n  TextStyle,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { CometChatUIEventHandler, CometChatUIEvents } from \"../../events\";\nimport { ViewAlignment } from \"../../constants/UIKitConstants\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatMessageInput component.\n */\nexport interface CometChatMessageInputInterface {\n  /**\n   * Text for the input.\n   *\n   * @type {string}\n   */\n  text?: string;\n  /**\n   * Placeholder text for the input.\n   *\n   * @type {string}\n   */\n  placeHolderText?: string;\n  /**\n   * Callback function invoked when the input text changes.\n   *\n   * @param {string} newText - The new text entered.\n   */\n  onChangeText?: (arg0: string) => void;\n  /**\n   * Custom style for the message input.\n   */\n  style?: CometChatTheme[\"messageComposerStyles\"][\"messageInputStyles\"];\n  /**\n   * Maximum height for the input.\n   *\n   * @type {number}\n   */\n  maxHeight?: number;\n  /**\n   * React component for the voice recording button.\n   *\n   * @type {JSX.Element}\n   */\n  VoiceRecordingButtonView?: JSX.Element;\n  /**\n   * React component for the secondary button.\n   *\n   * @type {JSX.Element}\n   */\n  SecondaryButtonView?: JSX.Element;\n  /**\n   * React component for the auxiliary button.\n   *\n   * @type {JSX.Element}\n   */\n  AuxiliaryButtonView?: JSX.Element;\n  /**\n   * Placement for the auxiliary button.\n   *\n   * @type {\"left\" | \"right\"}\n   */\n  auxiliaryButtonAlignment?: \"left\" | \"right\";\n  /**\n   * React component for the primary button.\n   *\n   * @type {React.FC}\n   */\n  PrimaryButtonView?: React.FC;\n  /**\n   * Callback for when the text selection changes.\n   *\n   * @param {NativeSyntheticEvent<TextInputSelectionChangeEventData>} event - The selection change event.\n   */\n  onSelectionChange?: (e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void;\n  /**\n   * Reference for the TextInput component.\n   *\n   * @type {RefObject<any>}\n   */\n  messageInputRef?: RefObject<any>;\n  /**\n   * Selection state for the TextInput to control cursor position.\n   *\n   * @type {{ start: number; end: number } | undefined}\n   */\n  selection?: { start: number; end: number };\n}\n\n/**\n * CometChatMessageInput renders a message input field with a divider and action buttons.\n * It supports auxiliary, secondary, and primary buttons along with search input functionality.\n *\n *   Props for the component.\n *   The rendered message input component.\n */\nexport const CometChatMessageInput = (props: CometChatMessageInputInterface) => {\n  const theme = useTheme();\n  const { t } = useCometChatTranslation()\n  const {\n    text = \"\",\n    placeHolderText = t(\"ENTER_YOUR_MESSAGE_HERE\"),\n    onChangeText,\n    style,\n    SecondaryButtonView,\n    AuxiliaryButtonView,\n    auxiliaryButtonAlignment = \"left\",\n    PrimaryButtonView,\n    onSelectionChange,\n    messageInputRef,\n    VoiceRecordingButtonView,\n    selection,\n  } = props;\n\n  return (\n    <View style={style?.containerStyle as ViewStyle}>\n      <TextInput\n        ref={messageInputRef}\n        style={style?.textStyle as TextStyle}\n        onChangeText={onChangeText}\n        placeholderTextColor={style?.placeHolderTextColor as ColorValue}\n        multiline\n        textAlignVertical='top'\n        placeholder={placeHolderText}\n        onSelectionChange={onSelectionChange}\n        selection={selection}\n        onFocus={() => {\n          CometChatUIEventHandler.emitUIEvent(CometChatUIEvents.hidePanel, {\n            alignment: ViewAlignment.composerBottom,\n            child: () => null, // Hide the panel content.\n          });\n        }}\n      >\n        {text}\n      </TextInput>\n      <View style={style?.dividerStyle as ViewStyle} />\n      <View\n        style={{\n          flexDirection: \"row\",\n          justifyContent: \"space-between\",\n          borderTopColor: theme.color.background1,\n          paddingHorizontal: 12,\n          paddingBottom: 8,\n          paddingTop: 4,\n        }}\n      >\n        <View style={{ flexDirection: \"row\", alignItems: \"center\", gap: theme.spacing.spacing.s4 }}>\n          {SecondaryButtonView}\n          {VoiceRecordingButtonView}\n          {auxiliaryButtonAlignment === \"left\" && AuxiliaryButtonView}\n        </View>\n        <View style={{ flexDirection: \"row\", alignItems: \"center\", gap: theme.spacing.spacing.s4 }}>\n          {auxiliaryButtonAlignment === \"right\" && AuxiliaryButtonView}\n          {PrimaryButtonView && <PrimaryButtonView />}\n        </View>\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMessageInput/index.ts",
    "content": "import {\n  CometChatMessageInput,\n  CometChatMessageInputInterface,\n} from \"./CometChatMessageInput\";\nexport {\n  CometChatMessageInput,\n};\nexport type {\n  CometChatMessageInputInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatMessageInput/styles.ts",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const styles = StyleSheet.create({\n  textInput: {\n    fontSize: 15,\n    fontWeight: \"400\",\n    padding: 10,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatNewMessageIndicator/CometChatNewMessageIndicator.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { Text, View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { NewMessageIndicatorStyle, getNewMessageIndicatorStyle } from \"./styles\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatNewMessageIndicator component.\n */\nexport interface CometChatNewMessageIndicatorInterface {\n  /**\n   * Custom styles for the new message indicator component.\n   */\n  style?: NewMessageIndicatorStyle;\n  /**\n   * Text to display in the indicator.\n   * Default: \"New Messages\"\n   */\n  text?: string;\n  /**\n   * Custom component to render instead of the default indicator.\n   */\n  NewMessageIndicatorView?: React.ComponentType<any>;\n}\n\n/**\n * CometChatNewMessageIndicator is a component for displaying a \"New Messages\" separator.\n *\n * It renders a horizontal line with a text label in the center.\n * The component is fully customizable via styles or by providing a custom view.\n */\nexport const CometChatNewMessageIndicator = React.memo((props: CometChatNewMessageIndicatorInterface) => {\n  const { style = {}, text, NewMessageIndicatorView } = props;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  // Merge default styles with any custom style overrides.\n  const indicatorStyles = useMemo(() => {\n    const defaultStyles = getNewMessageIndicatorStyle(theme);\n    return deepMerge(defaultStyles, style);\n  }, [theme, style]);\n\n  if (NewMessageIndicatorView) {\n    return <NewMessageIndicatorView {...props} />;\n  }\n\n  return (\n    <View style={indicatorStyles.containerStyle}>\n      <View style={indicatorStyles.lineStyle} />\n      <Text style={indicatorStyles.textStyle} numberOfLines={1}>{text || t(\"NEW\")}</Text>\n      <View style={indicatorStyles.lineStyle} />\n    </View>\n  );\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatNewMessageIndicator/index.ts",
    "content": "export { CometChatNewMessageIndicator } from \"./CometChatNewMessageIndicator\";\nexport type { CometChatNewMessageIndicatorInterface } from \"./CometChatNewMessageIndicator\";\nexport type { NewMessageIndicatorStyle } from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatNewMessageIndicator/styles.ts",
    "content": "import { StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport type NewMessageIndicatorStyle = {\n  containerStyle?: ViewStyle;\n  textStyle?: TextStyle;\n  lineStyle?: ViewStyle;\n};\n\nexport const getNewMessageIndicatorStyle = (\n  theme: CometChatTheme\n): NewMessageIndicatorStyle => {\n  return StyleSheet.create({\n    containerStyle: {\n      flexDirection: \"row\",\n      alignItems: \"center\",\n      justifyContent: \"center\",\n      marginVertical: 8,\n      width: \"100%\",\n    },\n    textStyle: {\n      color: theme.color.error,\n      ...theme.typography.caption1.medium,\n      marginHorizontal: 8,\n    },\n    lineStyle: {\n      flex: 1,\n      height: 1,\n      backgroundColor: theme.color.error,\n      opacity: 0.5,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatQuickReactions/CometChatQuickReactions.tsx",
    "content": "import React from \"react\";\nimport { ImageSourcePropType, Text, TouchableOpacity, View } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\n\n/**\n * Props for the CometChatQuickReactions component.\n */\nexport interface CometChatQuickReactionsProps {\n  /**\n   * An array of quick reaction emojis.\n   * Accepts a tuple with up to five strings.\n   */\n  quickReactions?: [string, string?, string?, string?, string?];\n  /**\n   * Custom style for the quick reactions component.\n   */\n  style?: CometChatTheme[\"quickReactionStyle\"];\n  /**\n   * Callback invoked when a reaction emoji is pressed.\n   *\n   * @param {string} emoji - The emoji that was pressed.\n   */\n  onReactionPress?: (emoji: string) => void;\n  /**\n   * Callback invoked when the add reaction button is pressed.\n   */\n  onAddReactionPress?: () => void;\n  /**\n   * URL for the add reaction icon.\n   */\n  addReactionUrl?: ImageSourcePropType;\n}\n\n/**\n * CometChatQuickReactions displays a list of quick reaction emojis along with an option to add a new reaction.\n *\n * @param {CometChatQuickReactionsProps} props - Props for the component.\n * @returns {JSX.Element} The rendered quick reactions component.\n */\nexport const CometChatQuickReactions = (props: CometChatQuickReactionsProps) => {\n  const { quickReactions, style, onReactionPress, onAddReactionPress, addReactionUrl } = props;\n  const theme = useTheme();\n\n  /**\n   * Returns the array of emojis to be displayed.\n   *\n   * @returns {string[]} An array of emoji strings.\n   */\n  function getEmojis(): string[] {\n    let defaultEmojis = Array.isArray(quickReactions) ? quickReactions.filter((e): e is string => typeof e === 'string') : [\"👍\", \"❤️\", \"😂\", \"😢\", \"🙏\"];\n    return defaultEmojis;\n  }\n\n  return (\n    <View style={[theme.quickReactionStyle.containerStyle, style?.containerStyle]}>\n      {getEmojis().map((item, index) => (\n        <TouchableOpacity\n          key={index}\n          style={[theme.quickReactionStyle.emojiContainerStyle, style?.emojiContainerStyle]}\n          onPress={() => onReactionPress && onReactionPress(item)}\n        >\n          <Text\n            style={[\n              { fontSize: 25, color: theme.color.textPrimary },\n              { ...theme.typography.heading1.regular },\n            ]}\n          >\n            {item}\n          </Text>\n        </TouchableOpacity>\n      ))}\n      <TouchableOpacity\n        onPress={() => onAddReactionPress && onAddReactionPress()}\n        style={[theme.quickReactionStyle.emojiContainerStyle, style?.emojiContainerStyle]}\n      >\n        <Icon name=\"add-reaction\" color={theme.color.iconSecondary} />\n      </TouchableOpacity>\n    </View>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatQuickReactions/QuickReactionsStyle.tsx",
    "content": "import { CometChatTheme } from \"../../../theme/type\";\n\nexport const getQuickReactionStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"quickReactionStyle\"] => {\n  return {\n    containerStyle: {\n      backgroundColor: color.background1,\n      paddingVertical: spacing.padding.p2,\n      // borderWidth: 1,\n      // borderColor: color.borderLight,\n      flexDirection: 'row',\n      gap: spacing.padding.p2,\n      justifyContent: 'space-evenly',\n    },\n    emojiContainerStyle: {\n      backgroundColor: color.background3,\n      borderRadius: spacing.radius.max,\n      minHeight: 40,\n      minWidth: 40,\n      justifyContent: 'center',\n      alignItems: 'center',\n    }\n  };\n};\n\nexport const getQuickReactionStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"quickReactionStyle\"] => {\n  return getQuickReactionStyleLight(color, spacing, typography);\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatQuickReactions/index.ts",
    "content": "import {\n  CometChatQuickReactions\n} from \"./CometChatQuickReactions\";\n\nexport {\n  CometChatQuickReactions\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatQuickReactions/resources/index.ts",
    "content": "import ADDREACTION from \"./AddReactions.png\";\nexport const ICONS = { ADDREACTION };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactionList/CometChatReactionList.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { JSX, useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n  ActivityIndicator,\n  FlatList,\n  Platform,\n  ScrollView,\n  Text,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { CommonUtils } from \"../../utils/CommonUtils\";\nimport { CometChatListItem } from \"../CometChatListItem\";\nimport { useTheme } from \"../../../theme\";\nimport { Skeleton } from \"../../../CometChatConversations\";\nimport { ErrorEmptyView } from \"../ErrorEmptyView/ErrorEmptyView\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CometChatUIEventHandler, MessageEvents } from \"../../events\";\nimport { messageStatus } from \"../../utils/CometChatMessageHelper\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatReactionList component.\n */\nexport interface CometChatReactionListInterface {\n  /**\n   * The message object whose reactions will be displayed.\n   */\n  message: CometChat.BaseMessage;\n  /**\n   * Callback function invoked when a reaction is pressed.\n   *\n   * @param messageReaction - The reaction object that was pressed.\n   * @param message - The message object.\n   */\n  onPress?: (messageReaction: CometChat.Reaction, message: CometChat.BaseMessage) => void;\n  /**\n   * Request builder for fetching reactions.\n   */\n  reactionsRequestBuilder?: CometChat.ReactionsRequestBuilder;\n  /**\n   * The currently selected reaction filter (e.g. \"All\" or a specific reaction).\n   */\n  selectedReaction?: string;\n  /**\n   * A custom component to render in case of an error state.\n   *\n   * @returns A JSX element representing the error state.\n   */\n  ErrorStateView?: () => JSX.Element;\n  /**\n   * Custom error text to display when an error occurs.\n   */\n  errorStateText?: string;\n  /**\n   * A custom component to render during the loading state.\n   *\n   * @returns A JSX element representing the loading state.\n   */\n  LoadingStateView?: () => JSX.Element;\n  /**\n   * Callback if reaction list becomes empty.\n   */\n  onListEmpty?: () => void;\n}\n\nexport const CometChatReactionList = (props: CometChatReactionListInterface) => {\n  const {\n    message,\n    onPress,\n    reactionsRequestBuilder,\n    selectedReaction,\n    ErrorStateView,\n    errorStateText,\n    LoadingStateView,\n    onListEmpty,\n  } = props;\n  const theme = useTheme();\n  const {t} =useCometChatTranslation()\n  const reactionListStyleFromTheme = theme.reactionListStyles.reactionListItemStyle;\n  const tabStyleFromTheme = theme.reactionListStyles.tabStyle;\n\n  const [messageReactions, setMessageReactions] = useState(message?.getReactions() || []);\n  const [currentSelectedReaction, setCurrentSelectedReaction] = useState(selectedReaction || \"All\");\n  const [reactionList, setReactionList] = useState<CometChat.Reaction[]>();\n  const [state, setState] = useState<\"loading\" | \"error\" | \"done\" | \"fetchNextOnScroll\">(\"loading\");\n  const loggedInUser = useRef<CometChat.User | null>(undefined);\n  const newMessageObj = useRef<CometChat.BaseMessage>(CommonUtils.clone(message));\n\n  const requestBuilderMap = useRef<Record<string, CometChat.ReactionsRequest>>({});\n  const reactionListMap = useRef<Record<string, CometChat.Reaction[]>>({});\n  const listRef = useRef<FlatList | null>(null);\n  const [messageObject, setMessageObject] = useState<CometChat.BaseMessage>(message);\n\n  useEffect(() => {\n    CometChat.getLoggedinUser()\n      .then((user) => (loggedInUser.current = user))\n      .catch((rej) => {\n        loggedInUser.current = null;\n        // onError && onError(rej);\n      });\n    showReactions(true);\n    let newMessageReactions = messageObject?.getReactions() || [];\n    _setAllReactions(newMessageReactions);\n  }, []);\n\n  useEffect(() => {\n    showReactions();\n  }, [currentSelectedReaction]);\n\n  const _setAllReactions = (_messageReactions: any) => {\n    let totalCount = _messageReactions.reduce((acc: any, curr: any) => acc + curr.count, 0);\n    const allReactionCountObject = new CometChat.ReactionCount(\"All\", totalCount, false);\n    setMessageReactions([allReactionCountObject, ..._messageReactions]);\n  };\n\n  const showReactions = async (firstFetch?: boolean) => {\n    const requestBuilder = getRequestBuilder(currentSelectedReaction);\n    const list = await getReactionList(requestBuilder, currentSelectedReaction);\n    if (firstFetch && currentSelectedReaction !== \"All\") {\n      await getReactionList(getRequestBuilder(\"All\"), \"All\");\n    }\n    setReactionList(list ?? []);\n  };\n\n  const getRequestBuilder = (reaction: string): CometChat.ReactionsRequest => {\n    let requestBuilder: CometChat.ReactionsRequestBuilder;\n    if (requestBuilderMap.current[reaction]) {\n      return requestBuilderMap.current[reaction];\n    }\n\n    requestBuilder =\n      reactionsRequestBuilder || new CometChat.ReactionsRequestBuilder().setLimit(10);\n    requestBuilder.setMessageId(messageObject?.getId());\n\n    if (reaction !== \"All\") {\n      requestBuilder.setReaction(reaction);\n    }\n\n    const request = requestBuilder.build();\n    requestBuilderMap.current[reaction] = request;\n    return request;\n  };\n\n  const getReactionList = async (requestBuilder: CometChat.ReactionsRequest, reaction: string) => {\n    setState(\"loading\");\n\n    if (reactionListMap.current[reaction]) {\n      setState(\"done\");\n      let list = reactionListMap.current[reaction];\n      return list;\n    }\n\n    try {\n      const list = await requestBuilder.fetchNext();\n      reactionListMap.current[reaction] = list;\n      setState(\"done\");\n      return list;\n    } catch (error: any) {\n      //console.log(\"error while fetching reactions\", error);\n      if (error?.code === \"REQUEST_IN_PROGRESS\") return null;\n      setState(\"error\");\n      return [];\n    }\n  };\n\n  const fetchNext = async () => {\n    try {\n      const requestBuilder = getRequestBuilder(currentSelectedReaction);\n      if (reactionListMap.current[currentSelectedReaction]?.length === 0) {\n        return;\n      } else {\n        const newList = await requestBuilder.fetchNext();\n        reactionListMap.current[currentSelectedReaction] = [\n          ...(reactionListMap.current?.[currentSelectedReaction] || []),\n          ...newList,\n        ];\n        setReactionList(reactionListMap.current[currentSelectedReaction]);\n      }\n      setState(\"done\");\n    } catch (error: any) {\n      //console.log(\"error while fetching next reactions\", error);\n      if (error?.code === \"REQUEST_IN_PROGRESS\") return;\n      setState(\"error\");\n    }\n  };\n\n  const reactToMessage = useCallback(\n    (emoji: string) => {\n      const msgObj = CommonUtils.clone(messageObject);\n\n      const messageId = msgObj?.getId();\n      const reactions = msgObj?.getReactions() || [];\n      const emojiObject = reactions?.find((reaction: any) => {\n        return reaction?.reaction == emoji;\n      });\n      if (emojiObject && emojiObject?.getReactedByMe()) {\n        const updatedReactions: any[] = [];\n        reactions.forEach((reaction: any) => {\n          if (reaction?.getReaction() == emoji) {\n            if (reaction?.getCount() === 1) {\n              return;\n            } else {\n              reaction.setCount(reaction?.getCount() - 1);\n              reaction.setReactedByMe(false);\n              updatedReactions.push(reaction);\n            }\n          } else {\n            updatedReactions.push(reaction);\n          }\n        });\n\n        const newMessageObj = CommonUtils.clone(msgObj);\n        newMessageObj.setReactions(updatedReactions);\n\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n          message: newMessageObj,\n          status: messageStatus.success,\n        });\n        CometChat.removeReaction(messageId, emoji)\n          .then((message: any) => {})\n          .catch((error: any) => {\n            CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n              message: msgObj,\n              status: messageStatus.success,\n            });\n            //console.log(error);\n          });\n      } else {\n        const updatedReactions: any[] = [];\n        const reactionAvailable = reactions.find((reaction: any) => {\n          return reaction?.getReaction() == emoji;\n        });\n        reactions.forEach((reaction: any) => {\n          if (reaction?.getReaction() == emoji) {\n            reaction.setCount(reaction?.getCount() + 1);\n            reaction.setReactedByMe(true);\n            updatedReactions.push(reaction);\n          } else {\n            updatedReactions.push(reaction);\n          }\n        });\n        if (!reactionAvailable) {\n          const react: CometChat.ReactionCount = new CometChat.ReactionCount(emoji, 1, true);\n          updatedReactions.push(react);\n        }\n\n        const newMessageObj = CommonUtils.clone(msgObj);\n\n        newMessageObj.setReactions(updatedReactions);\n\n        CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n          message: newMessageObj,\n          status: messageStatus.success,\n        });\n\n        CometChat.addReaction(messageId, emoji)\n          .then((response: any) => {})\n          .catch((error: any) => {\n            //console.log(error);\n            CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {\n              message: msgObj,\n              status: messageStatus.success,\n            });\n          });\n      }\n    },\n    [messageObject]\n  );\n\n  const removeReaction = (reactionObj: CometChat.Reaction) => {\n    let reactedByMe = loggedInUser.current!.getUid() === reactionObj?.getReactedBy()?.getUid();\n    if (onPress) {\n      onPress(reactionObj, newMessageObj.current);\n      return;\n    }\n    if (reactedByMe) {\n      reactToMessage(reactionObj?.getReaction());\n      let newReactionList = reactionList\n        ? [...reactionList]?.filter(\n            (reaction: CometChat.Reaction) =>\n              reaction?.getReactionId() !== reactionObj?.getReactionId()\n          )\n        : [];\n      setReactionList(newReactionList);\n\n      if (currentSelectedReaction === \"All\") {\n        reactionListMap.current[currentSelectedReaction] = [...newReactionList];\n        reactionListMap.current[reactionObj?.getReaction()] = reactionListMap.current[\n          reactionObj?.getReaction()\n        ]?.filter(\n          (reaction: CometChat.Reaction) =>\n            reaction?.getReactionId() !== reactionObj?.getReactionId()\n        );\n      } else {\n        reactionListMap.current[currentSelectedReaction] = [...newReactionList];\n        reactionListMap.current[\"All\"] = reactionListMap.current[\"All\"]?.filter(\n          (reaction: CometChat.Reaction) =>\n            reaction?.getReactionId() !== reactionObj?.getReactionId()\n        );\n      }\n\n      let newMessageReactions = [...messageReactions];\n\n      let messageReactionIndex = newMessageReactions.findIndex(\n        (reaction) => reaction?.getReaction() === reactionObj?.getReaction()\n      );\n      if (messageReactionIndex > -1) {\n        if (newMessageReactions[messageReactionIndex]?.getCount() > 1) {\n          newMessageReactions[messageReactionIndex].setCount(\n            newMessageReactions[messageReactionIndex].getCount() - 1\n          );\n          newMessageReactions[messageReactionIndex].setReactedByMe(false);\n          newMessageReactions.shift();\n          _setAllReactions(newMessageReactions);\n        } else {\n          newMessageReactions.splice(messageReactionIndex, 1);\n          newMessageReactions.shift();\n          _setAllReactions(newMessageReactions);\n          setCurrentSelectedReaction(\"All\");\n\n          // Check if all reactions are gone and call onListEmpty\n          if (newMessageReactions.length === 0 && onListEmpty) {\n            onListEmpty();\n          }\n        }\n      }\n\n      newMessageObj.current.setReactions(newMessageReactions);\n      setMessageObject(newMessageObj.current);\n    }\n  };\n\n  const subtitleView = useCallback((item: any) => {\n    let reactedByMe = loggedInUser.current!.getUid() === item?.reactedBy?.uid;\n    return reactedByMe ? (\n      <Text style={reactionListStyleFromTheme.subtitleStyle}>{t(\"TAP_TO_REMOVE\")}</Text>\n    ) : null;\n  }, []);\n\n  const _render = ({ item, index }: { item: any; index: number }) => {\n    function getName() {\n      let reactedByMe = loggedInUser.current!.getUid() === item?.reactedBy?.uid;\n      return reactedByMe ? t(\"YOU\") : item?.reactedBy?.name;\n    }\n\n    return (\n      <>\n        <CometChatListItem\n          id={item?.id || index}\n          title={getName()}\n          titleStyle={reactionListStyleFromTheme.titleStyle}\n          avatarStyle={reactionListStyleFromTheme.avatarStyle}\n          containerStyle={reactionListStyleFromTheme.containerStyle}\n          avatarName={item?.reactedBy?.name}\n          avatarURL={item?.reactedBy?.avatar ? { uri: item?.reactedBy?.avatar } : undefined}\n          SubtitleView={subtitleView(item)}\n          TrailingView={\n            <Text\n              style={[\n                reactionListStyleFromTheme.emojiStyle,\n                //toDoM: emojis look washed out on Android without a color\n                Platform.OS === \"android\" ? { color: theme.color.primary } : {},\n              ]}\n            >\n              {item?.reaction}\n            </Text>\n          }\n          onPress={(id: any) => removeReaction(item)}\n          titleSubtitleContainerStyle={reactionListStyleFromTheme.titleContainerStyle}\n        />\n      </>\n    );\n  };\n\n  const ErrorView = () => {\n    if (ErrorStateView) return <ErrorStateView />;\n    return (\n      <ErrorEmptyView\n        title={errorStateText ?? \"Oops!\"}\n        subTitle={t(\"SOMETHING_WENT_WRONG\")}\n        tertiaryTitle={t(\"WRONG_TEXT_TRY_AGAIN\")}\n        Icon={\n          <Icon\n            name='error-state'\n            size={theme.spacing.spacing.s15 * 2}\n            icon={theme.reactionListStyles.errorStateStyle?.icon}\n            height={theme.reactionListStyles.errorStateStyle?.iconStyle?.height}\n            width={theme.reactionListStyles.errorStateStyle?.iconStyle?.width}\n            imageStyle={theme.reactionListStyles.errorStateStyle?.iconStyle}\n            containerStyle={theme.reactionListStyles.errorStateStyle?.iconContainerStyle}\n          />\n        }\n        containerStyle={theme.reactionListStyles.errorStateStyle?.containerStyle}\n        titleStyle={theme.reactionListStyles.errorStateStyle?.titleStyle}\n        subTitleStyle={theme.reactionListStyles.errorStateStyle?.subtitleStyle}\n      />\n    );\n  };\n\n  const LoadingView = () => {\n    if (LoadingStateView) return <LoadingStateView />;\n    return (\n      <View\n        style={{\n          flex: 1,\n        }}\n      >\n        <Skeleton />\n      </View>\n    );\n  };\n\n  return (\n    <View style={{ flex: 1 }}>\n      {/* Slider for reactions */}\n      {state != \"error\" && (\n        <View>\n          <ScrollView\n            showsHorizontalScrollIndicator={false}\n            horizontal={true}\n            style={tabStyleFromTheme.containerStyle}\n          >\n            {messageReactions?.length > 0 &&\n              messageReactions.map((reactionObject, index) => {\n                return (\n                  <TouchableOpacity\n                    style={\n                      currentSelectedReaction === reactionObject?.getReaction()\n                        ? tabStyleFromTheme.selectedItemStyle\n                        : tabStyleFromTheme.itemStyle\n                    }\n                    key={index}\n                    onPress={() => setCurrentSelectedReaction(reactionObject?.getReaction())}\n                  >\n                    <Text\n                      style={\n                        currentSelectedReaction === reactionObject?.getReaction()\n                          ? reactionObject?.getReaction() === \"All\"\n                            ? tabStyleFromTheme.selectedItemTextStyle\n                            : tabStyleFromTheme.selectedItemEmojiStyle\n                          : reactionObject?.getReaction() === \"All\"\n                            ? tabStyleFromTheme.itemTextStyle\n                            : tabStyleFromTheme.itemEmojiStyle\n                      }\n                    >\n                      {reactionObject?.getReaction() === \"All\"\n                        ? t(\"ALL\")\n                        : reactionObject?.getReaction()}\n                    </Text>\n                    <Text\n                      style={\n                        currentSelectedReaction === reactionObject?.getReaction()\n                          ? tabStyleFromTheme.selectedItemTextStyle\n                          : tabStyleFromTheme.itemTextStyle\n                      }\n                    >\n                      {reactionObject?.getCount()}\n                    </Text>\n                  </TouchableOpacity>\n                );\n              })}\n          </ScrollView>\n\n          <View\n            style={{\n              height: 1,\n              backgroundColor: theme.color.borderLight,\n            }}\n          />\n        </View>\n      )}\n\n      {state === \"error\" ? (\n        <ErrorView />\n      ) : state === \"loading\" ? (\n        <LoadingView />\n      ) : (\n        <View style={{ flex: 1, marginBottom: 20 }}>\n          <FlatList\n            data={reactionList}\n            ref={listRef}\n            keyExtractor={(item, index) => index.toString()}\n            style={{\n              flex: 1,\n            }}\n            contentContainerStyle={{\n              paddingHorizontal: 5,\n            }}\n            renderItem={_render}\n            onMomentumScrollEnd={(event) => {\n              const contentOffsetY = event.nativeEvent.contentOffset.y; // The current scroll position\n              const contentHeight = event.nativeEvent.contentSize.height; // Total height of the content\n              const layoutHeight = event.nativeEvent.layoutMeasurement.height; // Height of the visible area\n\n              if (contentOffsetY + layoutHeight >= contentHeight - 10) {\n                setState(\"fetchNextOnScroll\");\n                fetchNext();\n              }\n            }}\n          />\n          {state == \"fetchNextOnScroll\" && (\n            <ActivityIndicator\n              color={theme.color.primary}\n              size={\"large\"}\n              style={{\n                position: \"absolute\",\n                flex: 1,\n                alignSelf: \"center\",\n                paddingHorizontal: 5,\n                bottom: \"50%\",\n              }}\n            />\n          )}\n        </View>\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactionList/ReactionListStyle.tsx",
    "content": "import { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\n\nexport const getReactionListStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"reactionListStyles\"] => {\n  return {\n    tabStyle: {\n      containerStyle: {\n        minHeight: 35\n      },\n      itemStyle: {\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        flexDirection: \"row\",\n        borderBottomWidth: spacing.spacing.s0_5,\n        borderBottomColor: \"transparent\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      },\n      selectedItemStyle: {\n        paddingHorizontal: spacing.padding.p4,\n        paddingVertical: spacing.padding.p2,\n        flexDirection: \"row\",\n        borderBottomWidth: spacing.spacing.s0_5,\n        borderBottomColor: color.primary,\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      },\n      itemEmojiStyle: {\n        color: color.textSecondary,\n        borderColor: \"transparent\",\n        ...typography.body.medium,\n      },\n      selectedItemEmojiStyle: {\n        color: color.textSecondary,\n        borderColor: \"transparent\",\n        ...typography.body.medium,\n      },\n      itemTextStyle: {\n        color: color.textSecondary,\n        marginLeft: spacing.margin.m1,\n        ...typography.body.medium,\n      },\n      selectedItemTextStyle: {\n        color: color.primary,\n        marginLeft: spacing.margin.m1,\n        ...typography.body.medium,\n      },\n    },\n    reactionListItemStyle: {\n      containerStyle: {\n        flexDirection: \"row\",\n        gap: spacing.padding.p3,\n        paddingVertical: spacing.padding.p2,\n        paddingHorizontal: spacing.padding.p5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.body.medium,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        ...typography.caption1.regular,\n      },\n      avatarStyle: {\n        containerStyle: {\n          height: 32,\n          width: 32,\n        },\n        imageStyle: {\n          borderRadius: spacing.radius.max,\n        },\n        textStyle: {},\n      },\n      emojiStyle: {\n        minHeight: 24,\n        minWidth: 24,\n        textAlign: \"center\",\n        ...typography.heading2.regular,\n      },\n      titleContainerStyle: {\n        alignSelf: 'center'\n      },\n    },\n    skeletonStyle: {\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 0.1,\n    },\n    errorStateStyle: {\n      containerStyle: {\n        marginTop: spacing.margin.m0,\n        height: \"95%\",\n        width: \"100%\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    },\n  };\n};\n\nexport const getReactionListStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): CometChatTheme[\"reactionListStyles\"] => {\n  return deepMerge(getReactionListStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 0.1,\n    },\n    errorStateStyle: {\n      containerStyle: {\n        marginTop: spacing.margin.m0,\n        height: \"95%\",\n        width: \"100%\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        paddingHorizontal: \"10%\",\n      },\n      iconContainerStyle: {\n        marginBottom: spacing.margin.m5,\n      },\n      titleStyle: {\n        color: color.textPrimary,\n        ...typography.heading3.bold,\n        marginBottom: spacing.margin.m1,\n      },\n      subtitleStyle: {\n        color: color.textSecondary,\n        textAlign: \"center\",\n        ...typography.body.regular,\n      },\n    }\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactionList/Skeleton.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, G, LinearGradient, Path, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../../../theme\";\n\nconst { width: screenWidth } = Dimensions.get(\"window\");\n\nconst SkeletonItemBottom = () => {\n  const theme = useTheme();\n  return (\n    <Svg\n      height={screenWidth / 5}\n      viewBox='0 0 360 72'\n      fill='none'\n      preserveAspectRatio='xMidYMid meet'\n    >\n      <G>\n        <Path d='M64 12H16V60H64V12Z' fill='url(#paint0_linear_4_115)' />\n      </G>\n      <Path d='M236 16.5H76V35.5H236V16.5Z' fill='url(#paint0_linear_4_115)' />\n      <Path d='M344 16.5H284V35.5H344V16.5Z' fill='url(#paint0_linear_4_115)' />\n      <Path d='M344 43.5H76V55.5H344V43.5Z' fill='url(#paint0_linear_4_115)' />\n      <Defs>\n        <LinearGradient\n          id='paint0_linear_4_115'\n          x1={16}\n          y1={36}\n          x2={64}\n          y2={36}\n          gradientUnits='userSpaceOnUse'\n        >\n          <Stop stopColor={theme.messageInformationStyles.skeletonStyle.linearGradientColors[0]} />\n          <Stop\n            offset={1}\n            stopColor={theme.messageInformationStyles.skeletonStyle.linearGradientColors[1]}\n          />\n        </LinearGradient>\n      </Defs>\n    </Svg>\n  );\n};\n\nconst SkeletonItemTop = () => {\n  const theme = useTheme();\n  return (\n    <Svg height={screenWidth / 5} viewBox='0 0 360 72' fill='none'>\n      <Path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M0 0H360V72H0V0ZM16 36C16 22.7452 26.7452 12 40 12C53.2548 12 64 22.7452 64 36C64 49.2548 53.2548 60 40 60C26.7452 60 16 49.2548 16 36ZM84 16.5C79.5817 16.5 76 20.0817 76 24.5V27.5C76 31.9183 79.5817 35.5 84 35.5H228C232.418 35.5 236 31.9183 236 27.5V24.5C236 20.0817 232.418 16.5 228 16.5H84ZM284 24.5C284 20.0817 287.582 16.5 292 16.5H336C340.418 16.5 344 20.0817 344 24.5V27.5C344 31.9183 340.418 35.5 336 35.5H292C287.582 35.5 284 31.9183 284 27.5V24.5ZM82 43.5C78.6863 43.5 76 46.1863 76 49.5C76 52.8137 78.6863 55.5 82 55.5H338C341.314 55.5 344 52.8137 344 49.5C344 46.1863 341.314 43.5 338 43.5H82Z'\n        fill={theme.color.background2}\n      />\n    </Svg>\n  );\n};\n\nexport const Skeleton = () => {\n  const theme = useTheme();\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: (1 / theme.messageInformationStyles.skeletonStyle.speed) * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n  }, [animatedValue]);\n\n  const shimmerTranslateX = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [-screenWidth * 2, screenWidth],\n  });\n\n  return (\n    <ScrollView>\n      {new Array(20).fill(0).map((_, i) => {\n        return <SkeletonItemBottom key={i} />;\n      })}\n      <Animated.View\n        style={[\n          {\n            transform: [\n              { translateX: shimmerTranslateX },\n              { translateY: -20 },\n              { rotate: \"15deg\" },\n            ],\n          },\n          styles.animatedView,\n          {\n            backgroundColor: theme.messageInformationStyles.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.messageInformationStyles.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      ></Animated.View>\n      <Animated.View\n        style={[\n          {\n            transform: [\n              { translateX: Animated.add(shimmerTranslateX, screenWidth / 2) },\n              { translateY: -20 },\n              { rotate: \"15deg\" },\n            ],\n          },\n          styles.animatedView,\n          {\n            backgroundColor: theme.messageInformationStyles.skeletonStyle.shimmerBackgroundColor,\n            opacity: theme.messageInformationStyles.skeletonStyle.shimmerOpacity,\n          },\n        ]}\n      ></Animated.View>\n      <View\n        style={{\n          position: \"absolute\",\n          top: 0,\n          left: 0,\n          right: 0,\n          bottom: 0,\n        }}\n      >\n        {new Array(20).fill(0).map((_, i) => {\n          return <SkeletonItemTop key={i} />;\n        })}\n      </View>\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  animatedView: {\n    width: \"25%\",\n    top: 0,\n    bottom: 0,\n    position: \"absolute\",\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactionList/index.ts",
    "content": "import { CometChatReactionList, CometChatReactionListInterface } from \"./CometChatReactionList\";\n\nexport {\n  CometChatReactionList,\n};\n\nexport type {\n  CometChatReactionListInterface\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactionList/resources/index.ts",
    "content": "import LoadingIcon from \"./Spineer.png\";\n\nexport { LoadingIcon };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactions/CometChatReactions.tsx",
    "content": "import React, { JSX, useLayoutEffect } from \"react\";\nimport { PixelRatio, Text, TouchableOpacity, View } from \"react-native\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { MessageBubbleAlignmentType } from \"../../base/Types\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { DeepPartial } from \"../../helper/types\";\n\n/**\n * Props for rendering reactions on a message bubble.\n */\nexport interface CometChatReactionsInterface {\n  /**\n   * The message object for which reactions will be displayed.\n   */\n  messageObject: CometChat.BaseMessage;\n  /**\n   * Custom styles for the reaction bubble.\n   */\n  style?: DeepPartial<CometChatTheme[\"messageBubbleReactionStyles\"]>;\n  /**\n   * Callback invoked when a reaction is pressed.\n   *\n   * @param reaction - The reaction count object that was pressed.\n   * @param messageObject - The message object associated with the reaction.\n   */\n  onReactionPress?: (\n    reaction: CometChat.ReactionCount,\n    messageObject: CometChat.BaseMessage\n  ) => void;\n  /**\n   * Callback invoked when a reaction is long pressed.\n   *\n   * @param reaction - The reaction count object that was long pressed.\n   * @param messageObject - The message object associated with the reaction.\n   */\n  onReactionLongPress?: (\n    reaction: CometChat.ReactionCount,\n    messageObject: CometChat.BaseMessage\n  ) => void;\n  /**\n   * Alignment of the reaction bubble.\n   */\n  alignment?: MessageBubbleAlignmentType;\n  /**\n   * Maximum width for the content inside the reaction bubble.\n   */\n  maxContentWidth?: number;\n}\n\nconst CometChatReactions = (props: CometChatReactionsInterface) => {\n  const { messageObject, style, onReactionPress, onReactionLongPress, alignment, maxContentWidth } =\n    props;\n\n  const [reactionList, setReactionList] = React.useState<(JSX.Element | null)[]>([]);\n  const reactionRef = React.useRef<CometChat.ReactionCount[]>([]);\n\n  const reactionView = (reactionObj: CometChat.ReactionCount, index : any) => {\n    let count: number = reactionObj?.getCount();\n    let Emoji: string = reactionObj?.getReaction();\n    const reactedByme = reactionObj.getReactedByMe();\n    return count >= 1 ? (\n      <TouchableOpacity\n        key={index}\n        onPress={() => {\n          onReactionPress && onReactionPress(reactionObj, messageObject!);\n        }}\n        onLongPress={() => {\n          onReactionLongPress && onReactionLongPress(reactionObj, messageObject!);\n        }}\n        style={\n          reactedByme ? style?.activeEmojiStyle?.containerStyle : style?.emojiStyle?.containerStyle\n        }\n      >\n        <Text\n          style={\n            reactedByme ? style?.activeEmojiStyle?.emojitextStyle : style?.emojiStyle?.emojitextStyle\n          }\n        >\n          {Emoji}\n        </Text>\n        <Text\n          style={\n            reactedByme\n              ? style?.activeEmojiStyle?.emojiCountTextStyle\n              : style?.emojiStyle?.emojiCountTextStyle\n          }\n        >\n          {count}\n        </Text>\n      </TouchableOpacity>\n    ) : null;\n  };\n\n  const extraEmojisView = (numberOfExtraEmojis: number) => {\n    let extraEmojis = reactionRef.current.slice(reactionRef.current.length - numberOfExtraEmojis);\n    let reactedByMe = extraEmojis.some((reaction) => reaction.getReactedByMe());\n\n    let totalCount = reactionRef.current.reduce((acc, curr) => acc + curr.getCount(), 0);\n    let AllObj = new CometChat.ReactionCount(\"All\", totalCount, false); // { reaction: \"All\", count: totalCount };\n\n    return (\n      <TouchableOpacity\n        onPress={() => {\n          onReactionPress && onReactionPress(AllObj, messageObject);\n        }}\n        onLongPress={() => {\n          onReactionLongPress && onReactionLongPress(AllObj, messageObject);\n        }}\n        key={new Date().getTime()}\n        style={\n          reactedByMe\n            ? style?.extraReactionStyle?.activeContainerStyle\n            : style?.extraReactionStyle?.containerStyle\n        }\n      >\n        <Text\n          style={\n            reactedByMe\n              ? style?.extraReactionStyle?.activeCountTextStyle\n              : style?.extraReactionStyle?.countTextStyle\n          }\n        >\n          +{numberOfExtraEmojis}\n        </Text>\n      </TouchableOpacity>\n    );\n  };\n\n  useLayoutEffect(() => {\n    reactionRef.current = messageObject?.getReactions()!;\n    let countEmoji = maxContentWidth ? 0 : 3;\n\n    // Scale width estimates by the system font scale so that when the user\n    // increases the device font/display size the layout calculation still\n    // fits reactions correctly and avoids cropping.\n    const fontScale = PixelRatio.getFontScale();\n\n    if (maxContentWidth) {\n      let remainingWidth = maxContentWidth;\n      for (let i = 0; i < reactionRef.current.length; i++) {\n        const currentReactionCount = reactionRef.current[i].getCount();\n        let widthToReduce = 0;\n        if (currentReactionCount < 10) {\n          widthToReduce = 45 * fontScale;\n        } else if (currentReactionCount < 100) {\n          widthToReduce = 55 * fontScale;\n        } else if (currentReactionCount < 1000) {\n          widthToReduce = 65 * fontScale;\n        }\n        if (remainingWidth > 30 * fontScale + widthToReduce) {\n          remainingWidth = remainingWidth - widthToReduce;\n          countEmoji++;\n          continue;\n        }\n\n        break;\n      }\n\n      countEmoji = countEmoji ? countEmoji : 1;\n    }\n    const messageReactions = reactionRef.current.slice(0, countEmoji).map(reactionView);\n\n    if (reactionRef.current.length > 0) {\n      if (reactionRef.current.length > countEmoji) {\n        messageReactions.push(extraEmojisView(reactionRef.current.length - countEmoji));\n        setReactionList(messageReactions);\n      } else setReactionList(messageReactions);\n    }\n  }, [messageObject, maxContentWidth]);\n\n  return reactionList.length ? (\n    <View\n      style={[\n        style?.reactionContainerStyle,\n        {\n          alignSelf:\n            alignment === \"right\" ? \"flex-end\" : alignment === \"center\" ? \"center\" : \"flex-start\",\n        },\n      ]}\n    >\n      {reactionList}\n    </View>\n  ) : null;\n};\n\nexport { CometChatReactions };"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactions/index.ts",
    "content": "import { CometChatReactions, CometChatReactionsInterface } from \"./CometChatReactions\";\n\nexport {\n  CometChatReactions\n};\n\nexport type {\n  CometChatReactionsInterface\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReactions/style.ts",
    "content": "import { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { DeepPartial } from \"../../helper/types\";\n\nexport const getReactionsStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: DeepPartial<CometChatTheme[\"messageBubbleReactionStyles\"]>;\n  outgoingBubbleStyle: DeepPartial<CometChatTheme[\"messageBubbleReactionStyles\"]>;\n} => {\n  return {\n    incomingBubbleStyle: {\n      reactionContainerStyle: {\n        marginTop: -3,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 2,\n        // Allow reactions row to wrap when system font size causes overflow\n        flexWrap: \"wrap\",\n      },\n      emojiStyle: {\n        containerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          minHeight: 24,\n          backgroundColor: color.background1,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        emojitextStyle: { ...typography.button.regular, color: color.textPrimary },\n        emojiCountTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n      },\n      activeEmojiStyle: {\n        containerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.extendedPrimary100,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        emojitextStyle: { ...typography.button.regular, color: color.textPrimary },\n        emojiCountTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n      },\n      extraReactionStyle: {\n        containerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.background1,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        countTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n        activeContainerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.extendedPrimary100,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        activeCountTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n      },\n    },\n    outgoingBubbleStyle: {\n      reactionContainerStyle: {\n        marginTop: -3,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 2,\n        // Allow reactions row to wrap when system font size causes overflow\n        flexWrap: \"wrap\",\n      },\n      emojiStyle: {\n        containerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.background1,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        emojitextStyle: { ...typography.button.regular, color: color.textPrimary },\n        emojiCountTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n      },\n      activeEmojiStyle: {\n        containerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.extendedPrimary100,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        emojitextStyle: { ...typography.button.regular, color: color.textPrimary },\n        emojiCountTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n      },\n      extraReactionStyle: {\n        containerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.background1,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        countTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n        activeContainerStyle: {\n          paddingVertical: spacing.padding.p0_5,\n          paddingHorizontal: spacing.padding.p2,\n          // Use minHeight instead of fixed height so the container grows\n          // when system font/display size is increased (accessibility)\n          minHeight: 24,\n          backgroundColor: color.extendedPrimary100,\n          borderRadius: 20,\n          flexDirection: \"row\",\n          alignItems: \"center\",\n          gap: 4,\n          borderWidth: 1,\n          borderColor: color.borderDark,\n        },\n        activeCountTextStyle: { ...typography.caption1.regular, color: color.textPrimary },\n      },\n    },\n  };\n};\n\nexport const getReactionsStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: DeepPartial<CometChatTheme[\"messageBubbleReactionStyles\"]>;\n  outgoingBubbleStyle: DeepPartial<CometChatTheme[\"messageBubbleReactionStyles\"]>;\n} => {\n  return deepMerge(getReactionsStyleLight(color, spacing, typography)!,  {\n    incomingBubbleStyle: {\n      emojiStyle: {\n        containerStyle: {\n          borderColor: color.borderLight,\n        },\n      },\n      activeEmojiStyle: {\n        containerStyle: {\n          borderColor: color.borderLight,\n        },\n      },\n      extraReactionStyle: {\n        containerStyle: {\n          borderColor: color.borderLight\n        },\n        activeContainerStyle: {\n          borderColor: color.borderLight\n        },\n      },\n    },\n    outgoingBubbleStyle: {\n      emojiStyle: {\n        containerStyle: {\n          borderColor: color.borderLight\n        },\n      },\n      activeEmojiStyle: {\n        containerStyle: {\n          borderColor: color.borderLight\n        },\n      },\n      extraReactionStyle: {\n        containerStyle: {\n          borderColor: color.borderLight\n        },\n        activeContainerStyle: {\n          borderColor: color.borderLight\n        },\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReceipt/CometChatReceipt.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { useTheme } from \"../../../theme\";\nimport { useCompTheme } from \"../../../theme/hook\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { Icon } from \"../../icons/Icon\";\nimport { ReceiptStyles } from \"./style\";\nimport { MessageReceipt } from \"../../constants/UIKitConstants\";\n\n/**\n * Props for the CometChatReceipt component.\n *\n * CometChatReceipt is a component used to display the status of a message using a custom symbol.\n * It returns an appropriate symbol depending on the message status and can be customized via style.\n *\n */\nexport interface CometChatReceiptInterface {\n  /**\n   * The message receipt object representing the status of the message.\n   */\n  receipt?: MessageReceipt;\n  /**\n   * Custom styles to override or extend the default receipt styles.\n   */\n  style?: Partial<ReceiptStyles>;\n}\n\nexport const CometChatReceipt = (props: CometChatReceiptInterface) => {\n  const theme = useTheme();\n  const compTheme = useCompTheme();\n\n  const { receipt, style = {} } = props;\n\n  const receiptStyles = useMemo(() => {\n    return deepMerge(theme.receiptStyles, compTheme.receiptStyles ?? {}, style);\n  }, [theme.receiptStyles, style, compTheme.receiptStyles]);\n\n  switch (receipt) {\n    case \"SENT\":\n      return (\n        <Icon\n          name='check-fill'\n          icon={receiptStyles?.sentIcon}\n          size={receiptStyles?.sentIconStyle?.width}\n          height={receiptStyles?.sentIconStyle?.height}\n          width={receiptStyles?.sentIconStyle?.width}\n          color={receiptStyles?.sentIconStyle?.tintColor}\n          imageStyle={[receiptStyles?.sentIconStyle]}\n        />\n      );\n    case \"DELIVERED\":\n      return (\n        <Icon\n          name='done-all-fill'\n          icon={receiptStyles?.deliveredIcon}\n          size={receiptStyles?.deliveredIconStyle?.width}\n          height={receiptStyles?.deliveredIconStyle?.height}\n          width={receiptStyles?.deliveredIconStyle?.width}\n          color={receiptStyles?.deliveredIconStyle?.tintColor}\n          imageStyle={[receiptStyles?.deliveredIconStyle]}\n        />\n      );\n    case \"READ\":\n      return (\n        <Icon\n          name='done-all-fill'\n          icon={receiptStyles?.readIcon}\n          size={receiptStyles?.readIconStyle?.width}\n          height={receiptStyles?.readIconStyle?.height}\n          width={receiptStyles?.readIconStyle?.width}\n          color={receiptStyles?.readIconStyle?.tintColor}\n          imageStyle={[receiptStyles?.readIconStyle]}\n        />\n      );\n    case \"ERROR\":\n      return (\n        <Icon\n          name='error-fill'\n          icon={receiptStyles?.errorIcon}\n          size={receiptStyles?.errorIconStyle?.width}\n          height={receiptStyles?.errorIconStyle?.height}\n          width={receiptStyles?.errorIconStyle?.width}\n          color={receiptStyles?.errorIconStyle?.tintColor}\n          imageStyle={[receiptStyles?.errorIconStyle]}\n        />\n      );\n    case \"WAIT\":\n      return (\n        <Icon\n          name='schedule'\n          icon={receiptStyles?.waitIcon}\n          size={receiptStyles?.waitIconStyle?.width}\n          height={receiptStyles?.waitIconStyle?.height}\n          width={receiptStyles?.waitIconStyle?.width}\n          color={receiptStyles?.waitIconStyle?.tintColor}\n          imageStyle={[receiptStyles?.waitIconStyle]}\n        />\n      );\n  }\n  return null;\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReceipt/index.ts",
    "content": "export { CometChatReceipt } from \"./CometChatReceipt\";\n\nexport type { ReceiptStyles } from \"./style\";\n\nexport type { CometChatReceiptInterface } from \"./CometChatReceipt\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReceipt/resources/index.ts",
    "content": "import WAITING from \"./sending.png\";\nimport GREY_TICK from \"./grey-tick-icon.png\";\nimport GREY_DOUBLE_TICK from \"./grey-double-tick-icon.png\";\nimport BLUE_DOUBLE_TICK from \"./blue-double-tick-icon.png\";\nimport ERROR_TICK from \"./error.png\";\n\nexport const ICONS = {\n  WAITING,\n  GREY_TICK,\n  GREY_DOUBLE_TICK,\n  BLUE_DOUBLE_TICK,\n  ERROR_TICK,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReceipt/style.ts",
    "content": "import { ImageSourcePropType, ImageStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { JSX } from \"react\";\n\nexport type ReceiptStyles = {\n  waitIcon?: ImageSourcePropType | JSX.Element;\n  sentIcon?: ImageSourcePropType | JSX.Element;\n  deliveredIcon?: ImageSourcePropType | JSX.Element;\n  readIcon?: ImageSourcePropType | JSX.Element;\n  errorIcon?: ImageSourcePropType | JSX.Element;\n\n  waitIconStyle: ImageStyle;\n  sentIconStyle: ImageStyle;\n  deliveredIconStyle: ImageStyle;\n  readIconStyle: ImageStyle;\n  errorIconStyle: ImageStyle;\n};\n\nexport const getMessageReceiptStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ReceiptStyles => {\n  return {\n    waitIconStyle: {\n      height: spacing.spacing.s4,\n      width: spacing.spacing.s4,\n      tintColor: color.iconSecondary,\n    },\n    sentIconStyle: {\n      height: spacing.spacing.s4,\n      width: spacing.spacing.s4,\n      tintColor: color.iconSecondary,\n    },\n    deliveredIconStyle: {\n      height: spacing.spacing.s4,\n      width: spacing.spacing.s4,\n      tintColor: color.iconSecondary,\n    },\n    readIconStyle: {\n      height: spacing.spacing.s4,\n      width: spacing.spacing.s4,\n      tintColor: color.success,\n    },\n    errorIconStyle: {\n      height: spacing.spacing.s4,\n      width: spacing.spacing.s4,\n      tintColor: color.error,\n    },\n  };\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReportDialog/CometChatReportDialog.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport {\n  Modal,\n  View,\n  Text,\n  TouchableOpacity,\n  StyleSheet,\n  TextInput,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  KeyboardAvoidingView,\n  Platform,\n  ScrollView,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport { useKeyboard } from \"../../helper/useKeyboard\";\n\nexport interface CometChatReportDialogInterface {\n  /** Controls modal visibility */\n  isOpen?: boolean;\n  /** Message object to be flagged */\n  message?: CometChat.BaseMessage;\n  /** Hide remark input field */\n  hideFlagRemarkField?: boolean;\n  /** Callback for cancel */\n  onCancel?: () => void;\n  /** Modal dismiss callback */\n  onDismiss?: () => void;\n  /** Optional custom handler after successful flag (receives SDK payload incl. original message) */\n  onReport?: (payload: {\n    message: CometChat.BaseMessage;\n    reason: CometChat.FlagReason;\n    description: string;\n  }) => void | Promise<void>;\n  /** Override reasons instead of fetching (mainly for testing) */\n  reasons?: CometChat.FlagReason[];\n  /** Text overrides / translations */\n  titleText?: string;\n  messageText?: string;\n  descriptionPlaceholder?: string;\n  cancelButtonText?: string;\n  reportButtonText?: string;\n  /** Styling overrides */\n  containerStyle?: StyleProp<ViewStyle>;\n  titleContainerStyle?: StyleProp<ViewStyle>;\n  titleTextStyle?: StyleProp<TextStyle>;\n  messageTextStyle?: StyleProp<TextStyle>;\n  pillsContainerStyle?: StyleProp<ViewStyle>;\n  pillStyle?: StyleProp<ViewStyle>;\n  pillTextStyle?: StyleProp<TextStyle>;\n  textInputContainerStyle?: StyleProp<ViewStyle>;\n  sectionTitle?: StyleProp<TextStyle>;\n  descriptionTextStyle?: StyleProp<TextStyle>;\n  buttonContainerStyle?: StyleProp<ViewStyle>;\n  cancelButtonStyle?: StyleProp<ViewStyle>;\n  cancelButtonTextStyle?: StyleProp<TextStyle>;\n  reportButtonStyle?: StyleProp<ViewStyle>;\n  reportButtonTextStyle?: StyleProp<TextStyle>;\n  /** Error callback */\n  onError?: (error: CometChat.CometChatException) => void;\n}\n\nexport const CometChatReportDialog = (props: CometChatReportDialogInterface) => {\n  const { t } = useCometChatTranslation();\n  const keyboardHeight = useKeyboard();\n  const {\n    isOpen,\n    message,\n    hideFlagRemarkField = false,\n    onCancel,\n    onDismiss = () => null,\n    onReport,\n    reasons,\n    titleText = t(\"Flag_Message_Title\"),\n    messageText = t(\"Flag_Message_Subtitle\"),\n    descriptionPlaceholder = t(\"Flag_Message_Remark_Placeholder\"),\n    cancelButtonText = t(\"Flag_Message_Confirm_No\"),\n    reportButtonText = t(\"Flag_Message_Confirm_Yes\"),\n    containerStyle = {},\n    titleContainerStyle = {},\n    titleTextStyle = {},\n    messageTextStyle = {},\n    pillsContainerStyle = {},\n    pillStyle = {},\n    pillTextStyle = {},\n    buttonContainerStyle = {},\n    textInputContainerStyle = {},\n    sectionTitle = {},\n    descriptionTextStyle = {},\n    cancelButtonStyle = {},\n    cancelButtonTextStyle = {},\n    reportButtonStyle = {},\n    reportButtonTextStyle = {},\n    onError,\n  } = props;\n\n  const theme = useTheme();\n  const [flagReasons, setFlagReasons] = useState<CometChat.FlagReason[]>(reasons || []);\n  const [isFetchingReasons, setIsFetchingReasons] = useState<boolean>(false);\n  const [selectedReason, setSelectedReason] = useState<CometChat.FlagReason | null>(null);\n  const [description, setDescription] = useState<string>(\"\");\n  const [errorMessage, setErrorMessage] = useState<string>(\"\");\n  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);\n  const [errorTimeoutId, setErrorTimeoutId] = useState<ReturnType<typeof setTimeout> | null>(null);\n  const [isTextInputFocused, setIsTextInputFocused] = useState<boolean>(false);\n\n  const resetStates = () => {\n    setSelectedReason(null);\n    setDescription(\"\");\n    setErrorMessage(\"\");\n    setIsSubmitting(false);\n  };\n\n  useEffect(() => {\n    if (isOpen && !reasons) {\n      (async () => {\n        try {\n          setIsFetchingReasons(true);\n          const list = await CometChat.getFlagReasons();\n          setFlagReasons(list);\n        } catch (e: any) {\n          onError?.(e);\n          showError(t(\"Flag_Error_Text\"));\n        } finally {\n          setIsFetchingReasons(false);\n        }\n      })();\n    } else if (reasons) {\n      setFlagReasons(reasons);\n    }\n    if (!isOpen) {\n      resetStates();\n    }\n  }, [isOpen, reasons, onError, t]);\n\n  const handleCancel = () => {\n    resetStates();\n    onCancel?.();\n  };\n\n  const showError = (message: string) => {\n    // Clear any existing timer\n    if (errorTimeoutId) {\n      clearTimeout(errorTimeoutId);\n    }\n    setErrorMessage(message);\n    const id = setTimeout(() => {\n      setErrorMessage(\"\");\n    }, 3500); // auto clear after 3.5 seconds\n    setErrorTimeoutId(id);\n  };\n\n  const handleReport = async () => {\n    // Validation: missing reason\n    if (!selectedReason) {\n      return;\n    }\n    if (!message) {\n      showError(t(\"Flag_Error_Text\"));\n      return;\n    }\n    setIsSubmitting(true);\n    try {\n      // Trim remark length (max 100 similar to web)\n      const remark = description.trim().substring(0, 100);\n      const messageId = String((message as any).getId?.() ?? \"\");\n      if (!messageId) {\n        throw new Error(\"Invalid message id\");\n      }\n      // SDK call\n      const sdkResult = await CometChat.flagMessage(messageId, {\n        reasonId: selectedReason.id,\n        remark: remark.trim().length > 0 ? remark.trim() : undefined,\n      });\n      console.log(\"[ReportDialog] flagMessage() success:\", sdkResult);\n      // Fire optional external callback\n      const result = onReport?.({ message, reason: selectedReason, description: remark });\n      if (result && typeof (result as Promise<any>).then === \"function\") {\n        await (result as Promise<any>);\n      }\n      // Show success toast then auto-close after 3s\n      setErrorMessage(\"\");\n    } catch (e: any) {\n      onError?.(e);\n      showError(t(\"Flag_Error_Text\"));\n      console.log(\"[ReportDialog] flagMessage() error:\", e);\n    } finally {\n      setIsSubmitting(false);\n    }\n  };\n\n  useEffect(() => {\n    return () => {\n      if (errorTimeoutId) clearTimeout(errorTimeoutId);\n    };\n  }, [errorTimeoutId]);\n\n  return (\n    <Modal\n      animationType='fade'\n      transparent\n      visible={isOpen}\n      onRequestClose={handleCancel}\n      onDismiss={onDismiss}\n    >\n      <KeyboardAvoidingView\n        style={styles.keyboardAvoidingView}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <ScrollView\n          contentContainerStyle={[\n            styles.wrapper,\n            (keyboardHeight > 0 || isTextInputFocused) && styles.wrapperWithKeyboard,\n          ]}\n          keyboardShouldPersistTaps='handled'\n          showsVerticalScrollIndicator={false}\n        >\n          <View style={[theme.reportDialogStyles.containerStyle, containerStyle]}>\n            <View style={[theme.reportDialogStyles.titleContainerStyle, titleContainerStyle]}>\n              <View style={{ flexDirection: \"row\", justifyContent: \"space-between\" }}>\n                <Text style={[theme.reportDialogStyles.titleTextStyle, titleTextStyle]}>\n                  {titleText}\n                </Text>\n                <TouchableOpacity\n                  onPress={(e) => {\n                    e.stopPropagation();\n                    handleCancel();\n                  }}\n                >\n                  <Icon name='close' size={24} color={theme.color.iconPrimary} />\n                </TouchableOpacity>\n              </View>\n              <Text style={[theme.reportDialogStyles.messageTextStyle, messageTextStyle]}>\n                {messageText}\n              </Text>\n            </View>\n\n            <View style={[theme.reportDialogStyles.pillsContainerStyle, pillsContainerStyle]}>\n              {isFetchingReasons && (\n                <Text style={[theme.reportDialogStyles.messageTextStyle, { marginBottom: 8 }]}>\n                  {\"Loading...\"}\n                </Text>\n              )}\n              {!isFetchingReasons &&\n                flagReasons.map((r) => {\n                  const active = r.id === selectedReason?.id;\n                  return (\n                    <TouchableOpacity\n                      key={r.id}\n                      disabled={isSubmitting}\n                      onPress={(e) => {\n                        e.stopPropagation();\n                        // Toggle selection: if already selected, deselect\n                        setSelectedReason((prev) => (prev?.id === r.id ? null : r));\n                        setErrorMessage(\"\");\n                      }}\n                      style={[\n                        theme.reportDialogStyles.pillsStyle,\n                        {\n                          backgroundColor: active\n                            ? theme.color.extendedPrimary100\n                            : theme.color.background1,\n                          borderColor: active\n                            ? theme.color.extendedPrimary200\n                            : theme.color.borderDefault,\n                          opacity: isSubmitting ? 0.6 : 1,\n                        },\n                        pillStyle,\n                      ]}\n                    >\n                      <Text\n                        numberOfLines={2}\n                        style={[\n                          theme.reportDialogStyles.pillsTextStyle,\n                          pillTextStyle,\n                          {\n                            color: active ? theme.color.textHighlight : theme.color.textPrimary,\n                          },\n                        ]}\n                      >\n                        {r.name}\n                      </Text>\n                    </TouchableOpacity>\n                  );\n                })}\n            </View>\n\n            {!hideFlagRemarkField && (\n              <View\n                style={[theme.reportDialogStyles.textInputContainerStyle, textInputContainerStyle]}\n              >\n                <Text style={[theme.reportDialogStyles.sectionTitle, sectionTitle]}>\n                  {t(\"Flag_Message_Remark_Label\")}{\" \"}\n                  <Text style={{ color: theme.color.textTertiary }}>\n                    ({t(\"Flag_Message_Remark_Optional\")})\n                  </Text>\n                </Text>\n                <View>\n                  <TextInput\n                    placeholder={descriptionPlaceholder}\n                    placeholderTextColor={theme.color.textTertiary}\n                    multiline\n                    value={description}\n                    onFocus={() => setIsTextInputFocused(true)}\n                    onBlur={() => setIsTextInputFocused(false)}\n                    onChangeText={(val) => setDescription(val.substring(0, 100))}\n                    editable={!isSubmitting}\n                    style={[theme.reportDialogStyles.descriptionTextStyle, descriptionTextStyle]}\n                  />\n                </View>\n              </View>\n            )}\n\n            {!!errorMessage && (\n              <Text style={{ color: theme.color.error, marginBottom: 16 }}>{errorMessage}</Text>\n            )}\n\n            <View style={[theme.reportDialogStyles.buttonContainer, buttonContainerStyle]}>\n              <TouchableOpacity\n                style={[theme.reportDialogStyles.cancelButtonStyle, cancelButtonStyle]}\n                disabled={isSubmitting}\n                onPress={(e) => {\n                  e.stopPropagation();\n                  handleCancel();\n                }}\n              >\n                <Text\n                  numberOfLines={1}\n                  style={[theme.reportDialogStyles.cancelButtonTextStyle, cancelButtonTextStyle]}\n                >\n                  {cancelButtonText}\n                </Text>\n              </TouchableOpacity>\n              <TouchableOpacity\n                style={[\n                  theme.reportDialogStyles.confirmButtonStyle,\n                  {\n                    backgroundColor:\n                      !selectedReason || isSubmitting\n                        ? theme.color.background4\n                        : theme.color.primary,\n                    opacity: isSubmitting ? 0.7 : 1,\n                  },\n                  reportButtonStyle,\n                ]}\n                // Allow press even if no reason to surface error\n                disabled={!selectedReason || isSubmitting}\n                onPress={(e) => {\n                  e.stopPropagation();\n                  handleReport();\n                }}\n              >\n                <Text\n                  numberOfLines={1}\n                  style={[theme.reportDialogStyles.confirmButtonTextStyle, reportButtonTextStyle]}\n                >\n                  {reportButtonText}\n                </Text>\n              </TouchableOpacity>\n            </View>\n          </View>\n        </ScrollView>\n      </KeyboardAvoidingView>\n    </Modal>\n  );\n};\n\nconst styles = StyleSheet.create({\n  keyboardAvoidingView: {\n    flex: 1,\n  },\n  wrapper: {\n    flexGrow: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    backgroundColor: \"#141414CC\",\n    paddingHorizontal: 20,\n    minHeight: \"100%\",\n  },\n  wrapperWithKeyboard: {\n    justifyContent: \"flex-start\",\n    paddingTop: Platform.OS === \"ios\" ? 50 : 20,\n  },\n});\n\nexport default CometChatReportDialog;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReportDialog/index.ts",
    "content": "import { CometChatReportDialog, CometChatReportDialogInterface } from \"./CometChatReportDialog\";\nexport { CometChatReportDialog };\nexport type { CometChatReportDialogInterface };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatReportDialog/style.ts",
    "content": "import { StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\n\nexport type ReportDialogStyle = {\n  containerStyle: ViewStyle;\n  titleContainerStyle: ViewStyle;\n  titleTextStyle: TextStyle;\n  messageTextStyle: TextStyle;\n  pillsContainerStyle: ViewStyle;\n  pillsStyle: ViewStyle;\n  pillsTextStyle: TextStyle;\n  textInputContainerStyle: ViewStyle;\n  sectionTitle: TextStyle;\n  descriptionTextStyle: TextStyle;\n  buttonContainer: ViewStyle;\n  cancelButtonStyle: ViewStyle;\n  cancelButtonTextStyle: TextStyle;\n  confirmButtonStyle: ViewStyle;\n  confirmButtonTextStyle: TextStyle;\n};\n\nexport const getReportDialogStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ReportDialogStyle => {\n  return StyleSheet.create({\n    containerStyle: {\n      backgroundColor: color.background1,\n      width: \"100%\",\n      padding: 24,\n      maxWidth: 500,\n      borderRadius: 16,\n      borderColor: color.borderDark,\n      shadowColor: \"#000\",\n      shadowOpacity: 0.12,\n      shadowRadius: 16,\n      shadowOffset: { width: 0, height: 6 },\n      elevation: 6,\n    },\n    titleContainerStyle: {\n      borderBottomColor: color.borderLight,\n      borderBottomWidth: 1,\n      paddingBottom: 16,\n      marginBottom: 16,\n      gap: 8,\n    },\n    titleTextStyle: {\n      color: color.textPrimary,\n      ...typography.heading2.bold,\n    },\n    messageTextStyle: {\n      color: color.textSecondary,\n      ...typography.button.regular,\n    },\n    pillsContainerStyle: {\n      width: \"100%\",\n      marginBottom: 16,\n      flexDirection: \"row\",\n      flexWrap: \"wrap\",\n    },\n    pillsStyle: {\n      borderWidth: 1,\n      borderRadius: 24,\n      borderColor: color.borderDefault,\n      paddingVertical: 10,\n      paddingHorizontal: 16,\n      marginBottom: 8,\n      marginRight: 8,\n    },\n    pillsTextStyle: {\n      color: color.textPrimary,\n      ...typography.button.regular,\n    },\n    textInputContainerStyle: {\n      width: \"100%\",\n      marginBottom: 24,\n    },\n    sectionTitle: {\n      ...typography.body.medium,\n      color: color.textPrimary,\n      marginBottom: 8,\n    },\n    descriptionTextStyle: {\n      borderWidth: 1,\n      borderRadius: 12,\n      minHeight: 100,\n      padding: 12,\n      textAlignVertical: \"top\",\n      maxHeight: 100,\n      borderColor: color.borderLight,\n      color: color.textPrimary,\n      ...typography.body.regular,\n    },\n    buttonContainer: {\n      flexDirection: \"row\",\n      width: \"100%\",\n      gap: 12,\n    },\n    cancelButtonStyle: {\n      flex: 1,\n      borderRadius: spacing.radius.r2,\n      height: 40,\n      backgroundColor: color.background1,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      borderWidth: 1,\n      borderColor: color.borderDark,\n    },\n    cancelButtonTextStyle: {\n      color: color.textPrimary,\n      ...typography.button.medium,\n      paddingHorizontal: 6,\n      textAlign: \"center\",\n    },\n    confirmButtonStyle: {\n      flex: 1,\n      backgroundColor: color.error,\n      borderRadius: spacing.radius.r2,\n      height: 40,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    confirmButtonTextStyle: {\n      color: color.primaryButtonIcon,\n      paddingHorizontal: 6,\n      textAlign: \"center\",\n      ...typography.button.medium,\n    },\n  });\n};\n\nexport const getReportDialogStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ReportDialogStyle => {\n  return <ReportDialogStyle>(\n    deepMerge(getReportDialogStyleLight(color, spacing, typography), {\n      containerStyle: {\n        backgroundColor: color.background2,\n      },\n      titleTextStyle: {\n        color: color.textPrimary,\n      },\n      messageTextStyle: {\n        color: color.textSecondary,\n      },\n      cancelButtonStyle: {\n        borderColor: color.borderDark,\n      },\n      cancelButtonTextStyle: {\n        color: color.textPrimary,\n      },\n      confirmButtonStyle: {\n        backgroundColor: color.error,\n      },\n      confirmButtonTextStyle: {\n        color: color.primaryButtonIcon,\n      },\n    })\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatRetryButton/CometChatRetryButton.tsx",
    "content": "import React from \"react\";\nimport { Text, TouchableOpacity } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\n/**\n * Props for the CometChatRetryButton component.\n */\nexport interface CometChatRetryButtonProps {\n  /**\n   * Callback function to execute when the retry button is pressed.\n   */\n  onPress: () => void;\n}\n\n/**\n * CometChatRetryButton is a reusable component that renders a styled retry button.\n * It is commonly used in error states to allow users to retry failed operations.\n *\n * @param props - The props for the component.\n * @returns The rendered retry button element.\n */\nexport const CometChatRetryButton = (props: CometChatRetryButtonProps) => {\n  const { onPress } = props;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const retryText = t(\"RETRY\");\n\n  return (\n    <TouchableOpacity\n      onPress={onPress}\n      accessibilityRole='button'\n      accessibilityLabel={retryText}\n      style={{\n        backgroundColor: theme.color.primary,\n        paddingVertical: theme.spacing.spacing.s3,\n        paddingHorizontal: theme.spacing.spacing.s10,\n        borderRadius: theme.spacing.radius.r2,\n        marginTop: theme.spacing.spacing.s5,\n      }}\n    >\n      <Text\n        style={{\n          color: theme.color.primaryButtonIcon,\n          ...theme.typography.button.medium,\n        }}\n      >\n        {retryText}\n      </Text>\n    </TouchableOpacity>\n  );\n};\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatRetryButton/index.ts",
    "content": "export { CometChatRetryButton } from \"./CometChatRetryButton\";\nexport type { CometChatRetryButtonProps } from \"./CometChatRetryButton\";\n\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSendButtonView/CometChatSendButtonView.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { TouchableOpacity, ViewStyle } from \"react-native\";\nimport { stopStreamingForRunId } from \"../../services/stream-message.service\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../../shared/icons/Icon\";\n\ntype CometChatSendButtonViewProps = {\n  isButtonDisabled: boolean;\n  composerRef: any;\n  isStreaming: boolean;\n  showStopButton: boolean;\n  setShowStopButton: (show: boolean) => void;\n};\n\nconst CometChatSendButtonView = ({\n  isButtonDisabled,\n  composerRef,\n  isStreaming,\n  showStopButton,\n  setShowStopButton,\n}: CometChatSendButtonViewProps) => {\n  const theme = useTheme();\n\n  const handlePress = () => {\n    if (isButtonDisabled) {\n      return;\n    }\n    \n    if (isStreaming || showStopButton) {\n      setShowStopButton(false);\n      stopStreamingForRunId();\n    } else {\n      setShowStopButton(true);\n      setTimeout(() => {\n        composerRef.current?.sendTextMessage?.();\n      }, 0);\n    }\n  };\n\n  const shouldShowStop = isStreaming || showStopButton;\n  const isActive = shouldShowStop || !isButtonDisabled;\n\n  const buttonStyle = useMemo<ViewStyle>(() => {\n    const { spacing } = theme;\n    const backgroundColor =\n      theme.mode === \"light\"\n        ? isActive\n          ? theme.color.secondaryButtonBackground\n          : theme.color.background4\n        : isActive\n        ? theme.color.staticWhite\n        : theme.color.background4;\n\n    return {\n      display: \"flex\",\n      width: spacing.spacing.s8,\n      height: spacing.spacing.s8,\n      padding: spacing.padding.p1,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      borderRadius: spacing.spacing.s15,\n      backgroundColor,\n    } as ViewStyle;\n  }, [theme, isActive]);\n\n  const iconColor =\n    theme.mode === \"light\"\n      ? theme.color.staticWhite\n      : isActive\n      ? theme.color.staticBlack\n      : theme.color.staticBlack;\n\n  return (\n    <TouchableOpacity\n      onPress={handlePress}\n      disabled={isButtonDisabled && shouldShowStop}\n      style={buttonStyle}\n    >\n      <Icon\n        name={shouldShowStop ? \"stop-fill\" : \"ai-send-button\"}\n        width={theme.spacing.spacing.s5}\n        height={theme.spacing.spacing.s5}\n        color={iconColor}\n      />\n    </TouchableOpacity>\n  );\n};\n\nexport { CometChatSendButtonView };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSendButtonView/index.ts",
    "content": "export { CometChatSendButtonView } from \"./CometChatSendButtonView\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatStatusIndicator/CometChatStatusIndicator.tsx",
    "content": "import { CometChat } from \"@cometchat/chat-sdk-react-native\";\nimport React, { useMemo } from \"react\";\nimport { Image, View } from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { useCompTheme } from \"../../../theme/hook\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { StatusIndicatorStyles } from \"./styles\";\n\n/**\n * Props for the CometChatStatusIndicator component.\n */\nexport interface CometChatStatusIndicatorInterface {\n  /**\n   * The status type to be displayed. Can be one of:\n   * \"online\", \"offline\", \"private\", \"protected\", or \"public\".\n   */\n  type?: \"online\" | \"offline\" | \"private\" | \"protected\" | \"public\" |  \"password\" | null;\n  /**\n   * Custom styles for the status indicator.\n   */\n  style?: Partial<StatusIndicatorStyles>;\n}\n\n/**\n * CometChatStatusIndicator is a component used for indicating the status of a user or group.\n * It displays the online/offline or custom status of the user/group using a predefined symbol or style.\n *\n * @author CometChat\n */\n\nexport const CometChatStatusIndicator = ({\n  type = \"offline\",\n  style,\n}: CometChatStatusIndicatorInterface) => {\n  const theme = useTheme();\n  const compTheme = useCompTheme();\n  const statusIndicatorStyles = useMemo(() => {\n    return deepMerge(theme.statusIndicatorStyle, compTheme.statusIndicatorStyle ?? {}, style ?? {});\n  }, [theme, compTheme, style]);\n\n  if (type === CometChat.USER_STATUS.ONLINE) {\n    return (\n      <View\n        style={[ statusIndicatorStyles.containerStyleOnline]}\n      />\n    );\n  }\n  if (type === CometChat.GROUP_TYPE.PRIVATE) {\n    return (\n      <View\n        style={[ statusIndicatorStyles.containerStylePrivate]}\n      >\n        <Image\n          style={[statusIndicatorStyles.imageStylePrivate]}\n          source={require(\"./private.png\")}\n        />\n      </View>\n    );\n  }\n  if (type === CometChat.GROUP_TYPE.PROTECTED) {\n    return (\n      <View\n        style={[\n          statusIndicatorStyles.containerStyleProtected,\n        ]}\n      >\n        <Image\n          style={[ statusIndicatorStyles.imageStyleProtected]}\n          source={require(\"./protected.png\")}\n        />\n      </View>\n    );\n  }\n  return null;\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatStatusIndicator/index.ts",
    "content": "export { CometChatStatusIndicator } from \"./CometChatStatusIndicator\";\n\nexport type { CometChatStatusIndicatorInterface } from \"./CometChatStatusIndicator\";\n\nexport {getStatusIndicatorStyles } from \"./styles\";\n\nexport type {StatusIndicatorStyles} from \"./styles\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatStatusIndicator/styles.ts",
    "content": "import { ImageStyle, StyleSheet, ViewStyle } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport type StatusIndicatorStyles = {\n  containerStyleOnline: ViewStyle;\n  containerStylePrivate: ViewStyle;\n  containerStyleProtected: ViewStyle;\n  imageStylePrivate: ImageStyle;\n  imageStyleProtected: ImageStyle;\n};\n\nexport const getStatusIndicatorStyles = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): StatusIndicatorStyles => {\n  return StyleSheet.create({\n    containerStyleOnline: {\n      position: \"absolute\",\n      end: spacing.spacing.s0,\n      bottom: spacing.spacing.s0,\n      height: spacing.spacing.s3 + spacing.spacing.s0_5,\n      width: spacing.spacing.s3 + spacing.spacing.s0_5,\n      borderRadius: spacing.radius.max,\n      borderWidth: spacing.spacing.s0_5,\n      borderColor: color.background1,\n      backgroundColor: color.success,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    containerStylePrivate: {\n      position: \"absolute\",\n      end: spacing.spacing.s0,\n      bottom: spacing.spacing.s0,\n      height: spacing.spacing.s3 + spacing.spacing.s0_5,\n      width: spacing.spacing.s3 + spacing.spacing.s0_5,\n      borderRadius: spacing.radius.max,\n      borderWidth: spacing.spacing.s0_5,\n      borderColor: color.background1,\n      backgroundColor: color.warning,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n    },\n    containerStyleProtected: {\n      position: \"absolute\",\n      end: spacing.spacing.s0,\n      bottom: spacing.spacing.s0,\n      height: spacing.spacing.s3 + spacing.spacing.s0_5,\n      width: spacing.spacing.s3 + spacing.spacing.s0_5,\n      borderRadius: spacing.radius.max,\n      borderWidth: spacing.spacing.s0_5,\n      borderColor: color.background1,\n      justifyContent: \"center\",\n      alignItems: \"center\",\n      backgroundColor: color.success,\n    },\n    imageStylePrivate: { width: spacing.spacing.s2, height: spacing.spacing.s2 },\n    imageStyleProtected: { width: spacing.spacing.s2, height: spacing.spacing.s2 },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatStreamMessageBubble/CometChatStreamMessageBubble.tsx",
    "content": "import React, { useState, useEffect, useRef, useCallback, memo, useMemo } from 'react';\nimport { View, Text, StyleSheet, Animated, TouchableOpacity } from 'react-native';\nimport Markdown from 'react-native-markdown-display';\nimport { CometChat } from '@cometchat/chat-sdk-react-native';\nimport { useTheme } from '../../../theme';\nimport { messageStream, streamingState$, IStreamData, getAIAssistantTools, stopStreamingForRunId, handleWebsocketMessage, startStreamingForRunId, streamConnection$, notifyStreamRenderComplete } from '../../services/stream-message.service';\nimport { CometChatUiKitConstants } from '../../index';\n\nexport interface CometChatStreamMessageBubbleProps {\n  message: any;\n  theme?: any;\n  style?: any;\n}\n\nconst CometChatStreamMessageBubble: React.FC<CometChatStreamMessageBubbleProps> = memo(({ \n  message, \n  style: bubbleStyle\n}) => {\n  const initialMessageRef = useRef<any>(message);\n  const [data, setData] = useState<any>(initialMessageRef.current || null);\n  const [fullMessage, setFullMessage] = useState<string>('');\n  const [executionText, setExecutionText] = useState<string>('');\n  const [isStreaming, setIsStreaming] = useState(false);\n  const [hasError, setHasError] = useState(false);\n  const [finished, setFinished] = useState(false);\n  const [contentStreamStarted, setContentStreamStarted] = useState(false);\n  const [runStarted, setRunStarted] = useState(false);\n  const [showingExecutionText, setShowingExecutionText] = useState(false);\n  const shimmerAnimation = useRef(new Animated.Value(-1)).current;\n  const theme = useTheme();\n  const [connectionStatus, setConnectionStatus] = useState<'connected' | 'disconnected' | 'error'>('connected');\n  const streamListenerId = useRef(`stream_bubble_${Date.now()}`);\n  const hasInitializedStreaming = useRef(false);\n  const [renderComplete, setRenderComplete] = useState(false);\n\n  const toolCallNameRef = useRef<string>('');\n  const toolCallDataRef = useRef<object>({});\n\n  // Tool events mapping\n  const toolEventsMap = [\n    CometChatUiKitConstants.streamMessageTypes.tool_call_args,\n    CometChatUiKitConstants.streamMessageTypes.tool_call_end,\n    CometChatUiKitConstants.streamMessageTypes.tool_call_result,\n    CometChatUiKitConstants.streamMessageTypes.tool_call_start\n  ];\n  useEffect(() => {\n  if (finished && fullMessage && contentStreamStarted) {\n    setRenderComplete(true);\n    const runId = (initialMessageRef.current as any)?.getId?.();\n    if (runId) {\n      notifyStreamRenderComplete(runId);\n    }\n  }\n}, [finished, fullMessage, contentStreamStarted]);\n\n  useEffect(() => {\n    const isStreamMessage = (initialMessageRef.current as any)?.isStreamMessage;\n    const runId = (initialMessageRef.current as any)?.getId?.();\n    const metadata = (initialMessageRef.current as any)?.getMetadata?.();\n    const startRunning = metadata?.start_running;\n\n    if (isStreamMessage && runId && startRunning && !hasInitializedStreaming.current) {\n      hasInitializedStreaming.current = true;\n      startStreamingForRunId(runId, (aiEvent: CometChat.AIAssistantBaseEvent) => {\n        if (aiEvent.getMessageId && aiEvent.getMessageId() === runId) {\n          if (aiEvent.getType() === CometChatUiKitConstants.streamMessageTypes.run_started) {\n            setRunStarted(true);\n          }\n        }\n      });\n    }\n\n    return () => {\n      if (hasInitializedStreaming.current) {\n        stopStreamingForRunId();\n      }\n    };\n  }, []);\n\n\n  useEffect(() => {\n    const streamState = streamingState$.subscribe({\n      next: setIsStreaming,\n      error: () => {\n        stopStreamingForRunId();\n        setHasError(true);\n      },\n    });\n\n    const subscription = messageStream.subscribe({\n      next: (streamData: IStreamData) => {\n        const targetMessageId = (initialMessageRef.current as any)?.getId?.();\n        const currentId = String(streamData.message?.getMessageId?.());\n\n        if (targetMessageId && String(currentId) !== String(targetMessageId)) {\n          return;\n        }\n\n        // Handle tool call events\n        const eventType = streamData.message.getType();\n\n        // Handle tool call events\n        if (toolEventsMap.includes(eventType)) {\n          if (eventType === CometChatUiKitConstants.streamMessageTypes.tool_call_start) {\n            toolCallNameRef.current = (streamData.message as any).getToolCallName();\n            const execText = streamData.message.getData()?.executionText;\n            \n            if (execText) {\n              setExecutionText(execText);\n              setShowingExecutionText(true);\n              setContentStreamStarted(false);\n            }\n          }\n          if (eventType === CometChatUiKitConstants.streamMessageTypes.tool_call_args) {\n            toolCallDataRef.current = JSON.parse((streamData.message as any).getDelta());\n          }\n          if (eventType === CometChatUiKitConstants.streamMessageTypes.tool_call_end) {\n            const assistantTools = getAIAssistantTools();\n            const toolCallName = toolCallNameRef.current;\n\n            if (toolCallName && assistantTools) {\n              const handler = assistantTools.getAction(toolCallName);\n              handler?.(toolCallDataRef.current);\n            }\n            setShowingExecutionText(false);\n            setExecutionText('');\n          }\n        }\n\n        setData(streamData.message);\n\n        if (streamData.streamedMessages !== undefined) {\n          const newMessage = streamData.streamedMessages ?? '';\n          if (!contentStreamStarted && streamData.message?.getType() === CometChatUiKitConstants.streamMessageTypes.text_message_content) {\n            setContentStreamStarted(true);\n            setShowingExecutionText(false);\n          }\n          setFullMessage(() => newMessage);\n        }\n\n        if (streamData.message?.getType() === CometChatUiKitConstants.streamMessageTypes.run_finished) {\n          setFinished(true);\n          setContentStreamStarted(true);\n          setShowingExecutionText(false);\n          subscription.unsubscribe();\n          streamState.unsubscribe();\n          if (hasInitializedStreaming.current) {\n            CometChat.removeAIAssistantListener(streamListenerId.current);\n            hasInitializedStreaming.current = false;\n          }\n        }\n      },\n      error: () => {\n        stopStreamingForRunId();\n        setHasError(true);\n      },\n    });\n\n    return () => {\n      subscription.unsubscribe();\n      streamState.unsubscribe();\n      shimmerAnimation.stopAnimation();\n    };\n  }, []);\n\n  useEffect(() => {\n    const sub = streamConnection$.subscribe(({ status, error }) => {\n      setConnectionStatus(status);\n      if (status === 'disconnected' || status === 'error') {\n        setHasError(true);\n        stopStreamingForRunId();\n      }\n    });\n    return () => sub.unsubscribe();\n  }, []);\n\n  // Create markdown styles based on theme\n  const markdownStyles = useMemo(() => ({\n    body: {\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      ...bubbleStyle?.textStyle,\n      margin: 0,\n      padding: 0,\n      textAlignVertical: 'top',\n    },\n    text: {\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      ...(bubbleStyle?.textStyle && Object.fromEntries(\n        Object.entries(bubbleStyle.textStyle).filter(([key]) => key !== 'color')\n      )),\n    },\n    paragraph: {\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      margin: 0,\n      padding: 0,\n    },\n    code_inline: {\n      backgroundColor: theme.color.background3,\n      color: theme.color.textHighlight,\n      borderRadius: theme.spacing.radius.r1,\n      padding: theme.spacing.padding.p0_5,\n    },\n    code_block: {\n      backgroundColor: theme.color.background3,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      borderRadius: theme.spacing.radius.r2,\n      padding: theme.spacing.padding.p2,\n      fontFamily: 'monospace',\n    },\n    fence: {\n      backgroundColor: theme.color.background3,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      borderRadius: theme.spacing.radius.r2,\n      borderWidth: 1,\n      borderColor: theme.color.borderDefault,\n      padding: theme.spacing.padding.p2,\n      marginVertical: theme.spacing.margin.m4,\n      fontFamily: 'monospace',\n    },\n    table: {\n      borderWidth: 1,\n      borderColor: theme.color.borderDefault,\n      borderRadius: theme.spacing.radius.r2,\n      backgroundColor: theme.color.background2,\n      marginVertical: theme.spacing.margin.m2,\n      overflow: 'hidden' as 'hidden',\n      borderCollapse: 'separate' as 'separate',\n      borderSpacing: 0,\n    },\n    thead: {\n      backgroundColor: theme.color.background3,\n      margin: 0,\n      padding: 0,\n    },\n    tbody: {\n      backgroundColor: theme.color.background1,\n      margin: 0,\n      padding: 0,\n    },\n    th: {\n      borderRightWidth: 1,\n      borderBottomWidth: 1,\n      borderColor: theme.color.borderDefault,\n      backgroundColor: theme.color.background3,\n      padding: theme.spacing.padding.p2,\n      fontWeight: 'bold',\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      margin: 0,\n      borderTopWidth: 0,\n      borderLeftWidth: 0,\n    },\n    td: {\n      borderRightWidth: 1,\n      borderBottomWidth: 1,\n      borderColor: theme.color.borderDefault,\n      backgroundColor: theme.color.background1,\n      padding: theme.spacing.padding.p2,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      margin: 0,\n      borderTopWidth: 0,\n      borderLeftWidth: 0,\n    },\n    tr: {\n      borderBottomWidth: 0,\n      margin: 0,\n      padding: 0,\n    },\n    heading1: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.6,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m2,\n    },\n    heading2: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.4,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m2,\n    },\n    heading3: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.2,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    heading4: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 1.1,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    heading5: {\n      fontWeight: '700' as '700',\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    heading6: {\n      fontWeight: '700' as '700',\n      fontSize: (bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize) * 0.9,\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      marginVertical: theme.spacing.margin.m1,\n    },\n    strong: {\n      fontWeight: '700' as '700',\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n    },\n    em: {\n      fontStyle: 'italic' as 'italic',\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n    },\n    blockquote: {\n      borderLeftWidth: 3,\n      borderLeftColor: theme.color.borderDefault,\n      paddingLeft: theme.spacing.padding.p2,\n      color: theme.color.textSecondary,\n    },\n    link: {\n      underlineColor: theme.color.textHighlight,\n      color: theme.color.textHighlight,\n      borderRadius: theme.spacing.radius.r1,\n      paddingHorizontal: theme.spacing.padding.p0_5,\n      paddingVertical: theme.spacing.padding.p0_5,\n      textDecorationLine: 'underline' as 'underline',\n    },\n  }), [bubbleStyle?.textStyle, theme]);\n\n  const MemoMarkdown = useMemo(() => {\n    return fullMessage ? (\n      <Markdown style={markdownStyles} mergeStyle={true}>\n        {fullMessage.trim()}\n      </Markdown>\n    ) : null;\n  }, [fullMessage, markdownStyles]);\n\n  const shouldShowThinking = !contentStreamStarted && !runStarted && !showingExecutionText;\n  const shouldShowExecutionText = showingExecutionText && executionText && !contentStreamStarted;\n\n  // Simple shimmer component - single text with opacity and color animation\n  const ShimmerText = useCallback(({ children, style }: { children: string, style: any }) => {\n    return (\n      <Animated.Text\n        style={[\n          style,\n          {\n            opacity: shimmerAnimation.interpolate({\n              inputRange: [-1, -0.5, 0, 0.5, 1],\n              outputRange: [0.4, 0.7, 1, 0.7, 0.4],\n            }),\n            color: shimmerAnimation.interpolate({\n              inputRange: [-1, -0.5, 0, 0.5, 1],\n              outputRange: [\n                style.color || theme.color.textTertiary,\n                style.color || theme.color.textTertiary,\n                style.color || theme.color.textSecondary,\n                style.color || theme.color.textPrimary,\n                style.color || theme.color.receiveBubbleText,\n              ],\n            }),\n          },\n        ]}\n      >\n        {children}\n      </Animated.Text>\n    );\n  }, [shimmerAnimation, theme]);\n\n  // Use the bubbleStyle passed from parent, with theme fallbacks\n  const styles = StyleSheet.create({\n    container: {\n      display: 'flex',\n      paddingVertical: bubbleStyle?.containerStyle?.paddingVertical || 0,\n      paddingHorizontal: bubbleStyle?.containerStyle?.paddingHorizontal || 0,\n      alignItems: 'flex-start',\n      justifyContent: 'flex-start',\n      alignSelf: 'flex-start',\n      backgroundColor: bubbleStyle?.containerStyle?.backgroundColor || 'transparent',\n      borderRadius: bubbleStyle?.containerStyle?.borderRadius || theme.spacing.radius.r3,\n      maxWidth: '100%',\n      ...bubbleStyle?.containerStyle,\n    },\n    textContainer: {\n      ...bubbleStyle?.textContainerStyle,\n    },\n    text: {\n      color: bubbleStyle?.textStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.textStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.textStyle?.fontSize || theme.typography.body.regular.fontSize,\n      ...bubbleStyle?.textStyle,\n    },\n    placeholderText: {\n      color: bubbleStyle?.placeholderTextStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.placeholderTextStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.placeholderTextStyle?.fontSize || theme.typography.body.regular.fontSize,\n      opacity: bubbleStyle?.placeholderTextStyle?.opacity || 0.6,\n      ...bubbleStyle?.placeholderTextStyle,\n      marginTop: 10\n    },\n    executionText: {\n      color: bubbleStyle?.placeholderTextStyle?.color || theme.color.receiveBubbleText,\n      fontFamily: bubbleStyle?.placeholderTextStyle?.fontFamily || theme.typography.body.regular.fontFamily,\n      fontSize: bubbleStyle?.placeholderTextStyle?.fontSize || theme.typography.body.regular.fontSize,\n      opacity: bubbleStyle?.placeholderTextStyle?.opacity || 0.8,\n      ...bubbleStyle?.placeholderTextStyle,\n      marginTop: 10,\n      fontStyle: 'italic',\n    },\n    errorContainer: {\n      borderLeftWidth: 3,\n      borderLeftColor: theme.color.error,\n      backgroundColor: bubbleStyle?.errorContainerStyle?.backgroundColor || theme.color.error,\n      padding: bubbleStyle?.errorContainerStyle?.padding || theme.spacing.padding.p2,\n      borderRadius: bubbleStyle?.errorContainerStyle?.borderRadius || theme.spacing.radius.r2,\n      marginTop: bubbleStyle?.errorContainerStyle?.marginTop || theme.spacing.margin.m1,\n      ...bubbleStyle?.errorContainerStyle,\n    },\n    errorText: {\n      color: bubbleStyle?.errorTextStyle?.color || theme.color.background1,\n      fontFamily: bubbleStyle?.errorTextStyle?.fontFamily || theme.typography.caption1.regular.fontFamily,\n      fontSize: bubbleStyle?.errorTextStyle?.fontSize || theme.typography.caption1.regular.fontSize,\n      ...bubbleStyle?.errorTextStyle,\n    },\n  });\n\n  useEffect(() => {\n    if ((shouldShowThinking || shouldShowExecutionText) && !fullMessage.trim() && !hasError) {\n      Animated.loop(\n        Animated.timing(shimmerAnimation, {\n          toValue: 1,\n          duration: 1200,\n          useNativeDriver: false,\n        })\n      ).start();\n    } else {\n      shimmerAnimation.stopAnimation();\n      shimmerAnimation.setValue(-1);\n    }\n  }, [shouldShowThinking, shouldShowExecutionText, fullMessage, hasError]);\n\n\n  return (\n    <View style={styles.container}>\n      {shouldShowThinking && (\n        <ShimmerText\n          style={[\n            styles.placeholderText,\n            {\n              fontStyle: theme.assistantBubbleStyles?.placeholderTextStyle?.fontStyle || theme.typography.body.regular.fontStyle,\n            },\n          ]}\n        >\n          Thinking...\n        </ShimmerText>\n      )}\n\n      {shouldShowExecutionText && (\n        <ShimmerText\n          style={[\n            styles.executionText,\n            {\n              fontStyle: theme.assistantBubbleStyles?.placeholderTextStyle?.fontStyle || theme.typography.body.regular.fontStyle,\n            },\n          ]}\n        >\n          {executionText}\n        </ShimmerText>\n      )}\n\n      {fullMessage && contentStreamStarted && MemoMarkdown}\n\n      {hasError && (\n        <View style={styles.errorContainer}>\n          <Text style={styles.errorText}>No internet connection. Please check your connection and try again.</Text>\n        </View>\n      )}\n    </View>\n  );\n});\n\nexport default CometChatStreamMessageBubble;"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatStreamMessageBubble/index.ts",
    "content": "import CometChatStreamMessageBubble, { CometChatStreamMessageBubbleProps } from \"./CometChatStreamMessageBubble\";\n\nexport {\n  CometChatStreamMessageBubble,\n};\n\nexport type {\n  CometChatStreamMessageBubbleProps,\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSuggestionList/CometChatSuggestionList.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { ActivityIndicator, FlatList, ListRenderItemInfo, View, Text } from \"react-native\";\nimport { CometChatListItem } from \"../CometChatListItem\";\nimport { SuggestionItem } from \"./SuggestionItem\";\nimport { SuggestionListStyle } from \"../../../theme/type\";\nimport { Skeleton } from \"./Skeleton\";\nimport { useTheme } from \"../../../theme\";\nimport { useCometChatTranslation } from \"../..\";\n\n/**\n * Props for the CometChatSuggestionList component.\n */\nexport interface CometChatSuggestionListInterface {\n  /**\n   * Color for the separator between suggestion items.\n   */\n  separatorColor?: string;\n  /**\n   * Array of suggestion items to be displayed.\n   */\n  data: Array<SuggestionItem>;\n  /**\n   * Custom styles for the suggestion list.\n   */\n  listStyle: SuggestionListStyle;\n  /**\n   * Callback function invoked when a suggestion item is pressed.\n   *\n   * @param item - The suggestion item that was pressed.\n   */\n  onPress: (item: SuggestionItem) => void;\n  /**\n   * Optional callback function invoked when the end of the list is reached.\n   */\n  onEndReached?: () => void;\n  /**\n   * Indicates whether the suggestion list is currently loading.\n   */\n  loading?: boolean;\n}\n\nexport const CometChatSuggestionList = (props: CometChatSuggestionListInterface) => {\n  const { data, listStyle, onPress, onEndReached, loading } = props;\n  const theme = useTheme();\n  const { t } = useCometChatTranslation();\n\n  const [initialLoad, setInitialLoad] = useState(true);\n\n  useEffect(() => {\n    setTimeout(() => {\n      setInitialLoad(false);\n    }, 800);\n  }, []);\n\n  const _render = ({ item, index }: ListRenderItemInfo<SuggestionItem>) => {\n    let shouldLoadAvatarName = item.hideLeadingIcon ? {} : { avatarName: item.name };\n    const isAllAlias = item.underlyingText?.startsWith?.('<@all:');\n    const TitleView = isAllAlias ? (\n      <Text\n        numberOfLines={1}\n        ellipsizeMode='tail'\n        style={[listStyle?.listItemStyle.titleStyle, { flexShrink: 1 }]}\n      >\n        <Text style={[listStyle?.listItemStyle.titleStyle, { fontWeight: '700' }]}>@{item.name} </Text>\n        <Text style={[theme.typography.caption1.regular, { opacity: 0.7 }]}>{t('MESSAGE_COMPOSER_MENTION_NOTIFY_EVERYONE_LABEL')}</Text>\n      </Text>\n    ) : undefined;\n    return (\n      <CometChatListItem\n        key={index}\n        id={item.id}\n        title={isAllAlias ? undefined : item.name}\n        TitleView={TitleView}\n        avatarURL={item.leadingIconUrl ? { uri: item.leadingIconUrl } : undefined}\n        containerStyle={listStyle?.listItemStyle.containerStyle}\n        titleStyle={listStyle?.listItemStyle.titleStyle}\n        avatarStyle={listStyle?.listItemStyle.avatarStyle}\n        titleSubtitleContainerStyle={{\n          alignSelf: \"center\",\n        }}\n        onPress={() => onPress(item)}\n        {...shouldLoadAvatarName}\n      />\n    );\n  };\n\n  return (\n    <View>\n      {initialLoad ? (\n        <View>\n          <Skeleton />\n        </View>\n      ) : (\n        <FlatList\n          data={data}\n          renderItem={_render}\n          keyExtractor={(item, index) => `${item.id}_${index}`}\n          onMomentumScrollEnd={(event) => {\n            const contentOffsetY = event.nativeEvent.contentOffset.y; // The current scroll position\n            const contentHeight = event.nativeEvent.contentSize.height; // Total height of the content\n            const layoutHeight = event.nativeEvent.layoutMeasurement.height; // Height of the visible area\n\n            if (contentOffsetY + layoutHeight >= contentHeight - 10) {\n              onEndReached && onEndReached();\n            }\n          }}\n          onEndReachedThreshold={0.3}\n          keyboardShouldPersistTaps={\"always\"}\n        />\n      )}\n      {!initialLoad && (\n        <View\n          style={{\n            position: \"absolute\",\n            bottom: 0,\n            width: \"100%\",\n          }}\n        >\n          {loading && <ActivityIndicator animating={loading} />}\n        </View>\n      )}\n    </View>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSuggestionList/Skeleton.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from \"react-native\";\nimport Svg, { Defs, LinearGradient, Circle, Rect, Stop } from \"react-native-svg\";\nimport { useTheme } from \"../../../theme\";\n\nconst CircleSkeleton = () => {\n  const theme = useTheme();\n\n  return (\n    <View style={styles.circleContainer}>\n      <Svg height={50} width={70}>\n        <Defs>\n          <LinearGradient\n            id='circleGradient'\n            x1={0}\n            y1={0}\n            x2={40}\n            y2={1}\n            gradientUnits='userSpaceOnUse'\n          >\n            <Stop stopColor={theme.mentionsListStyle.skeletonStyle.linearGradientColors[0]} />\n            <Stop\n              offset={1}\n              stopColor={theme.mentionsListStyle.skeletonStyle.linearGradientColors[1]}\n            />\n          </LinearGradient>\n        </Defs>\n        <Circle cx={34.5} cy={25} r={20} fill='url(#circleGradient)' />\n      </Svg>\n    </View>\n  );\n};\n\nconst RectangleSkeleton = () => {\n  const theme = useTheme();\n\n  return (\n    <View style={styles.rectangleContainer}>\n      <Svg height={50} width={270}>\n        <Defs>\n          <LinearGradient\n            id='rectangleGradient'\n            x1={0}\n            y1={24}\n            x2={240}\n            y2={24}\n            gradientUnits='userSpaceOnUse'\n          >\n            <Stop stopColor={theme.mentionsListStyle.skeletonStyle.linearGradientColors[0]} />\n            <Stop\n              offset={1}\n              stopColor={theme.mentionsListStyle.skeletonStyle.linearGradientColors[1]}\n            />\n          </LinearGradient>\n        </Defs>\n        <Rect x={0} y={17} width={270} height={20} rx={10} fill='url(#rectangleGradient)' />\n      </Svg>\n    </View>\n  );\n};\n\nexport const Skeleton = () => {\n  const theme = useTheme();\n  const animatedValue = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const startShimmer = () => {\n      animatedValue.setValue(0);\n      Animated.loop(\n        Animated.timing(animatedValue, {\n          toValue: 1,\n          duration: theme.mentionsListStyle.skeletonStyle.speed * 1000,\n          easing: Easing.linear,\n          useNativeDriver: false,\n        })\n      ).start();\n    };\n\n    startShimmer();\n  }, [animatedValue]);\n\n  const shimmerTranslateXCircle = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [0, 50],\n  });\n\n  const shimmerTranslateXRectangle = animatedValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [0, 130],\n  });\n\n  return (\n    <ScrollView>\n      {new Array(20).fill(0).map((_, i) => (\n        <View key={i} style={styles.skeletonContainer}>\n          <View style={theme.mentionsListStyle.listItemStyle.containerStyle}>\n            <View style={styles.circleWrapper}>\n              <CircleSkeleton />\n              <Animated.View\n                style={[\n                  styles.circleShimmer,\n                  {\n                    transform: [{ translateX: shimmerTranslateXCircle }],\n                    backgroundColor: theme.mentionsListStyle.skeletonStyle.shimmerBackgroundColor,\n                    opacity: theme.mentionsListStyle.skeletonStyle.shimmerOpacity,\n                  },\n                ]}\n              />\n            </View>\n            <View style={styles.rectangleWrapper}>\n              <RectangleSkeleton />\n              <Animated.View\n                style={[\n                  styles.rectangleShimmer,\n                  {\n                    transform: [{ translateX: shimmerTranslateXRectangle }],\n                    backgroundColor: theme.mentionsListStyle.skeletonStyle.shimmerBackgroundColor,\n                    opacity: theme.mentionsListStyle.skeletonStyle.shimmerOpacity,\n                  },\n                ]}\n              />\n            </View>\n          </View>\n        </View>\n      ))}\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  skeletonContainer: {\n    flexDirection: \"row\", // Align skeleton items in a row\n    alignItems: \"center\", // Center items vertically\n  },\n  skeletonRow: {\n    flexDirection: \"row\", // Arrange circle and rectangle side by side\n    alignItems: \"center\", // Center align vertically\n  },\n  circleWrapper: {\n    position: \"relative\", // Allow absolute positioning for shimmer effect\n    height: 50, // Reduced height\n    width: 40, // Width to match the SVG\n    overflow: \"hidden\",\n    alignSelf: \"center\",\n  },\n  circleContainer: {\n    height: 50, // Height matching the SVG\n    justifyContent: \"center\", // Center content vertically\n    alignItems: \"center\", // Center content horizontally,\n  },\n  circleShimmerContainer: {\n    position: \"absolute\",\n    top: 5,\n    left: 15, // Start shimmer from the left\n    height: 40, // Match the SVG height\n    width: 40, // Ensure shimmer is wide enough to cover the circle\n    overflow: \"hidden\", // Hide anything that goes outside this container\n    borderRadius: 50, // Fully round to match the circle\n  },\n  circleShimmer: {\n    height: 40,\n    width: 40, // This matches the shimmer width\n    borderRadius: 20, // Make it circular\n    position: \"absolute\",\n    left: 0,\n    top: 5,\n  },\n  rectangleWrapper: {\n    flex: 1, // Allow the rectangle to take the remaining space\n    position: \"relative\", // Allow absolute positioning for shimmer effect\n    alignSelf: \"center\",\n    justifyContent: \"center\",\n  },\n  rectangleContainer: {\n    position: \"relative\",\n  },\n  rectangleShimmer: {\n    position: \"absolute\",\n    top: 17,\n    left: 0, // Ensure it's aligned to the left of the rectangle\n    height: 20, // Match the height of the rectangle\n    width: 140, // Match the width of the rectangle\n    borderRadius: 10, // Optional: Add rounded corners to the shimmer\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSuggestionList/SuggestionItem.ts",
    "content": "export class SuggestionItem {\n  /**\n   * Unique identifier of item\n   */\n  id!: string;\n  /**\n   * Name to be displayed in the list\n   */\n  name?: string;\n  /**\n   * Avatar Icon Url\n   */\n  leadingIconUrl?: string;\n  /**\n   * Hide leading icon\n   * @default true\n   */\n  hideLeadingIcon?: boolean = true;\n  /**\n   * Presence Indicator\n   */\n  status?: \"online\" | \"offline\";\n  /**\n   * Name to be displayed in the composer\n   */\n  promptText?: string;\n  /**\n   * underlying text\n   * @example <@uid:superhero1>\n   */\n  underlyingText!: string;\n  /**\n   * set a tracking character.\n   * @description string length has be 1.\n   * @example '@' | '#' etc.\n   */\n  trackingCharacter?: string;\n  /**\n   * extra data to be passed in JSON format\n   */\n  data?: JSON;\n\n  constructor(props: SuggestionItem) {\n    Object.assign(this, props);\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSuggestionList/index.ts",
    "content": "import {\n  CometChatSuggestionList,\n  CometChatSuggestionListInterface,\n} from \"./CometChatSuggestionList\";\nimport { SuggestionItem } from \"./SuggestionItem\";\n\nexport { CometChatSuggestionList, SuggestionItem };\n\nexport type {\n  CometChatSuggestionListInterface\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatSuggestionList/style.ts",
    "content": "import { Dimensions } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\nimport { deepMerge } from \"../../helper/helperFunctions\";\nimport { DeepPartial } from \"../../helper/types\";\n\nexport const getMentionsListStyleLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CometChatTheme[\"mentionsListStyle\"]> => {\n  return {\n    containerStyle: {\n      borderRadius: spacing.radius.r4,\n      paddingVertical: spacing.padding.p2,\n      backgroundColor: color.background1,\n      borderWidth: 1,\n      borderColor: color.borderDark,\n      maxHeight: Dimensions.get(\"window\").height * 0.3,\n      justifyContent: \"flex-end\",\n      marginBottom: spacing.margin.m1,\n      paddingHorizontal: 5,\n    },\n    listItemStyle: {\n      containerStyle: {\n        backgroundColor: color.background1,\n        paddingVertical: spacing.padding.p2,\n        paddingHorizontal: spacing.padding.p4,\n        flexDirection: \"row\",\n        gap: 12,\n      },\n      titleStyle: {\n        ...typography.heading4.medium,\n        color: color.textPrimary,\n      },\n      avatarStyle: {\n        containerStyle: {\n          height: 40,\n          width: 40,\n        },\n        textStyle: {},\n        imageStyle: {\n          height: \"100%\",\n          width: \"100%\",\n          borderRadius: spacing.radius.max,\n        },\n      },\n    },\n    skeletonStyle: {\n      linearGradientColors: [\"#E8E8E8\", \"#F5F5F5\"] as [string, string],\n      shimmerBackgroundColor: color.staticBlack,\n      shimmerOpacity: 0.01,\n      speed: 0.7,\n    },\n  };\n};\n\nexport const getMentionsListStyleDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CometChatTheme[\"mentionsListStyle\"]> => {\n  return deepMerge(getMentionsListStyleLight(color, spacing, typography), {\n    skeletonStyle: {\n      linearGradientColors: [\"#383838\", \"#272727\"] as [string, string],\n      shimmerBackgroundColor: color.staticWhite,\n      shimmerOpacity: 0.01,\n      speed: 0.7,\n    },\n  });\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatTextBubble/CometChatTextBubble.tsx",
    "content": "import React, { useLayoutEffect, useState, useRef, useCallback, useMemo, JSX } from \"react\";\nimport {\n  StyleProp,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n  TouchableOpacity,\n  LayoutChangeEvent,\n  TextLayoutEvent,\n} from \"react-native\";\nimport {\n  CometChatMentionsFormatter,\n  CometChatTextFormatter,\n  CometChatUrlsFormatter,\n} from \"../../formatters\";\nimport { useCometChatTranslation } from \"../../resources/CometChatLocalizeNew\";\n\n// Pre-allocated style for view-based content wrapper (blockquotes, lists).\nconst VIEW_BASED_WRAPPER_STYLE = { flexShrink: 1 as const };\n\nexport interface CometChatTextBubbleInterface {\n  /*** text to be shown */\n  text?: string;\n  textStyle?: StyleProp<TextStyle>;\n  /** text container style */\n  textContainerStyle?: StyleProp<ViewStyle>;\n  textFormatters?: Array<\n    CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter\n  >;\n  /** number of lines to collapse to (default 4) */\n  collapseLines?: number;\n  /** style for the toggle container */\n  toggleContainerStyle?: StyleProp<ViewStyle>;\n  /** style for the toggle text */\n  toggleTextStyle?: StyleProp<TextStyle>;\n}\n\nexport const CometChatTextBubble = (props: CometChatTextBubbleInterface) => {\n  const { textContainerStyle } = props;\n\n  return (\n    <View style={textContainerStyle}>\n      <CometChatTextBubbleText {...props} />\n    </View>\n  );\n};\n\n/**\n * Recursively apply textStyle to all Text elements within a tree.\n * Needed for View-based content (blockquotes, lists) where Text nodes\n * can be nested multiple levels deep inside View wrappers.\n */\nfunction applyTextStyleDeep(\n  el: React.ReactElement<any>,\n  textStyle: StyleProp<TextStyle> | undefined\n): React.ReactElement<any> {\n  if (el.type === Text) {\n    return React.cloneElement(el, {\n      style: [textStyle, el.props?.style],\n    });\n  }\n  if (el.type === View && el.props.children) {\n    const newChildren = React.Children.map(el.props.children, (child) => {\n      if (!React.isValidElement(child)) return child;\n      return applyTextStyleDeep(child as React.ReactElement<any>, textStyle);\n    });\n    return React.cloneElement(el, {}, newChildren);\n  }\n  return el;\n}\n\n/**\n * CometChatTextBubbleText\n *\n * - measure by actual container width (onLayout) so both iOS and Android measure correctly\n * - robust measurement cache keyed by text+width so we don't re-measure unnecessarily\n */\nexport const CometChatTextBubbleText = (\n  props: Omit<CometChatTextBubbleInterface, \"textContainerStyle\">\n) => {\n  const { t } = useCometChatTranslation();\n  const {\n    text = \"\",\n    textFormatters,\n    textStyle,\n    collapseLines = 4,\n    toggleContainerStyle,\n    toggleTextStyle,\n  } = props;\n\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [isTruncatable, setIsTruncatable] = useState(false);\n  const [containerWidth, setContainerWidth] = useState<number | null>(null);\n  const [measurementComplete, setMeasurementComplete] = useState(false);\n\n  // Refs to track state without causing re-renders\n  const measuredCacheRef = useRef<Record<string, boolean>>({});\n  const lastTextRef = useRef<string>(text);\n\n  // Compute formatted text synchronously - no state update needed\n  // Result can be string or JSX.Element (when rich text formatter produces View-based content)\n  const formattedText = useMemo(() => {\n    let finalText: string | JSX.Element | null = text;\n    if (textFormatters && textFormatters.length) {\n      for (let i = 0; i < textFormatters.length; i++) {\n        finalText = textFormatters[i].getFormattedText(finalText);\n      }\n    }\n    return finalText as string | JSX.Element;\n  }, [text, textFormatters]);\n\n  // Reset state only when text actually changes\n  useLayoutEffect(() => {\n    if (lastTextRef.current !== text) {\n      lastTextRef.current = text;\n      setIsExpanded(false);\n      setIsTruncatable(false);\n      setMeasurementComplete(false);\n    }\n  }, [text]);\n\n  // Handler to capture container width - use functional update to avoid dependency\n  const onContainerLayout = useCallback((e: LayoutChangeEvent) => {\n    const w = Math.round(e.nativeEvent.layout.width);\n    if (w > 0) {\n      setContainerWidth(prev => prev === w ? prev : w);\n    }\n  }, []);\n\n  // Called when hidden text is laid out\n  // Use original text for the measurement key (formattedText may be JSX)\n  const onMeasuredTextLayout = useCallback((e: TextLayoutEvent) => {\n    const key = `${text}::${containerWidth ?? 0}`;\n\n    // Skip if already measured\n    if (measuredCacheRef.current[key]) {\n      if (!measurementComplete) {\n        setMeasurementComplete(true);\n      }\n      return;\n    }\n\n    const lines = e.nativeEvent.lines;\n    const isNowTruncatable = !!(lines && lines.length > collapseLines);\n\n    measuredCacheRef.current[key] = true;\n    setIsTruncatable(isNowTruncatable);\n    setMeasurementComplete(true);\n  }, [text, containerWidth, collapseLines, measurementComplete]);\n\n  const toggle = useCallback(() => setIsExpanded(v => !v), []);\n\n  // Use original text for keys (formattedText may be JSX and can't be stringified)\n  const hiddenTextKey = `${text}::${containerWidth ?? 0}`;\n\n  // Check if formattedText is a View-based element (e.g. blockquote with View wrapper).\n  // View elements cannot be nested inside Text, so render them directly.\n  const isViewBased = React.isValidElement(formattedText) &&\n    (formattedText as React.ReactElement<any>).type === View;\n\n  // For View-based content, count children to determine truncatability and slice when collapsed.\n  // This avoids pixel-based clipping that cuts through the middle of list items.\n  const viewChildren = useMemo(() => {\n    if (!isViewBased) return null;\n    const children = React.Children.toArray(\n      (formattedText as React.ReactElement<any>).props.children\n    ).filter(React.isValidElement) as React.ReactElement<any>[];\n    return children;\n  }, [isViewBased, formattedText]);\n\n  const viewChildCount = viewChildren?.length ?? 0;\n  const isViewTruncatable = isViewBased && viewChildCount > collapseLines;\n\n  // Memoize styled children to avoid re-running applyTextStyleDeep on every render/toggle\n  const styledViewChildren = useMemo(() => {\n    if (!viewChildren) return null;\n    return viewChildren.map((child) => {\n      if (!React.isValidElement(child)) return child;\n      return applyTextStyleDeep(child as React.ReactElement<any>, textStyle);\n    });\n  }, [viewChildren, textStyle]);\n\n  // Only measure string content via onTextLayout — View-based uses child count instead\n  const needsMeasurement = containerWidth !== null && !isViewBased && !measuredCacheRef.current[hiddenTextKey];\n\n  return (\n    <View onLayout={onContainerLayout}>\n      {/* Hidden measurement text - only render if we need to measure */}\n      {needsMeasurement && (\n        <Text\n          key={hiddenTextKey}\n          style={[\n            { position: \"absolute\", left: 0, top: -10000, width: containerWidth!, opacity: 0 },\n            textStyle as any,\n          ]}\n          onTextLayout={onMeasuredTextLayout}\n          accessible={false}\n          importantForAccessibility='no-hide-descendants'\n        >\n          {formattedText}\n        </Text>\n      )}\n\n      {/* Visible text — View-based content (blockquotes, lists) rendered directly */}\n      {isViewBased && styledViewChildren ? (\n        <View style={VIEW_BASED_WRAPPER_STYLE}>\n          {isExpanded || !isViewTruncatable ? styledViewChildren : styledViewChildren.slice(0, collapseLines)}\n        </View>\n      ) : (\n        <Text\n          style={textStyle}\n          numberOfLines={isExpanded ? undefined : collapseLines}\n          ellipsizeMode='tail'\n        >\n          {formattedText}\n        </Text>\n      )}\n\n      {/* Toggle - only show after measurement is complete to prevent flicker */}\n      {((measurementComplete && isTruncatable) || isViewTruncatable) && (\n        <View style={[{ alignItems: \"flex-end\", marginTop: 6 }, toggleContainerStyle]}>\n          <TouchableOpacity onPress={toggle} accessibilityRole='button'>\n            <Text style={[{ alignSelf: \"flex-end\" }, textStyle, toggleTextStyle]}>\n              {isExpanded ? t(\"SHOW_LESS\") : t(\"READ_MORE\")}\n            </Text>\n          </TouchableOpacity>\n        </View>\n      )}\n    </View>\n  );\n};\nexport default CometChatTextBubble;\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatTextBubble/index.ts",
    "content": "import { CometChatTextBubble, CometChatTextBubbleInterface } from \"./CometChatTextBubble\";\n\n\nexport {\n  CometChatTextBubble,\n};\n\nexport type {\n  CometChatTextBubbleInterface,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatTooltipMenu/CometChatTooltipMenu.tsx",
    "content": "import { JSX, useMemo } from \"react\";\nimport {\n  ColorValue,\n  Dimensions,\n  ImageSourcePropType,\n  ImageStyle,\n  Modal,\n  StyleSheet,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  TouchableWithoutFeedback,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { useTheme } from \"../../../theme\";\nimport { Icon } from \"../../icons/Icon\";\n\nconst { width: screenWidth, height: screenHeight } = Dimensions.get(\"window\");\n\n/**\n * Interface for the menu items (tooltip actions).\n */\nexport interface MenuItemInterface {\n  text: string;\n  translationKey?: string; // for localization\n  onPress: () => void;\n  icon?: ImageSourcePropType | JSX.Element;\n  textStyle?: TextStyle;\n  iconStyle?: ImageStyle;\n  iconContainerStyle?: ViewStyle;\n  disabled?: boolean;\n}\n\n/**\n * Props for the CometChatTooltipMenu component.\n */\ntype CometChatTooltipMenuProps = {\n  /**\n   * Controls the visibility of the tooltip menu.\n   */\n  visible?: boolean;\n  /**\n   * Callback function invoked when the tooltip menu is dismissed.\n   */\n  onDismiss?: () => void;\n  /**\n   * Callback function invoked when the tooltip menu is closed.\n   */\n  onClose?: () => void;\n  /**\n   * The event object containing the coordinates where the tooltip menu should appear.\n   */\n  event: {\n    nativeEvent: {\n      /**\n       * The X coordinate of the event.\n       */\n      pageX: number;\n      /**\n       * The Y coordinate of the event.\n       */\n      pageY: number;\n    };\n  };\n  /**\n   * An array of menu item objects that define the options in the tooltip menu.\n   */\n  menuItems: MenuItemInterface[];\n};\n\nexport const CometChatTooltipMenu = (props: CometChatTooltipMenuProps) => {\n  const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;\n  const theme = useTheme();\n\n  const position = useMemo(() => {\n    let x = event.nativeEvent.pageX;\n    let y = event.nativeEvent.pageY;\n    const position: {\n      left?: number;\n      right?: number;\n      top?: number;\n      bottom?: number;\n    } = {};\n    if (x <= screenWidth / 3) {\n      position.left = x + 10;\n    } else {\n      position.right = 12;\n    }\n\n    if (y <= screenHeight / 2) {\n      position.top = y + 20;\n    } else if (y >= screenHeight / 2) {\n      position.bottom = Math.max(screenHeight - y + 10, 40);\n    }\n    return position;\n  }, [event]);\n\n  return (\n    <Modal\n      presentationStyle='overFullScreen'\n      transparent={true}\n      visible={visible}\n      onRequestClose={onClose}\n      onDismiss={onDismiss}\n      animationType='none'\n    >\n      <TouchableWithoutFeedback onPress={onClose}>\n        <View style={tooltipStyles.overlay}>\n          <View\n            style={[\n              tooltipStyles.menu,\n              position,\n              {\n                backgroundColor: theme.color.background1,\n                borderWidth: 1,\n                borderColor: theme.color.borderLight,\n                borderRadius: theme.spacing.radius.r2,\n                shadowColor: theme.color.neutral900,\n              },\n            ]}\n          >\n            {menuItems.map((item, i) => {\n              return (\n                <TouchableOpacity\n                  key={i} // Ensure each item has a unique key\n                  onPress={() => {\n                    item.onPress();\n                    onClose();\n                  }}\n                  style={[\n                    {\n                      flexDirection: \"row\",\n                      alignItems: \"center\",\n                      paddingVertical: 10,\n                      paddingHorizontal: 16,\n                      gap: 8,\n                      backgroundColor: theme.color.background1,\n                      minWidth: 160,\n                    },\n                    i === 0\n                      ? {\n                          borderTopLeftRadius: theme.spacing.radius.r2,\n                          borderTopRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                    i === menuItems.length - 1\n                      ? {\n                          borderBottomLeftRadius: theme.spacing.radius.r2,\n                          borderBottomRightRadius: theme.spacing.radius.r2,\n                        }\n                      : {},\n                  ]}\n                >\n                  <Icon\n                    color={item?.iconStyle?.tintColor ?? theme.color.textSecondary}\n                    size={item?.iconStyle?.height ?? theme.spacing.spacing.s6}\n                    height={item?.iconStyle?.height ?? theme.spacing.spacing.s6}\n                    width={item?.iconStyle?.width ?? theme.spacing.spacing.s6}\n                    icon={item.icon}\n                    containerStyle={item?.iconContainerStyle}\n                  />\n                  <Text\n                    style={{\n                      color: theme.color.textPrimary,\n                      ...theme.typography.heading4.regular,\n                      ...item?.textStyle,\n                    }}\n                  >\n                    {item.text}\n                  </Text>\n                </TouchableOpacity>\n              );\n            })}\n          </View>\n        </View>\n      </TouchableWithoutFeedback>\n    </Modal>\n  );\n};\n\nconst tooltipStyles = StyleSheet.create({\n  overlay: {\n    flex: 1,\n    backgroundColor: \"transparent\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  menu: {\n    position: \"absolute\",\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.025,\n    shadowRadius: 4,\n    elevation: 3,\n  },\n  menuItem: {\n    fontSize: 16,\n    paddingVertical: 5,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatTooltipMenu/index.ts",
    "content": "export { tooltipStyles } from \"./styles\";\n\nexport { CometChatTooltipMenu } from \"./CometChatTooltipMenu\";\nexport type {MenuItemInterface} from \"./CometChatTooltipMenu\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatTooltipMenu/styles.ts",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport const tooltipStyles = StyleSheet.create({\n  overlay: {\n    flex: 1,\n    backgroundColor: 'transparent', \n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  menu: {\n    position: 'absolute',\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.025,\n    shadowRadius: 4,\n    elevation: 3,\n  },\n  menuItem: {\n    fontSize: 16,\n    paddingVertical: 5,\n  },\n});\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatVideoBubble/CometChatVideoBubble.tsx",
    "content": "import React, { JSX, useEffect, useState } from \"react\";\nimport {\n  ActivityIndicator,\n  EmitterSubscription,\n  ImageBackground,\n  ImageSourcePropType,\n  ImageStyle,\n  NativeEventEmitter,\n  NativeModules,\n  Pressable,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { CometChatVideoPlayer } from \"../CometChatVideoPlayer\";\nimport { defaultThumbnail } from \"./resources\";\nimport { Icon } from \"../../icons/Icon\";\nimport { CommonUtils } from \"../../utils/CommonUtils\";\n\nconst { FileManager } = NativeModules;\nconst eventEmitter = new NativeEventEmitter(FileManager);\nlet statusListener: EmitterSubscription;\n/**\n * Props for the CometChatVideoBubble component.\n */\nexport interface CometChatVideoBubbleInterface {\n  /**\n   * URL for the video.\n   */\n  videoUrl: string;\n  /**\n   * Thumbnail URL for the video bubble.\n   */\n  thumbnailUrl?: ImageSourcePropType;\n  /**\n   * Placeholder image to display while the video is loading.\n   */\n  placeholderImage?: ImageSourcePropType;\n  /**\n   * Custom play icon, which can be a JSX element or an image source.\n   */\n  playIcon?: JSX.Element | ImageSourcePropType;\n  /**\n   * Callback function executed when the play button is clicked.\n   * The function receives the video URL as a parameter.\n   *\n   * @param videoUrl - The URL of the video.\n   */\n  onPress?: Function;\n  /**\n   * Custom style for the video thumbnail image.\n   */\n  imageStyle?: ImageStyle;\n  /**\n   * Custom style for the play icon.\n   */\n  playIconStyle?: ImageStyle;\n  /**\n   * Custom style for the container of the play icon.\n   */\n  playIconContainerStyle?: ViewStyle;\n}\n/**\n * CometChatVideoBubble is a component that displays a video bubble with a thumbnail image\n * and a play icon. It handles video playback when the play icon is clicked.\n *\n * @param props - Props for the component.\n * @returns The rendered video bubble.\n */\n\nexport const CometChatVideoBubble = (props: CometChatVideoBubbleInterface) => {\n  const {\n    videoUrl,\n    thumbnailUrl,\n    placeholderImage,\n    playIcon,\n    playIconStyle,\n    onPress,\n    playIconContainerStyle = {},\n    imageStyle,\n  } = props;\n\n  const [isOpening, setOpening] = useState(false);\n  const [isLoading, setIsLoading] = useState(false);\n  const [isVideoPlayerVisible, setIsVideoPlayerVisible] = useState(false);\n  const [imageSource, setImageSource] = useState<ImageSourcePropType>();\n\n  // Prefetch thumbnail logic on mount\n  useEffect(() => {\n    if (thumbnailUrl && typeof thumbnailUrl === \"object\" && \"uri\" in thumbnailUrl) {\n      CommonUtils.prefetchThumbnail(thumbnailUrl.uri!).then((success) => {\n        if (success) {\n          setImageSource(thumbnailUrl);\n        } else {\n          setImageSource(placeholderImage ?? defaultThumbnail);\n        }\n      });\n    } else {\n      setImageSource(placeholderImage ?? defaultThumbnail);\n    }\n  }, [thumbnailUrl, placeholderImage]);\n\n  const playVideo = () => {\n    if (isOpening) {\n      return;\n    }\n\n    if (onPress) {\n      onPress(videoUrl);\n      return;\n    }\n\n    if (!videoUrl) return;\n    setIsLoading(true);\n    setIsVideoPlayerVisible(true);\n  };\n\n  useEffect(() => {\n    statusListener = eventEmitter.addListener(\"status\", (data) => {\n      if (data[\"url\"] == videoUrl && data[\"state\"] == \"downloading\") {\n        setOpening(true);\n      }\n      if (data[\"url\"] == videoUrl && data[\"state\"] == \"opening\") {\n        setOpening(false);\n      }\n    });\n\n    return () => {\n      statusListener.remove();\n    };\n  }, []);\n\n  return (\n    <>\n      <CometChatVideoPlayer\n        videoUri={videoUrl}\n        isVisible={isVideoPlayerVisible}\n        onClose={() => {\n          setIsLoading(false);\n          setIsVideoPlayerVisible(false);\n        }}\n        onLoad={() => setIsLoading(false)}\n      />\n      <ImageBackground source={imageSource} resizeMode={\"cover\"} style={imageStyle}>\n        <View style={{ flex: 1, justifyContent: \"center\", alignItems: \"center\" }}>\n          <Pressable onPress={() => playVideo()} style={playIconContainerStyle} hitSlop={12}>\n            <View>\n              {isLoading ? (\n                <ActivityIndicator\n                  size={typeof playIconStyle?.height === \"number\" ? playIconStyle.height : \"large\"}\n                  color={playIconStyle?.tintColor}\n                />\n              ) : (\n                <Icon\n                  name='play-arrow-fill'\n                  height={playIconStyle?.height}\n                  width={playIconStyle?.width}\n                  icon={playIcon}\n                  imageStyle={playIconStyle}\n                  color={playIconStyle?.tintColor}\n                ></Icon>\n              )}\n            </View>\n          </Pressable>\n        </View>\n      </ImageBackground>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatVideoBubble/index.ts",
    "content": "import { CometChatVideoBubble, CometChatVideoBubbleInterface } from \"./CometChatVideoBubble\";\n\nexport {\n  CometChatVideoBubble,\n};\n\nexport type {\n  CometChatVideoBubbleInterface\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatVideoBubble/resources/index.ts",
    "content": "import defaultThumbnail from \"./default_image.png\";\nimport defaultPlayIcon from \"./play.png\";\n\nexport { defaultPlayIcon, defaultThumbnail };\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatVideoBubble/style.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport { CometChatTheme } from \"../../../theme/type\";\n\nexport const Style = StyleSheet.create({\n  playIconPosition: {\n    flex: 1,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n});\n\nexport const getVideoBubbleStylesLight = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"videoBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"videoBubbleStyles\"];\n} => {\n  return {\n    incomingBubbleStyle: {\n      containerStyle: {\n        padding: spacing.padding.p1,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n      },\n      imageStyle: {\n        borderRadius: spacing.radius.r2,\n        backgroundColor: color.background3,\n        height: 140,\n        width: 232,\n        overflow: 'hidden'\n      },\n      playIconStyle: {\n        height: 48,\n        width: 48,\n        tintColor: color.primaryButtonIcon\n      },\n      playIconContainerStyle: {\n        //flex: 1,\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        alignSelf: 'center',\n        height: 48,\n        width: 48,\n        borderRadius: spacing.radius.max,\n        backgroundColor: '#14141499',\n        position: 'absolute',\n        left: 96,\n        top: 50\n      },\n      dateReceiptContainerStyle: {\n        paddingRight: spacing.padding.p1\n      }\n    },\n    outgoingBubbleStyle: {\n      containerStyle: {\n        padding: spacing.padding.p1,\n        paddingBottom: spacing.padding.p0,\n        borderRadius: spacing.radius.r3,\n        alignSelf: \"flex-start\",\n        justifyContent: 'center',\n      },\n      imageStyle: {\n        borderRadius: spacing.radius.r2,\n        backgroundColor: color.background3,\n        height: 140,\n        width: 232,\n        overflow: 'hidden'\n      },\n      playIconStyle: {\n        height: 48,\n        width: 48,\n        tintColor: color.primaryButtonIcon\n      },\n      playIconContainerStyle: {\n        //flex: 1,\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        alignSelf: 'center',\n        height: 48,\n        width: 48,\n        borderRadius: spacing.radius.max,\n        backgroundColor: '#14141499',\n        position: 'absolute',\n        left: 96,\n        top: 50\n      },\n      dateReceiptContainerStyle: {\n        paddingRight: spacing.padding.p1\n      }\n    },\n  };\n};\n\nexport const getVideoBubbleStylesDark = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): {\n  incomingBubbleStyle: CometChatTheme[\"videoBubbleStyles\"];\n  outgoingBubbleStyle: CometChatTheme[\"videoBubbleStyles\"];\n} => {\n  return getVideoBubbleStylesLight(color, spacing, typography);\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatVideoPlayer/CometChatVideoPlayer.tsx",
    "content": "import React, { useState, useEffect, useRef } from \"react\";\nimport {\n  ActivityIndicator,\n  Dimensions,\n  Modal,\n  Platform,\n  StyleSheet,\n  TouchableWithoutFeedback,\n  View,\n} from \"react-native\";\nimport Video from \"react-native-video\";\nimport { Icon } from \"../../icons/Icon\";\n\n/**\n * Props for the CometChatVideoPlayer component.\n */\nexport interface CometChatVideoPlayerProps {\n  /**\n   * URI of the video to play.\n   */\n  videoUri: string;\n  /**\n   * Flag indicating whether the video player is visible.\n   */\n  isVisible: boolean;\n  /**\n   * Callback function invoked when the video player is closed.\n   */\n  onClose: () => void;\n  /**\n   * Callback function invoked when the video has loaded.\n   */\n  onLoad: () => void;\n  /**\n   * Optional color for the loading icon.\n   */\n  loadingIconColor?: string;\n}\n\nexport const CometChatVideoPlayer = (props: CometChatVideoPlayerProps) => {\n  const { videoUri, isVisible, onClose, onLoad, loadingIconColor } = props;\n  const [isPaused, setPaused] = useState(false);\n  const [isLoading, setIsLoading] = useState(true);\n  const [showCloseButton, setShowCloseButton] = useState(true);\n  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  // Handle closing the video player\n  const handleClose = () => {\n    setPaused(true);\n    onClose();\n  };\n  const handleLoadStart = () => {\n    setIsLoading(true);\n    setPaused(false);\n  };\n\n  const handleBuffer = (bufferData: { isBuffering: boolean }) => {\n    setIsLoading(bufferData.isBuffering);\n  };\n\n  const handleLoad = () => {\n    setIsLoading(false);\n    onLoad();\n  };\n\n  const resetHideTimer = () => {\n    // Clear existing timer\n    if (timerRef.current) {\n      clearTimeout(timerRef.current);\n      timerRef.current = null;\n    }\n\n    // Show close button and set new timer\n    setShowCloseButton(true);\n    timerRef.current = setTimeout(() => {\n      setShowCloseButton(false);\n    }, 2500);\n  };\n\n  // Handle tap on video area\n  const handleVideoTap = () => {\n    resetHideTimer();\n  };\n\n  // Set up and clean up timers based on visibility\n  useEffect(() => {\n    if (isVisible) {\n      resetHideTimer();\n    } else {\n      // Clean up on close\n      if (timerRef.current) {\n        clearTimeout(timerRef.current);\n        timerRef.current = null;\n      }\n      // Reset to visible state for next open\n      setShowCloseButton(true);\n    }\n\n    // Clear timer when component unmounts\n    return () => {\n      if (timerRef.current) {\n        clearTimeout(timerRef.current);\n      }\n    };\n  }, [isVisible]);\n\n  return (\n    <Modal\n      animationType='fade'\n      presentationStyle='overFullScreen'\n      transparent={true}\n      visible={isVisible}\n      onRequestClose={handleClose}\n    >\n      <View style={styles.modalBackground}>\n        <TouchableWithoutFeedback onPress={handleVideoTap}>\n          <View style={styles.playerContainer}>\n            <Video\n              source={{ uri: videoUri }}\n              controls={true}\n              resizeMode='contain'\n              onLoadStart={handleLoadStart}\n              onBuffer={handleBuffer}\n              onLoad={handleLoad}\n              onError={(e) => console.warn(\"Video Error: \", e?.error)}\n              paused={isPaused}\n              style={styles.video}\n            />\n            {isLoading && (\n              <ActivityIndicator\n                style={styles.loadingIndicator}\n                size='large'\n                color={loadingIconColor || \"#fff\"}\n              />\n            )}\n\n            {showCloseButton && (\n              <TouchableWithoutFeedback onPress={handleClose}>\n                <View style={styles.closeButton}>\n                  <Icon name='close' size={22} color={\"#fff\"} />\n                </View>\n              </TouchableWithoutFeedback>\n            )}\n          </View>\n        </TouchableWithoutFeedback>\n      </View>\n    </Modal>\n  );\n};\n\nconst { width, height } = Dimensions.get(\"window\");\nconst styles = StyleSheet.create({\n  modalBackground: {\n    flex: 1,\n    backgroundColor: \"rgba(0,0,0,0.8)\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  playerContainer: {\n    width: width,\n    height: height * 0.96,\n    backgroundColor: \"black\",\n    borderRadius: 10,\n    overflow: \"hidden\",\n    elevation: 5,\n    shadowColor: \"#000\",\n    shadowOffset: { width: 0, height: 2 },\n    shadowOpacity: 0.8,\n    shadowRadius: 2,\n  },\n  video: {\n    width: \"100%\",\n    height: \"100%\",\n  },\n  loadingIndicator: {\n    position: \"absolute\",\n    alignSelf: \"center\",\n    top: \"50%\",\n  },\n  closeButton: {\n    position: \"absolute\",\n    top: Platform.OS === \"ios\" ? 60 : 15,\n    right: 10,\n    width: 30,\n    height: 30,\n    backgroundColor: \"rgba(0,0,0,0.3)\",\n    borderRadius: 15,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    zIndex: 1,\n  },\n});"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/CometChatVideoPlayer/index.tsx",
    "content": "export { CometChatVideoPlayer } from \"./CometChatVideoPlayer\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/ErrorEmptyView/ErrorEmptyView.tsx",
    "content": "import { JSX } from \"react\";\nimport { StyleProp, Text, TextStyle, View, ViewStyle } from \"react-native\";\n\n/**\n * Props for the ErrorEmptyView component.\n */\ntype ErrorEmptyViewProps = {\n  /**\n   * The title text to display in the error/empty view.\n   */\n  title?: string;\n  /**\n   * The subtitle text to display in the error/empty view.\n   */\n  subTitle?: string;\n  /**\n   * A JSX element to display as an icon.\n   */\n  Icon?: JSX.Element;\n  /**\n   * An optional tertiary title for additional context.\n   */\n  tertiaryTitle?: string;\n  /**\n   * Custom style for the container of the error/empty view.\n   */\n  containerStyle?: StyleProp<ViewStyle>;\n  /**\n   * Custom style for the title text.\n   */\n  titleStyle?: StyleProp<TextStyle>;\n  /**\n   * Custom style for the subtitle text.\n   */\n  subTitleStyle?: StyleProp<TextStyle>;\n  /**\n   * A custom JSX element to render for a retry action.\n   */\n  RetryView?: JSX.Element;\n};\n\n\nexport const ErrorEmptyView = (props: ErrorEmptyViewProps) => {\n  const {\n    title,\n    subTitle,\n    tertiaryTitle = \"\",\n    Icon = null,\n    containerStyle = {},\n    titleStyle = {},\n    subTitleStyle = {},\n    RetryView = null,\n  } = props;\n  return (\n    <View style={[containerStyle]}>\n      {Icon}\n      {title && <Text style={titleStyle}>{title}</Text>}\n      {subTitle && <Text style={subTitleStyle}>{subTitle}</Text>}\n      {tertiaryTitle && <Text style={subTitleStyle}>{tertiaryTitle}</Text>}\n      {RetryView}\n    </View>\n  );\n};"
  },
  {
    "path": "packages/ChatUiKit/src/shared/views/index.ts",
    "content": "import {\n  ActionItemInterface,\n  CometChatActionSheet,\n} from \"./CometChatActionSheet\";\nimport { CometChatAudioBubble, CometChatAudioBubbleInterface } from \"./CometChatAudioBubble\";\nimport { CometChatFileBubble, CometChatFileBubbleInterface } from \"./CometChatFileBubble\";\nimport {\n  CometChatImageBubble,\n  CometChatImageBubbleInterface,\n} from \"./CometChatImageBubble\";\nimport {\n  CometChatTextBubble,\n  CometChatTextBubbleInterface,\n} from \"./CometChatTextBubble\";\nimport {\n  CometChatVideoBubble,\n  CometChatVideoBubbleInterface\n} from \"./CometChatVideoBubble\";\n\nimport {\n  CometChatDate,\n  CometChatDateInterface,\n  DateStyle,\n} from \"./CometChatDate\";\nimport {\n  CometChatList,\n  CometChatListActionsInterface,\n  CometChatListProps,\n  CometChatListStylesInterface,\n} from \"./CometChatList\";\nimport {\n  CometChatListItem,\n  CometChatListItemInterface,\n} from \"./CometChatListItem\";\nexport {\n  CometChatReceipt,\n} from \"./CometChatReceipt\";\n\nexport type {\n  CometChatReceiptInterface,\n} from \"./CometChatReceipt\";\n\nimport {\n  CometChatStatusIndicator,\n  CometChatStatusIndicatorInterface,\n} from \"./CometChatStatusIndicator\";\n\nimport { CometChatBottomSheet, CometChatBottomSheetInterface } from \"./CometChatBottomSheet\";\nimport {\n  CometChatConfirmDialog,\n  CometChatConfirmDialogInterface,\n} from \"./CometChatConfirmDialog\";\nimport {\n  CometChatMediaRecorder,\n  CometChatMediaRecorderInterface,\n} from \"./CometChatMediaRecorder\";\nimport {\n  CometChatMessageInput,\n  CometChatMessageInputInterface\n} from \"./CometChatMessageInput\";\n\nimport {\n  CometChatQuickReactions,\n} from \"./CometChatQuickReactions\";\nimport {\n  CometChatReactionList,\n  CometChatReactionListInterface,\n} from \"./CometChatReactionList\";\nimport {\n  CometChatReactions,\n  CometChatReactionsInterface,\n} from \"./CometChatReactions\";\nimport {\n  CometChatRetryButton,\n  CometChatRetryButtonProps,\n} from \"./CometChatRetryButton\";\n\nimport { CometChatEmojiKeyboard } from \"./CometChatEmojiKeyboard\";\n\nimport {\n  CometChatSuggestionList,\n  CometChatSuggestionListInterface,\n  SuggestionItem,\n} from \"./CometChatSuggestionList\";\nimport { CometChatAvatar } from \"./CometChatAvatar\";\nimport { BadgeStyle, CometChatBadge } from \"./CometChatBadge\";\nimport { MenuItemInterface } from \"./CometChatTooltipMenu\";\nimport {\n  CometChatReportDialog,\n  CometChatReportDialogInterface,\n} from \"./CometChatReportDialog\";\n\nimport {\n  CometChatLinkConfirmPopup,\n  CometChatLinkConfirmPopupInterface,\n} from \"./CometChatLinkConfirmPopup\";\n\nimport {\n  CometChatNewMessageIndicator,\n  CometChatNewMessageIndicatorInterface,\n  NewMessageIndicatorStyle,\n} from \"./CometChatNewMessageIndicator\";\n\nimport {\n  CometChatInlineAudioRecorder,\n  formatDuration,\n  AudioWaveformVisualizer,\n  useAudioRecorder,\n  getInlineAudioRecorderStyle,\n  getInlineAudioRecorderStyleLight,\n  getInlineAudioRecorderStyleDark,\n} from \"./CometChatInlineAudioRecorder\";\n\nimport type {\n  CometChatInlineAudioRecorderProps,\n  CometChatInlineAudioRecorderStyle,\n  AudioWaveformVisualizerProps,\n  WaveformStyle,\n  RecorderState,\n  UseAudioRecorderReturn,\n} from \"./CometChatInlineAudioRecorder\";\n\nexport type {\n  CometChatConfirmDialogInterface,\n  CometChatReportDialogInterface,\n  CometChatLinkConfirmPopupInterface,\n  CometChatDateInterface,\n  CometChatFileBubbleInterface,\n  CometChatImageBubbleInterface,\n  CometChatListActionsInterface,\n  CometChatBottomSheetInterface,\n  CometChatAudioBubbleInterface,\n  ActionItemInterface,\n  CometChatListItemInterface,\n  CometChatListStylesInterface,\n  CometChatMediaRecorderInterface,\n  CometChatMessageInputInterface,\n  CometChatReactionListInterface,\n  CometChatReactionsInterface,\n  CometChatRetryButtonProps,\n  CometChatStatusIndicatorInterface,\n  CometChatSuggestionListInterface,\n  CometChatVideoBubbleInterface,\n  CometChatTextBubbleInterface,\n  CometChatNewMessageIndicatorInterface,\n  CometChatInlineAudioRecorderProps,\n  CometChatInlineAudioRecorderStyle,\n  AudioWaveformVisualizerProps,\n  WaveformStyle,\n  RecorderState,\n  UseAudioRecorderReturn,\n};\n\nexport {\n  CometChatActionSheet,\n  CometChatAudioBubble,\n  CometChatAvatar,\n  CometChatBadge,\n  CometChatBottomSheet,\n  CometChatConfirmDialog,\n  CometChatReportDialog,\n  CometChatLinkConfirmPopup,\n  CometChatDate,\n  CometChatEmojiKeyboard,\n  CometChatFileBubble,\n  CometChatImageBubble,\n  CometChatList,\n  CometChatListItem,\n  CometChatMediaRecorder,\n  CometChatMessageInput,\n  CometChatQuickReactions,\n  CometChatReactionList,\n  CometChatReactions,\n  CometChatRetryButton,\n  CometChatStatusIndicator,\n  CometChatSuggestionList,\n  CometChatTextBubble,\n  CometChatVideoBubble,\n  SuggestionItem,\n  CometChatNewMessageIndicator,\n  CometChatInlineAudioRecorder,\n  formatDuration,\n  AudioWaveformVisualizer,\n  useAudioRecorder,\n  getInlineAudioRecorderStyle,\n  getInlineAudioRecorderStyleLight,\n  getInlineAudioRecorderStyleDark,\n};\n\n\nexport type {\n  BadgeStyle,\n  CometChatListProps,\n  DateStyle,\n  MenuItemInterface,\n  NewMessageIndicatorStyle,\n};"
  },
  {
    "path": "packages/ChatUiKit/src/theme/CometChatThemeHelper.ts",
    "content": "import { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { defaultColorDark, defaultSpacing } from \"./default\";\nimport { defaultColorLight } from \"./default/color/light\";\nimport { CometChatTheme } from \"./type\";\n\nexport enum Brightness {\n  LIGHT = 0,\n  DARK = 1,\n}\n\nenum Colors {\n  BLACK = \"#000000\",\n  WHITE = \"#FFFFFF\",\n}\n\nexport class CometChatThemeHelper {\n  /**\n   * Returns a list of color blending percentages based on the brightness setting.\n   *\n   * @param brightness - The brightness mode to determine the blending percentages.\n   *                      Use `Brightness.LIGHT` for light mode and `Brightness.DARK` for dark mode.\n   * @returns An array of blending percentages. For light mode, it provides percentages closer to white;\n   *          for dark mode, it provides percentages closer to black.\n   *\n   * @example\n   * ```typescript\n   * const lightPercentages = getBlendColorsPercentage(Brightness.LIGHT);\n   * console.log(lightPercentages); // Output: [0.96, 0.88, 0.77, 0.66, 0.55, 0.44, 0.33, 0.22, 0.11, 0.11]\n   * ```\n   */\n  public static getBlendColorsPercentage(brightness: Brightness): { [key: number]: number } {\n    return brightness === Brightness.LIGHT\n      ? {\n          50: 0.96,\n          100: 0.88,\n          200: 0.77,\n          300: 0.66,\n          400: 0.55,\n          500: 0.44,\n          600: 0.33,\n          700: 0.22,\n          800: 0.11,\n          900: 0.11,\n        }\n      : {\n          50: 0.8,\n          100: 0.72,\n          200: 0.64,\n          300: 0.56,\n          400: 0.48,\n          500: 0.4,\n          600: 0.32,\n          700: 0.24,\n          800: 0.16,\n          900: 0.08,\n        };\n  }\n\n  /**\n   * Converts a hexadecimal color code to its RGB components.\n   *\n   * @param hex - The color code in hexadecimal format (e.g., \"#FF0000\" for red).\n   * @returns An object containing the red, green, and blue components of the color.\n   *\n   * @example\n   * ```typescript\n   * const rgb = hexToRgb(\"#FF0000\");\n   * console.log(rgb); // Output: { r: 255, g: 0, b: 0 }\n   * ```\n   */\n  private static hexToRgb(hex: string): { r: number; g: number; b: number } {\n    let r = 0,\n      g = 0,\n      b = 0;\n    // 3 digits\n    if (hex.length === 4) {\n      r = parseInt(hex[1] + hex[1], 16);\n      g = parseInt(hex[2] + hex[2], 16);\n      b = parseInt(hex[3] + hex[3], 16);\n    }\n    // 6 digits\n    else if (hex.length === 7) {\n      r = parseInt(hex[1] + hex[2], 16);\n      g = parseInt(hex[3] + hex[4], 16);\n      b = parseInt(hex[5] + hex[6], 16);\n    }\n    return { r, g, b };\n  }\n\n  /**\n   * Converts RGB color components to a hexadecimal color code.\n   *\n   * @param r - The red component of the color (0-255).\n   * @param g - The green component of the color (0-255).\n   * @param b - The blue component of the color (0-255).\n   * @returns The color code in hexadecimal format.\n   *\n   * @example\n   * ```typescript\n   * const hex = rgbToHex(255, 0, 0);\n   * console.log(hex); // Output: \"#FF0000\"\n   * ```\n   */\n  private static rgbToHex(r: number, g: number, b: number): string {\n    return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;\n  }\n\n  /**\n   * Blends two colors together based on a specified percentage.\n   *\n   * @param baseColor - The base color in hexadecimal format (e.g., \"#FF0000\" for red).\n   * @param blendColor - The color to blend with the base color, also in hexadecimal format.\n   * @param percentage - The percentage of the `blendColor` to blend with the `baseColor`.\n   *                      It should be a value between 0 and 1, where 0 means only the `baseColor` is used and\n   *                      1 means only the `blendColor` is used.\n   * @returns The resulting blended color in hexadecimal format.\n   *\n   * @example\n   * ```typescript\n   * const brightness: Brightness = Brightness.LIGHT;\n   * const blendColorsPercentage = CometChatThemeHelper.getBlendColorsPercentage(brightness);\n   * const blendedColor = CometChatThemeHelper.blendColors(\"#6852D6\", \"#FFFFFF\", blendColorsPercentage[0]);\n   * ```\n   */\n  private static blendColors(baseColor: string, blendColor: string, percentage: number): string {\n    const { r: rBase, g: gBase, b: bBase } = this.hexToRgb(baseColor);\n    const { r: rBlend, g: gBlend, b: bBlend } = this.hexToRgb(blendColor);\n\n    const r = Math.round(rBase * (1 - percentage) + rBlend * percentage);\n    const g = Math.round(gBase * (1 - percentage) + gBlend * percentage);\n    const b = Math.round(bBase * (1 - percentage) + bBlend * percentage);\n\n    return this.rgbToHex(r, g, b);\n  }\n\n  public static updateColors(color: Partial<CometChatTheme[\"color\"]>, mode: Brightness) {\n    if (mode == Brightness.LIGHT) {\n      return CometChatThemeHelper.updateOtherColors(\n        CometChatThemeHelper.updateExtendedColorsForLightTheme(\n          color,\n          CometChatThemeHelper.getBlendColorsPercentage(mode)\n        ),\n        defaultColorLight\n      );\n    }\n    return CometChatThemeHelper.updateOtherColors(\n      CometChatThemeHelper.updateExtendedColorsForDarkTheme(\n        color,\n        CometChatThemeHelper.getBlendColorsPercentage(mode)\n      ),\n      defaultColorDark\n    );\n  }\n\n  private static updateOtherColors(\n    color: Partial<CometChatTheme[\"color\"]>,\n    defaultColor: Partial<CometChatTheme[\"color\"]>\n  ) {\n    color.neutral50 = color.neutral50 ?? defaultColor.neutral50;\n    color.neutral100 = color.neutral100 ?? defaultColor.neutral100;\n    color.neutral200 = color.neutral200 ?? defaultColor.neutral200;\n    color.neutral300 = color.neutral300 ?? defaultColor.neutral300;\n    color.neutral400 = color.neutral400 ?? defaultColor.neutral400;\n    color.neutral500 = color.neutral500 ?? defaultColor.neutral500;\n    color.neutral600 = color.neutral600 ?? defaultColor.neutral600;\n    color.neutral700 = color.neutral700 ?? defaultColor.neutral700;\n    color.neutral800 = color.neutral800 ?? defaultColor.neutral800;\n    color.neutral900 = color.neutral900 ?? defaultColor.neutral900;\n\n    color.staticBlack = color.staticBlack ?? defaultColor.staticBlack;\n    color.staticWhite = color.staticWhite ?? defaultColor.staticWhite;\n\n    color.info = color.info ?? defaultColor.info;\n    color.warning = color.warning ?? defaultColor.warning;\n    color.success = color.success ?? defaultColor.success;\n    color.error = color.error ?? defaultColor.error;\n\n    /***background colors***/\n    color.background1 = color.background1 === undefined ? color.neutral50 : color.background1;\n    color.background2 = color.background2 === undefined ? color.neutral100 : color.background2;\n    color.background3 = color.background3 === undefined ? color.neutral200 : color.background3;\n    color.background4 = color.background4 === undefined ? color.neutral300 : color.background4;\n\n    /***border colors***/\n    color.borderLight = color.borderLight === undefined ? color.neutral200 : color.borderLight;\n    color.borderDefault =\n      color.borderDefault === undefined ? color.neutral300 : color.borderDefault;\n    color.borderDark = color.borderDark === undefined ? color.neutral400 : color.borderDark;\n    color.borderHighlight =\n      color.borderHighlight === undefined ? color.primary : color.borderHighlight;\n\n    /***text colors***/\n    color.textPrimary = color.textPrimary === undefined ? color.neutral900 : color.textPrimary;\n    color.textSecondary =\n      color.textSecondary === undefined ? color.neutral600 : color.textSecondary;\n    color.textTertiary = color.textTertiary === undefined ? color.neutral500 : color.textTertiary;\n    color.textDisabled = color.textDisabled === undefined ? color.neutral400 : color.textDisabled;\n    color.textWhite = color.textWhite === undefined ? color.neutral50 : color.textWhite;\n    color.textHighlight = color.textHighlight === undefined ? color.primary : color.textHighlight;\n\n    /***icon colors***/\n    color.iconPrimary = color.iconPrimary === undefined ? color.neutral900 : color.iconPrimary;\n    color.iconSecondary =\n      color.iconSecondary === undefined ? color.neutral500 : color.iconSecondary;\n    color.iconTertiary = color.iconTertiary === undefined ? color.neutral400 : color.iconTertiary;\n    color.iconWhite = color.iconWhite === undefined ? color.neutral50 : color.iconWhite;\n    color.iconHighlight = color.iconHighlight === undefined ? color.primary : color.iconHighlight;\n\n    /***button colors***/\n    color.primaryButtonBackground =\n      color.primaryButtonBackground === undefined ? color.primary : color.primaryButtonBackground;\n    color.primaryButtonIcon =\n      color.primaryButtonIcon === undefined ? color.staticWhite : color.primaryButtonIcon;\n    color.primaryButtonText =\n      color.primaryButtonText === undefined ? color.staticWhite : color.primaryButtonText;\n    color.secondaryButtonBackground =\n      color.secondaryButtonBackground === undefined\n        ? color.neutral900\n        : color.secondaryButtonBackground;\n    color.secondaryButtonIcon =\n      color.secondaryButtonIcon === undefined ? color.neutral900 : color.secondaryButtonIcon;\n    color.secondaryButtonText =\n      color.secondaryButtonText === undefined ? color.neutral900 : color.secondaryButtonText;\n\n    /***other colors***/\n    color.linkBackground = color.linkBackground === undefined ? color.info : color.linkBackground;\n    color.fabButtonBackground =\n      color.fabButtonBackground === undefined ? color.primary : color.fabButtonBackground;\n    color.fabButtonIcon =\n      color.fabButtonIcon === undefined ? color.staticWhite : color.fabButtonIcon;\n\n    color.whiteHover = color.whiteHover === undefined ? color.neutral100 : color.whiteHover;\n    color.whitePressed = color.whitePressed === undefined ? color.neutral300 : color.whitePressed;\n\n    /***send bubble colors***/\n    color.sendBubbleBackground =\n      color.sendBubbleBackground === undefined ? color.primary : color.sendBubbleBackground;\n    color.sendBubbleText =\n      color.sendBubbleText === undefined ? color.staticWhite : color.sendBubbleText;\n    color.sendBubbleTextHighlight =\n      color.sendBubbleTextHighlight === undefined\n        ? color.staticWhite\n        : color.sendBubbleTextHighlight;\n    color.sendBubbleLink =\n      color.sendBubbleLink === undefined ? color.staticWhite : color.sendBubbleLink;\n    color.sendBubbleTimestamp =\n      color.sendBubbleTimestamp === undefined ? color.staticWhite : color.sendBubbleTimestamp;\n    color.sendBubbleIcon =\n      color.sendBubbleIcon === undefined ? color.staticWhite : color.sendBubbleIcon;\n\n    /***receive bubble colors***/\n    color.receiveBubbleBackground =\n      color.receiveBubbleBackground === undefined\n        ? color.neutral300\n        : color.receiveBubbleBackground;\n    color.receiveBubbleText =\n      color.receiveBubbleText === undefined ? color.neutral900 : color.receiveBubbleText;\n    color.receiveBubbleTextHighlight =\n      color.receiveBubbleTextHighlight === undefined\n        ? color.primary\n        : color.receiveBubbleTextHighlight;\n    color.receiveBubbleLink =\n      color.receiveBubbleLink === undefined ? color.info : color.receiveBubbleLink;\n    color.receiveBubbleTimestamp =\n      color.receiveBubbleTimestamp === undefined ? color.neutral600 : color.receiveBubbleTimestamp;\n    color.receiveBubbleIcon =\n      color.receiveBubbleIcon === undefined ? color.primary : color.receiveBubbleIcon;\n\n    color.previewInlineCodeBackground =\n      color.previewInlineCodeBackground === undefined ? defaultColor.previewInlineCodeBackground : color.previewInlineCodeBackground;\n\n    color.previewCodeBlockBackground =\n      color.previewCodeBlockBackground === undefined ? defaultColor.previewCodeBlockBackground : color.previewCodeBlockBackground;\n\n    color.previewCodeBlockBorder =\n      color.previewCodeBlockBorder === undefined ? defaultColor.previewCodeBlockBorder : color.previewCodeBlockBorder;\n\n    return color;\n  }\n\n  private static updateExtendedColorsForLightTheme(\n    color: Partial<CometChatTheme[\"color\"]>,\n    blendColorsPercentage: { [key: number]: number }\n  ) {\n    color.primary = color.primary ?? defaultColorLight.primary;\n    color.extendedPrimary50 =\n      color.extendedPrimary50 === undefined\n        ? CometChatThemeHelper.blendColors(\n            color.primary as string,\n            Colors.WHITE,\n            blendColorsPercentage[50]\n          )\n        : color.extendedPrimary50;\n    for (let i = 100; i < 900; i += 100) {\n      // Ensure the blend percentage is defined in your blendColorsPercentage object\n      const percentage = blendColorsPercentage[i] || 0; // Default to 0 if not defined\n      color[`extendedPrimary${i}` as keyof typeof color] =\n        color[`extendedPrimary${i}` as keyof typeof color] === undefined\n          ? CometChatThemeHelper.blendColors(color.primary as string, Colors.WHITE, percentage)\n          : color[`extendedPrimary${i}` as keyof typeof color];\n\n      color.extendedPrimary900 =\n        color.extendedPrimary900 === undefined\n          ? CometChatThemeHelper.blendColors(\n              color.primary as string,\n              Colors.BLACK,\n              blendColorsPercentage[900]\n            )\n          : color.extendedPrimary900;\n    }\n    return color;\n  }\n\n  private static updateExtendedColorsForDarkTheme(\n    color: Partial<CometChatTheme[\"color\"]>,\n    blendColorsPercentage: { [key: number]: number }\n  ) {\n    color.primary = color.primary ?? defaultColorDark.primary;\n    color.extendedPrimary50 =\n      color.extendedPrimary50 === undefined\n        ? CometChatThemeHelper.blendColors(\n            color.primary as string,\n            Colors.BLACK,\n            blendColorsPercentage[50]\n          )\n        : color.extendedPrimary50;\n    for (let i = 100; i < 900; i += 100) {\n      // Ensure the blend percentage is defined in your blendColorsPercentage object\n      const percentage = blendColorsPercentage[i] || 0; // Default to 0 if not defined\n      color[`extendedPrimary${i}` as keyof typeof color] =\n        color[`extendedPrimary${i}` as keyof typeof color] === undefined\n          ? CometChatThemeHelper.blendColors(color.primary as string, Colors.BLACK, percentage)\n          : color[`extendedPrimary${i}` as keyof typeof color];\n      color.extendedPrimary900 =\n        color.extendedPrimary900 === undefined\n          ? CometChatThemeHelper.blendColors(\n              color.primary as string,\n              Colors.WHITE,\n              blendColorsPercentage[900]\n            )\n          : color.extendedPrimary900;\n    }\n    return color;\n  }\n\n  public static updateSpacing(\n    spacing: DeepPartial<CometChatTheme[\"spacing\"]>\n  ): CometChatTheme[\"spacing\"] {\n    if (!spacing.spacing) {\n      spacing.spacing = {} as any;\n    }\n\n    spacing.spacing!.s0 =\n      spacing.spacing!.s0 !== undefined ? spacing.spacing?.s0 : defaultSpacing.spacing?.s0;\n    spacing.spacing!.s1 =\n      spacing.spacing!.s1 !== undefined ? spacing.spacing?.s1 : defaultSpacing.spacing?.s1;\n    spacing.spacing!.s2 =\n      spacing.spacing!.s2 !== undefined ? spacing.spacing?.s2 : defaultSpacing.spacing?.s2;\n    spacing.spacing!.s3 =\n      spacing.spacing!.s3 !== undefined ? spacing.spacing?.s3 : defaultSpacing.spacing?.s3;\n    spacing.spacing!.s4 =\n      spacing.spacing!.s4 !== undefined ? spacing.spacing?.s4 : defaultSpacing.spacing?.s4;\n    spacing.spacing!.s5 =\n      spacing.spacing!.s5 !== undefined ? spacing.spacing?.s5 : defaultSpacing.spacing?.s5;\n    spacing.spacing!.s6 =\n      spacing.spacing!.s6 !== undefined ? spacing.spacing?.s6 : defaultSpacing.spacing?.s6;\n    spacing.spacing!.s7 =\n      spacing.spacing!.s7 !== undefined ? spacing.spacing?.s7 : defaultSpacing.spacing?.s7;\n    spacing.spacing!.s8 =\n      spacing.spacing!.s8 !== undefined ? spacing.spacing?.s8 : defaultSpacing.spacing?.s8;\n    spacing.spacing!.s9 =\n      spacing.spacing!.s9 !== undefined ? spacing.spacing?.s9 : defaultSpacing.spacing?.s9;\n    spacing.spacing!.s10 =\n      spacing.spacing!.s10 !== undefined ? spacing.spacing?.s10 : defaultSpacing.spacing?.s10;\n    spacing.spacing!.s11 =\n      spacing.spacing!.s11 !== undefined ? spacing.spacing?.s11 : defaultSpacing.spacing?.s11;\n    spacing.spacing!.s12 =\n      spacing.spacing!.s12 !== undefined ? spacing.spacing?.s12 : defaultSpacing.spacing?.s12;\n    spacing.spacing!.s13 =\n      spacing.spacing!.s13 !== undefined ? spacing.spacing?.s13 : defaultSpacing.spacing?.s13;\n    spacing.spacing!.s14 =\n      spacing.spacing!.s14 !== undefined ? spacing.spacing?.s14 : defaultSpacing.spacing?.s14;\n    spacing.spacing!.s15 =\n      spacing.spacing!.s15 !== undefined ? spacing.spacing?.s15 : defaultSpacing.spacing?.s15;\n    spacing.spacing!.s16 =\n      spacing.spacing!.s16 !== undefined ? spacing.spacing?.s16 : defaultSpacing.spacing?.s16;\n    spacing.spacing!.s17 =\n      spacing.spacing!.s17 !== undefined ? spacing.spacing?.s17 : defaultSpacing.spacing?.s17;\n    spacing.spacing!.s18 =\n      spacing.spacing!.s18 !== undefined ? spacing.spacing?.s18 : defaultSpacing.spacing?.s18;\n    spacing.spacing!.s19 =\n      spacing.spacing!.s19 !== undefined ? spacing.spacing?.s19 : defaultSpacing.spacing?.s19;\n    spacing.spacing!.s20 =\n      spacing.spacing!.s20 !== undefined ? spacing.spacing?.s20 : defaultSpacing.spacing?.s20;\n    spacing.spacing!.max =\n      spacing.spacing!.max !== undefined ? spacing.spacing?.max : defaultSpacing.spacing?.max;\n\n    if (!spacing.padding) {\n      spacing.padding = {} as any;\n    }\n    if (!spacing.margin) {\n      spacing.margin = {} as any;\n    }\n    if (!spacing.radius) {\n      spacing.radius = {} as any;\n    }\n    /**padding**/\n    Object.keys(defaultSpacing.padding).forEach((key) => {\n      const k = key as string;\n      const paddingKey = k as keyof typeof spacing.padding;\n      const spacingKey = k.replace(\"p\", \"s\") as keyof typeof spacing.spacing;\n\n      spacing.padding![paddingKey] =\n        spacing.padding![paddingKey] === undefined\n          ? spacing.spacing![spacingKey]\n          : spacing.padding![paddingKey];\n    });\n\n    /**margin**/\n    Object.keys(defaultSpacing.margin).forEach((key) => {\n      const k = key as string;\n      const marginKey = k as keyof typeof spacing.margin;\n      const spacingKey = k.replace(\"m\", \"s\") as keyof typeof spacing.spacing;\n\n      spacing.margin![marginKey] = (\n        spacing.margin?.[marginKey] === undefined\n          ? spacing.spacing?.[spacingKey]\n          : spacing.margin?.[marginKey]\n      )!;\n    });\n\n    /**radius**/\n    Object.keys(defaultSpacing.radius).forEach((key) => {\n      const k = key as string;\n      const radiusKey = k as keyof typeof spacing.radius;\n      const spacingKey = k.replace(\"r\", \"s\") as keyof typeof spacing.spacing;\n\n      spacing.radius![radiusKey] =\n        (spacing.radius?.[radiusKey] === undefined\n          ? spacing.spacing?.[spacingKey]\n          : spacing.radius?.[radiusKey])!;\n    });\n\n    return spacing as CometChatTheme[\"spacing\"];\n  }\n}\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/context.ts",
    "content": "import { createContext } from \"react\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { defaultDarkTheme, defaultLightTheme } from \"./default/default\";\nimport { CometChatTheme } from \"./type\";\n\nexport interface ThemeProviderValue {\n  dark: DeepPartial<CometChatTheme>;\n  light: DeepPartial<CometChatTheme>;\n  mode: \"dark\" | \"light\" | \"auto\";\n}\n\nexport const themeProviderDefaultValue: ThemeProviderValue = {\n  dark: defaultDarkTheme,\n  light: defaultLightTheme,\n  mode: \"auto\",\n};\n\nexport const ThemeContext = createContext<ThemeProviderValue>(themeProviderDefaultValue);\n\nexport const CompThemeContext = createContext<DeepPartial<ThemeProviderValue[\"light\"]>>({} as any);\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/color/dark.ts",
    "content": "import { Color } from \"./light\";\n\nconst primaryColor = \"#6852D6\";\n\nconst extendedPrimaryColors = {\n  extendedPrimary50: \"#15102B\",\n  extendedPrimary100: \"#1D173C\",\n  extendedPrimary200: \"#251E4D\",\n  extendedPrimary300: \"#2E245E\",\n  extendedPrimary400: \"#362B6F\",\n  extendedPrimary500: \"#3E3180\",\n  extendedPrimary600: \"#473892\",\n  extendedPrimary700: \"#4F3EA3\",\n  extendedPrimary800: \"#5745B4\",\n  extendedPrimary900: \"#7460D9\",\n};\n\nconst neutralColors = {\n  neutral50: \"#141414\",\n  neutral100: \"#1A1A1A\",\n  neutral200: \"#272727\",\n  neutral300: \"#383838\",\n  neutral400: \"#4C4C4C\",\n  neutral500: \"#858585\",\n  neutral600: \"#989898\",\n  neutral700: \"#A8A8A8\",\n  neutral800: \"#C8C8C8\",\n  neutral900: \"#FFFFFF\",\n};\n\nconst alertColors = {\n  info: \"#0D66BF\",\n  warning: \"#D08D04\",\n  success: \"#0B9F5D\",\n  error: \"#C73C3E\",\n};\n\nconst staticColors = {\n  staticBlack: \"#141414\",\n  staticWhite: \"#FFFFFF\",\n};\n\nconst overlayColors = {\n  overlayLight: neutralColors.neutral300,\n  overlayCodeBlock: \"rgba(255, 255, 255, 0.20)\",\n  overlayCodeBlockBorder: \"rgba(255, 255, 255, 0.30)\",\n};\n\nexport const defaultColorDark: Color = {\n  primary: primaryColor,\n  ...extendedPrimaryColors,\n  ...neutralColors,\n  ...alertColors,\n  ...staticColors,\n\n  background1: neutralColors.neutral50,\n  background2: neutralColors.neutral100,\n  background3: neutralColors.neutral200,\n  background4: neutralColors.neutral300,\n\n  borderLight: neutralColors.neutral200,\n  borderDefault: neutralColors.neutral300,\n  borderDark: neutralColors.neutral400,\n  borderHighlight: primaryColor,\n\n  textPrimary: neutralColors.neutral900,\n  textSecondary: neutralColors.neutral600,\n  textTertiary: neutralColors.neutral500,\n  textDisabled: neutralColors.neutral400,\n  textWhite: neutralColors.neutral50,\n  textHighlight: primaryColor,\n\n  iconPrimary: neutralColors.neutral900,\n  iconSecondary: neutralColors.neutral500,\n  iconTertiary: neutralColors.neutral400,\n  iconWhite: neutralColors.neutral50,\n  iconHighlight: primaryColor,\n\n  primaryButtonBackground: primaryColor,\n  primaryButtonIcon: staticColors.staticWhite,\n  primaryButtonText: staticColors.staticWhite,\n  secondaryButtonBackground: neutralColors.neutral900,\n  secondaryButtonIcon: neutralColors.neutral900,\n  secondaryButtonText: neutralColors.neutral900,\n  linkBackground: alertColors.info,\n  fabButtonBackground: primaryColor,\n  fabButtonIcon: staticColors.staticWhite,\n\n  whiteHover: neutralColors.neutral100,\n  whitePressed: neutralColors.neutral300,\n\n  sendBubbleBackground: primaryColor,\n  sendBubbleText: staticColors.staticWhite,\n  sendBubbleTextHighlight: staticColors.staticWhite,\n  sendBubbleLink: staticColors.staticWhite,\n  sendBubbleTimestamp: staticColors.staticWhite,\n  sendBubbleIcon: staticColors.staticWhite,\n\n  receiveBubbleBackground: neutralColors.neutral300,\n  receiveBubbleText: neutralColors.neutral900,\n  receiveBubbleTextHighlight: primaryColor,\n  receiveBubbleLink: alertColors.info,\n  receiveBubbleTimestamp: neutralColors.neutral600,\n  receiveBubbleIcon: primaryColor,\n\n  // Reply preview inline code background\n  previewInlineCodeBackground: overlayColors.overlayLight,\n  // Reply preview code block background\n  previewCodeBlockBackground: overlayColors.overlayCodeBlock,\n  // Reply preview code block border\n  previewCodeBlockBorder: overlayColors.overlayCodeBlockBorder,\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/color/light.ts",
    "content": "import { ColorValue } from \"react-native\";\n\nconst primaryColor = \"#6852D6\";\n\nconst extendedPrimaryColors = {\n  extendedPrimary50: \"#F9F8FD\",\n  extendedPrimary100: \"#EDEAFA\",\n  extendedPrimary200: \"#DCD7F6\",\n  extendedPrimary300: \"#CCC4F1\",\n  extendedPrimary400: \"#BBB1ED\",\n  extendedPrimary500: \"#AA9EE8\",\n  extendedPrimary600: \"#9A8BE4\",\n  extendedPrimary700: \"#8978DF\",\n  extendedPrimary800: \"#7965DB\",\n  extendedPrimary900: \"#5D49BE\",\n};\n\nconst neutralColors = {\n  neutral50: \"#FFFFFF\",\n  neutral100: \"#FAFAFA\",\n  neutral200: \"#F5F5F5\",\n  neutral300: \"#E8E8E8\",\n  neutral400: \"#DCDCDC\",\n  neutral500: \"#A1A1A1\",\n  neutral600: \"#727272\",\n  neutral700: \"#5B5B5B\",\n  neutral800: \"#434343\",\n  neutral900: \"#141414\",\n};\n\nconst alertColors = {\n  info: \"#0B7BEA\",\n  warning: \"#FFAB00\",\n  success: \"#09C26F\",\n  error: \"#F44649\",\n};\n\nconst staticColors = {\n  staticBlack: \"#141414\",\n  staticWhite: \"#FFFFFF\",\n};\n\nconst overlayColors = {\n  overlayLight: \"rgba(120, 120, 128, 0.70)\",\n  overlayCodeBlock: \"rgba(255, 255, 255, 0.20)\",\n  overlayCodeBlockBorder: neutralColors.neutral300,\n};\n\nexport const defaultColorLight = {\n  primary: primaryColor,\n  ...extendedPrimaryColors,\n  ...neutralColors,\n  ...alertColors,\n  ...staticColors,\n\n  background1: neutralColors.neutral50,\n  background2: neutralColors.neutral100,\n  background3: neutralColors.neutral200,\n  background4: neutralColors.neutral300,\n\n  borderLight: neutralColors.neutral200,\n  borderDefault: neutralColors.neutral300,\n  borderDark: neutralColors.neutral400,\n  borderHighlight: primaryColor,\n\n  textPrimary: neutralColors.neutral900,\n  textSecondary: neutralColors.neutral600,\n  textTertiary: neutralColors.neutral500,\n  textDisabled: neutralColors.neutral400,\n  textWhite: neutralColors.neutral50,\n  textHighlight: primaryColor,\n\n  iconPrimary: neutralColors.neutral900,\n  iconSecondary: neutralColors.neutral500,\n  iconTertiary: neutralColors.neutral400,\n  iconWhite: neutralColors.neutral50,\n  iconHighlight: primaryColor,\n\n  primaryButtonBackground: primaryColor,\n  primaryButtonIcon: staticColors.staticWhite,\n  primaryButtonText: staticColors.staticWhite,\n  secondaryButtonBackground: neutralColors.neutral900,\n  secondaryButtonIcon: neutralColors.neutral900,\n  secondaryButtonText: neutralColors.neutral900,\n  linkBackground: alertColors.info,\n  fabButtonBackground: primaryColor,\n  fabButtonIcon: staticColors.staticWhite,\n\n  whiteHover: neutralColors.neutral100,\n  whitePressed: neutralColors.neutral300,\n\n  sendBubbleBackground: primaryColor,\n  sendBubbleText: staticColors.staticWhite,\n  sendBubbleTextHighlight: staticColors.staticWhite,\n  sendBubbleLink: staticColors.staticWhite,\n  sendBubbleTimestamp: staticColors.staticWhite,\n  sendBubbleIcon: staticColors.staticWhite,\n\n  receiveBubbleBackground: neutralColors.neutral300,\n  receiveBubbleText: neutralColors.neutral900,\n  receiveBubbleTextHighlight: primaryColor,\n  receiveBubbleLink: alertColors.info,\n  receiveBubbleTimestamp: neutralColors.neutral600,\n  receiveBubbleIcon: primaryColor,\n\n  // Reply preview inline code background\n  previewInlineCodeBackground: overlayColors.overlayLight,\n  // Reply preview code block background\n  previewCodeBlockBackground: overlayColors.overlayCodeBlock,\n  // Reply preview code block border\n  previewCodeBlockBorder: overlayColors.overlayCodeBlockBorder,\n};\ntype EachColorValue<T extends typeof defaultColorLight> = {\n  [P in keyof T]: ColorValue;\n};\n\nexport type Color = EachColorValue<typeof defaultColorLight>;\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/default.ts",
    "content": "import { getCallButtonStyle } from \"../../calls/CometChatCallButtons/style\";\nimport { getCallLogsItemStyle, getCallLogsStyleLight, getCallLogsStyleDark } from \"../../calls/CometChatCallLogs/style\";\nimport { getIncomingCallStyle } from \"../../calls/CometChatIncomingCall/style\";\nimport { getOutgoingCallStyle } from \"../../calls/CometChatOutgoingCall\";\nimport {\n  getConversationStyleDark,\n  getConversationStyleLight,\n} from \"../../CometChatConversations/style\";\nimport {\n  getGroupMemberStyleDark,\n  getGroupMemberStyleLight,\n} from \"../../CometChatGroupMembers/style\";\nimport { getGroupListStyleLight, getUserGroupStyleDark } from \"../../CometChatGroups/GroupsStyle\";\nimport { getComposerStyle } from \"../../CometChatMessageComposer/styles\";\nimport { getMessageHeaderStyle } from \"../../CometChatMessageHeader/styles\";\nimport {\n  getMessageInormationListStyleDark,\n  getMessageInormationListStyleLight,\n} from \"../../CometChatMessageInformation/styles\";\nimport {\n  getMessageListStylesDark,\n  getMessageListStylesLight,\n} from \"../../CometChatMessageList/style\";\nimport {\n  getThreadHeaderStyleDark,\n  getThreadHeaderStyleLight,\n} from \"../../CometChatThreadHeader/style\";\nimport { getUserListStyleDark, getUserListStyleLight } from \"../../CometChatUsers/style\";\nimport { getAvatarStyle } from \"../../shared/views/CometChatAvatar/styles\";\nimport { getBadgeStyle } from \"../../shared/views/CometChatBadge\";\nimport {\n  getConfirmDialogStyleDark,\n  getConfirmDialogStyleLight,\n} from \"../../shared/views/CometChatConfirmDialog/style\";\nimport { getDateStyleDark, getDateStyleLight } from \"../../shared/views/CometChatDate\";\nimport {\n  getQuickReactionStyleDark,\n  getQuickReactionStyleLight,\n} from \"../../shared/views/CometChatQuickReactions/QuickReactionsStyle\";\nimport {\n  getReactionListStyleDark,\n  getReactionListStyleLight,\n} from \"../../shared/views/CometChatReactionList/ReactionListStyle\";\nimport { getMessageReceiptStyle } from \"../../shared/views/CometChatReceipt/style\";\nimport { getStatusIndicatorStyles } from \"../../shared/views/CometChatStatusIndicator/styles\";\nimport {\n  getMentionsListStyleDark,\n  getMentionsListStyleLight,\n} from \"../../shared/views/CometChatSuggestionList/style\";\nimport { ActionSheetStyle, CometChatTheme } from \"../type\";\nimport { defaultColorDark } from \"./color/dark\";\nimport { defaultColorLight } from \"./color/light\";\nimport { defaultSpacing } from \"./spacing\";\nimport { defaultTypography } from \"./typography\";\nimport { DeepPartial } from \"../../shared/helper/types\";\nimport { getMediaRecorderStyle } from \"../../shared/views/CometChatMediaRecorder/style\";\nimport { getDateSeparatorStyleDark, getDateSeparatorStyleLight } from \"../../shared/views/CometChatDateSeperator\";\nimport { getReportDialogStyleDark, getReportDialogStyleLight } from \"../../shared/views/CometChatReportDialog/style\";\n\nconst getActionSheetStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ActionSheetStyle => {\n  return {\n    optionsItemStyle: {\n      containerStyle: {\n        paddingVertical: spacing.padding.p3,\n        paddingHorizontal: spacing.padding.p8,\n        backgroundColor: color.background1,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 5,\n      },\n      titleStyle: {\n        ...typography.body.regular,\n        color: color.textPrimary,\n      },\n      iconStyle: {\n        height: 24,\n        width: 24,\n      },\n      iconContainerStyle: {},\n    },\n  };\n};\n\nconst getAIOptionsStyle = (\n  color: CometChatTheme[\"color\"],\n  spacing: CometChatTheme[\"spacing\"],\n  typography: CometChatTheme[\"typography\"]\n): ActionSheetStyle => {\n  return {\n    optionsItemStyle: {\n      containerStyle: {\n        padding: spacing.padding.p4,\n        backgroundColor: color.background1,\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        gap: 5,\n      },\n      titleStyle: {\n        ...typography.body.regular,\n        color: color.textPrimary,\n        paddingLeft: spacing.padding.p1,\n      },\n      iconStyle: {\n        height: 24,\n        width: 24,\n      },\n      iconContainerStyle: {},\n    },\n  };\n};\n\nexport const lightThemeMaker = (\n  spacing: CometChatTheme[\"spacing\"],\n  color: CometChatTheme[\"color\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CometChatTheme> => {\n  return {\n    mode:'light',\n    spacing: spacing,\n    color: color,\n    typography: typography,\n    avatarStyle: getAvatarStyle(color, spacing, typography),\n    receiptStyles: getMessageReceiptStyle(color, spacing, typography),\n    statusIndicatorStyle: getStatusIndicatorStyles(color, spacing, typography),\n    badgeStyle: getBadgeStyle(color, spacing, typography),\n    dateStyles: getDateStyleLight(color, spacing, typography),\n    dateSeparatorStyles: getDateSeparatorStyleLight(color, spacing, typography),\n    conversationStyles: getConversationStyleLight(color, spacing, typography),\n    confirmDialogStyles: getConfirmDialogStyleLight(color, spacing, typography),\n    reportDialogStyles: getReportDialogStyleLight(color, spacing, typography),\n    messageHeaderStyles: getMessageHeaderStyle(color, spacing, typography),\n    messageComposerStyles: getComposerStyle(color, spacing, typography),\n    userStyles: getUserListStyleLight(color, spacing, typography),\n    groupStyles: getGroupListStyleLight(color, spacing, typography),\n    groupMemberStyle: getGroupMemberStyleLight(color, spacing, typography),\n    callButtonStyles: getCallButtonStyle(color, spacing, typography),\n    messageListStyles: getMessageListStylesLight(color, spacing, typography),\n    reactionListStyles: getReactionListStyleLight(color, spacing, typography),\n    messageInformationStyles: getMessageInormationListStyleLight(color, spacing, typography),\n    actionSheetStyle: getActionSheetStyle(color, spacing, typography),\n    mentionsListStyle: getMentionsListStyleLight(color, spacing, typography),\n    threadHeaderStyles: getThreadHeaderStyleLight(color, spacing, typography),\n    aiOptionsStyle: getAIOptionsStyle(color, spacing, typography),\n    quickReactionStyle: getQuickReactionStyleLight(color, spacing, typography),\n    outgoingCallStyle: getOutgoingCallStyle(color, spacing, typography),\n    incomingCallStyle: getIncomingCallStyle(color, spacing, typography),\n    callLogsStyles: getCallLogsStyleLight(color, spacing, typography),\n    callLogsItemStyle: getCallLogsItemStyle(color, spacing, typography),\n    mediaRecorderStyle: getMediaRecorderStyle(color, spacing, typography),\n  };\n};\n\nexport const darkThemeMaker = (\n  spacing: CometChatTheme[\"spacing\"],\n  color: CometChatTheme[\"color\"],\n  typography: CometChatTheme[\"typography\"]\n): DeepPartial<CometChatTheme> => {\n  return {\n    mode:'dark',\n    spacing: spacing,\n    color: color,\n    typography: typography,\n    avatarStyle: getAvatarStyle(color, spacing, typography),\n    receiptStyles: getMessageReceiptStyle(color, spacing, typography),\n    statusIndicatorStyle: getStatusIndicatorStyles(color, spacing, typography),\n    badgeStyle: getBadgeStyle(color, spacing, typography),\n    dateStyles: getDateStyleDark(color, spacing, typography),\n    dateSeparatorStyles: getDateSeparatorStyleDark(color, spacing, typography),\n    conversationStyles: getConversationStyleDark(color, spacing, typography),\n    confirmDialogStyles: getConfirmDialogStyleDark(color, spacing, typography),\n    reportDialogStyles: getReportDialogStyleDark(color, spacing, typography),\n    messageHeaderStyles: getMessageHeaderStyle(color, spacing, typography),\n    messageComposerStyles: getComposerStyle(color, spacing, typography),\n    userStyles: getUserListStyleDark(color, spacing, typography),\n    groupMemberStyle: getGroupMemberStyleDark(color, spacing, typography),\n    groupStyles: getUserGroupStyleDark(color, spacing, typography),\n    callButtonStyles: getCallButtonStyle(color, spacing, typography),\n    messageListStyles: getMessageListStylesDark(color, spacing, typography),\n    reactionListStyles: getReactionListStyleDark(color, spacing, typography),\n    messageInformationStyles: getMessageInormationListStyleDark(color, spacing, typography),\n    actionSheetStyle: getActionSheetStyle(color, spacing, typography),\n    mentionsListStyle: getMentionsListStyleDark(color, spacing, typography),\n    threadHeaderStyles: getThreadHeaderStyleDark(color, spacing, typography),\n    aiOptionsStyle: getAIOptionsStyle(color, spacing, typography),\n    quickReactionStyle: getQuickReactionStyleDark(color, spacing, typography),\n    outgoingCallStyle: getOutgoingCallStyle(color, spacing, typography),\n    incomingCallStyle: getIncomingCallStyle(color, spacing, typography),\n    callLogsStyles: getCallLogsStyleDark(color, spacing, typography),\n    callLogsItemStyle: getCallLogsItemStyle(color, spacing, typography),\n    mediaRecorderStyle: getMediaRecorderStyle(color, spacing, typography),\n  };\n};\n\nexport const defaultLightTheme: DeepPartial<CometChatTheme> = lightThemeMaker(\n  defaultSpacing,\n  defaultColorLight,\n  defaultTypography\n);\nexport const defaultDarkTheme: DeepPartial<CometChatTheme> = darkThemeMaker(\n  defaultSpacing,\n  defaultColorDark,\n  defaultTypography\n);\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/index.ts",
    "content": "export * from \"./color/dark\";\nexport * from \"./color/light\";\nexport * from \"./spacing\";\nexport * from \"./typography\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/resources/icons/index.ts",
    "content": "import THREAD_REPLIES from \"./thread_replies.png\";\n\nimport COLLAB_DOCUMENT_1X from \"./collaborative_document_1x.png\";\nimport COLLAB_DOCUMENT_2X from \"./collaborative_document_2x.png\";\nimport COLLAB_DOCUMENT_3X from \"./collaborative_document_3x.png\";\n\nimport COLLAB_WHITEBOARD_1X from \"./collaborative_whiteboard_1x.png\";\nimport COLLAB_WHITEBOARD_2X from \"./collaborative_whiteboard_2x.png\";\nimport COLLAB_WHITEBOARD_3X from \"./collaborative_whiteboard_3x.png\";\n\nexport const DEFAULT_ICONS = {\n  THREAD_REPLIES,\n  COLLAB_DOCUMENT_1X,\n  COLLAB_DOCUMENT_2X,\n  COLLAB_DOCUMENT_3X,\n  COLLAB_WHITEBOARD_1X,\n  COLLAB_WHITEBOARD_2X,\n  COLLAB_WHITEBOARD_3X\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/spacing.ts",
    "content": "const spacing = {\n  s0: 0,\n  s0_5: 2,\n  s1: 4,\n  s2: 8,\n  s3: 12,\n  s4: 16,\n  s5: 20,\n  s6: 24,\n  s7: 28,\n  s8: 32,\n  s9: 36,\n  s10: 40,\n  s11: 44,\n  s12: 48,\n  s13: 52,\n  s14: 56,\n  s15: 60,\n  s16: 64,\n  s17: 68,\n  s18: 72,\n  s19: 76,\n  s20: 80,\n  max: 1000,\n};\n\nconst padding = {\n  p0: spacing.s0,\n  p0_5: spacing.s0_5,\n  p1: spacing.s1,\n  p2: spacing.s2,\n  p3: spacing.s3,\n  p4: spacing.s4,\n  p5: spacing.s5,\n  p6: spacing.s6,\n  p7: spacing.s7,\n  p8: spacing.s8,\n  p9: spacing.s9,\n  p10: spacing.s10,\n};\n\nconst margin = {\n  m0: spacing.s0,\n  m0_5: spacing.s0_5,\n  m1: spacing.s1,\n  m2: spacing.s2,\n  m3: spacing.s3,\n  m4: spacing.s4,\n  m5: spacing.s5,\n  m6: spacing.s6,\n  m7: spacing.s7,\n  m8: spacing.s8,\n  m9: spacing.s9,\n  m10: spacing.s10,\n  m11: spacing.s11,\n  m12: spacing.s12,\n  m13: spacing.s13,\n  m14: spacing.s14,\n  m15: spacing.s15,\n  m16: spacing.s16,\n  m17: spacing.s17,\n  m18: spacing.s18,\n  m19: spacing.s19,\n  m20: spacing.s20,\n  max: spacing.max,\n};\n\nconst radius = {\n  r0: spacing.s0,\n  r1: spacing.s1,\n  r2: spacing.s2,\n  r3: spacing.s3,\n  r4: spacing.s4,\n  r5: spacing.s5,\n  r6: spacing.s6,\n  max: spacing.max,\n};\n\nexport const defaultSpacing = {\n  spacing,\n  padding,\n  margin,\n  radius,\n};\n\nexport type Spacing = typeof defaultSpacing;\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/default/typography.ts",
    "content": "import { TextStyle } from \"react-native\";\n\ninterface TypographyVariant {\n  bold: TextStyle;\n  medium: TextStyle;\n  regular: TextStyle;\n}\n\nexport interface Typography {\n  fontFamily: TextStyle[\"fontFamily\"];\n  title: TypographyVariant;\n  heading1: TypographyVariant;\n  heading2: TypographyVariant;\n  heading3: TypographyVariant;\n  heading4: TypographyVariant;\n  body: TypographyVariant;\n  caption1: TypographyVariant;\n  caption2: TypographyVariant;\n  button: TypographyVariant;\n  link: TextStyle;\n}\n\nexport const defaultTypography = {\n  //ToDoM ad other fonts\n  fontFamily: \"Inter\",\n  title: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 32,\n      lineHeight: 38.4,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 32,\n      lineHeight: 38.4,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 32,\n      lineHeight: 38.4,\n    },\n  },\n  heading1: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 24,\n      // lineHeight: 28.8,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 24,\n      lineHeight: 28.8,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 24,\n      lineHeight: 28.8,\n    },\n  },\n  heading2: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 20,\n      lineHeight: 24,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 20,\n      // lineHeight: 24,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 20,\n      lineHeight: 24,\n    },\n  },\n  heading3: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 18,\n      lineHeight: 21.6,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 18,\n      lineHeight: 21.6,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 18,\n      lineHeight: 21.6,\n    },\n  },\n  heading4: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 16,\n      lineHeight: 20.8,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 16,\n      // lineHeight: 20.8,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 16,\n      lineHeight: 20.8,\n    },\n  },\n  body: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 14,\n      lineHeight: 19.6,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 14,\n      lineHeight: 19.6,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 14,\n      lineHeight: 19.6,\n    },\n  },\n  caption1: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 12,\n      lineHeight: 16.8,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 12,\n      lineHeight: 16.8,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 12,\n      lineHeight: 16.8,\n    },\n  },\n  caption2: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 10,\n      lineHeight: 14,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 10,\n      lineHeight: 14,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 10,\n      lineHeight: 14,\n    },\n  },\n  button: {\n    bold: {\n      fontWeight: \"700\",\n      fontSize: 14,\n      lineHeight: 16.8,\n    },\n    medium: {\n      fontWeight: \"500\",\n      fontSize: 14,\n      // lineHeight: 16.8,\n    },\n    regular: {\n      fontWeight: \"400\",\n      fontSize: 14,\n      lineHeight: 16.8,\n    },\n  },\n  link: {\n    fontWeight: \"400\",\n    fontSize: 14,\n    lineHeight: 16.8,\n  },\n} as const satisfies Typography;\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/hook.ts",
    "content": "import { useContext } from \"react\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { CompThemeContext, ThemeContext } from \"./context\";\nimport { CometChatTheme } from \"./type\";\n\nexport const useTheme = (): CometChatTheme => {\n  const theme = useContext(ThemeContext);\n  return theme.mode === \"dark\" ? theme.dark as CometChatTheme: theme.light as CometChatTheme;\n};\n\nexport const useThemeInternal = () => {\n  const theme = useContext(ThemeContext);\n  return theme;\n};\n\nexport const useCompTheme = (): DeepPartial<CometChatTheme> => {\n  const theme = useContext(CompThemeContext);\n  return theme;\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/index.ts",
    "content": "export * from \"./CometChatThemeHelper\";\nexport { useTheme, useThemeInternal } from \"./hook\";\nexport { CometChatThemeProvider } from \"./provider\";\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/provider.tsx",
    "content": "import {\n  PropsWithChildren,\n  useMemo,\n  useState,\n  useEffect,\n} from \"react\";\nimport { useColorScheme, Platform } from \"react-native\";\nimport { deepMerge } from \"../shared/helper/helperFunctions\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { Brightness, CometChatThemeHelper } from \"./CometChatThemeHelper\";\nimport {\n  CompThemeContext,\n  ThemeContext,\n  ThemeProviderValue,\n} from \"./context\";\nimport { darkThemeMaker, lightThemeMaker } from \"./default/default\";\nimport { useThemeInternal } from \"./hook\";\nimport { CometChatTheme } from \"./type\";\n\nexport interface CometChatThemeProviderProps {\n  theme?: DeepPartial<ThemeProviderValue>;\n}\n\nfunction useDebounce<T>(value: T, delay: number): T {\n  const [debouncedValue, setDebouncedValue] = useState(value);\n\n  useEffect(() => {\n    const handler = setTimeout(() => {\n      setDebouncedValue(value);\n    }, delay);\n\n    return () => {\n      clearTimeout(handler);\n    };\n  }, [value, delay]);\n\n  return debouncedValue;\n}\n\nexport const CometChatThemeProvider = ({\n  children,\n  theme = {} as any,\n}: PropsWithChildren<CometChatThemeProviderProps>) => {\n  const rawScheme = useColorScheme();\n  // For iOS, debounce the scheme value (300ms delay in this example).\n  // For Android, use the raw value.\n  const scheme =\n    Platform.OS === \"ios\" ? useDebounce(rawScheme, 300) : rawScheme;\n\n  const parentProviderTheme = useThemeInternal();\n  const {\n    mode = parentProviderTheme.mode,\n    dark = parentProviderTheme.dark,\n    light = parentProviderTheme.light,\n  } = theme;\n\n  const lightTheme = useMemo(() => {\n    if (light) {\n      const isColorAvailable = light.color !== undefined;\n      const updatedColors = light.color\n        ? CometChatThemeHelper.updateColors(\n            light.color as Partial<CometChatTheme[\"color\"]>,\n            Brightness.LIGHT\n          )\n        : {} as any;\n      const updatedSpacing = light.spacing\n        ? CometChatThemeHelper.updateSpacing(light.spacing)\n        : {} as any;\n\n      // Create a new object cause react native will know the theme has changed\n      const updatedLight = {\n        ...light,\n        color: updatedColors,\n        spacing: updatedSpacing\n      };\n      const mergedTheme = deepMerge(parentProviderTheme.light, updatedLight);\n      const defaultLightThemeWithOverridesApplied = isColorAvailable ? lightThemeMaker(\n        mergedTheme.spacing as CometChatTheme[\"spacing\"],\n        mergedTheme.color as CometChatTheme[\"color\"],\n        mergedTheme.typography as CometChatTheme[\"typography\"]\n      ) : mergedTheme;\n      return deepMerge(defaultLightThemeWithOverridesApplied, updatedLight);\n    }\n    return parentProviderTheme.light;\n  }, [light, parentProviderTheme.light]);\n\n  const darkTheme = useMemo(() => {\n    if (dark) {\n      const isColorAvailable = dark.color !== undefined;\n      const updatedColors = dark.color\n        ? CometChatThemeHelper.updateColors(\n            dark.color as Partial<CometChatTheme[\"color\"]>,\n            Brightness.DARK\n          )\n        : {} as any;\n      const updatedSpacing = dark.spacing\n        ? CometChatThemeHelper.updateSpacing(dark.spacing)\n        : {} as any;\n      // Create a new object cause react native will know the theme has changed\n      const updatedDark = {\n        ...dark,\n        color: updatedColors,\n        spacing: updatedSpacing\n      };\n      const mergedTheme = deepMerge(parentProviderTheme.dark, updatedDark);\n      const defaultDarkThemeWithOverridesApplied = isColorAvailable ? darkThemeMaker(\n        mergedTheme.spacing as CometChatTheme[\"spacing\"],\n        mergedTheme.color as CometChatTheme[\"color\"],\n        mergedTheme.typography as CometChatTheme[\"typography\"]\n      ) : mergedTheme;\n      return deepMerge(defaultDarkThemeWithOverridesApplied, updatedDark);\n    }\n    return parentProviderTheme.dark;\n  }, [dark, parentProviderTheme.dark]);\n\n  const themeValue = {\n    light: lightTheme,\n    dark: darkTheme,\n    // Use the (conditionally) debounced scheme when determining the mode\n    mode:\n      mode === \"auto\"\n        ? (typeof scheme === \"string\" ? scheme : \"light\")\n        : mode,\n  };\n\n  return (\n    <ThemeContext.Provider value={themeValue}>\n      {children}\n    </ThemeContext.Provider>\n  );\n};\n\nexport interface CometChatCompThemeProviderProps {\n  theme?: DeepPartial<ThemeProviderValue[\"light\"]>;\n}\n\nexport const CometChatCompThemeProvider = ({\n  children,\n  theme = {} as any,\n}: PropsWithChildren<CometChatCompThemeProviderProps>) => {\n  return (\n    <CompThemeContext.Provider value={theme}>\n      {children}\n    </CompThemeContext.Provider>\n  );\n};\n"
  },
  {
    "path": "packages/ChatUiKit/src/theme/type.ts",
    "content": "import { ColorValue, ImageSourcePropType, ImageStyle, TextStyle, ViewStyle } from \"react-native\";\nimport { CallButtonStyle } from \"../calls/CometChatCallButtons\";\nimport { ConversationStyle } from \"../CometChatConversations/style\";\nimport { GroupMemberStyle } from \"../CometChatGroupMembers/style\";\nimport { GroupStyle } from \"../CometChatGroups/GroupsStyle\";\nimport { MessageComposerStyle } from \"../CometChatMessageComposer/styles\";\nimport { MessageHeaderStyle } from \"../CometChatMessageHeader/styles\";\nimport { UserStyle } from \"../CometChatUsers/style\";\nimport { AvatarStyle } from \"../shared/views/CometChatAvatar\";\nimport { BadgeStyle } from \"../shared/views/CometChatBadge\";\nimport { ConfirmDialogStyle } from \"../shared/views/CometChatConfirmDialog/style\";\nimport { DateStyle } from \"../shared/views/CometChatDate\";\nimport { DeletedBubbleStyle } from \"../shared/views/CometChatDeletedBubble\";\nimport { ReceiptStyles } from \"../shared/views/CometChatReceipt/style\";\nimport { StatusIndicatorStyles } from \"../shared/views/CometChatStatusIndicator\";\nimport { Color, Spacing, Typography } from \"./default\";\nimport { OutgoingCallStyle } from \"../calls/CometChatOutgoingCall\";\nimport { IncomingCallStyle } from \"../calls/CometChatIncomingCall/style\";\nimport { GroupCallBubbleStyles } from \"../calls/CometChatCallBubble\";\nimport { CallActionBubbleStyles } from \"../calls/CometChatCallBubble/styles\";\nimport { CallLogsItemStyle, CallLogsStyle } from \"../calls/CometChatCallLogs/style\";\nimport { DeepPartial } from \"../shared/helper/types\";\nimport { DateSeparatorStyle } from \"../shared/views/CometChatDateSeperator/styles\";\nimport { JSX } from \"react\";\nimport { ChatHistoryStyle } from \"../CometChatAIAssistantChatHistory/style\";\nimport { ReportDialogStyle } from \"../shared/views/CometChatReportDialog/style\";\n\nexport type BubbleStyles = {\n  containerStyle: ViewStyle;\n  threadedMessageStyles: DeepPartial<CometChatTheme[\"threadedMessageStyles\"]>;\n  avatarStyle: DeepPartial<CometChatTheme[\"avatarStyle\"]>;\n  receiptStyles: DeepPartial<CometChatTheme[\"receiptStyles\"]>;\n  reactionStyles: DeepPartial<CometChatTheme[\"messageBubbleReactionStyles\"]>;\n  dateStyles: DeepPartial<CometChatTheme[\"dateStyles\"]>;\n  dateReceiptContainerStyle: ViewStyle;\n  senderNameTextStyles: TextStyle;\n  textBubbleStyles?: DeepPartial<CometChatTheme[\"textBubbleStyles\"]>;\n  assistantBubbleStyles?: DeepPartial<CometChatTheme[\"assistantBubbleStyles\"]>;\n  imageBubbleStyles?: DeepPartial<CometChatTheme[\"imageBubbleStyles\"]>;\n  videoBubbleStyles?: DeepPartial<CometChatTheme[\"videoBubbleStyles\"]>;\n  audioBubbleStyles?: DeepPartial<CometChatTheme[\"audioBubbleStyles\"]>;\n  deletedBubbleStyles?: DeepPartial<CometChatTheme[\"deletedBubbleStyles\"]>;\n  fileBubbleStyles?: DeepPartial<CometChatTheme[\"fileBubbleStyles\"]>;\n  collaborativeBubbleStyles?: DeepPartial<CometChatTheme[\"collaborativeBubbleStyles\"]>;\n  meetCallBubbleStyles?: DeepPartial<CometChatTheme[\"meetCallBubbleStyles\"]>;\n  stickerBubbleStyles?: DeepPartial<CometChatTheme[\"stickerBubbleStyles\"]>;\n  pollBubbleStyles?: DeepPartial<CometChatTheme[\"pollBubbleStyles\"]>;\n  linkPreviewBubbleStyles?: DeepPartial<CometChatTheme[\"linkPreviewBubbleStyles\"]>;\n};\n\nexport type OutgoingBubbleStyles = Omit<BubbleStyles, 'assistantBubbleStyles'> & {\n  moderationStyle?: {\n    containerStyle?: ViewStyle;\n    textStyle?: TextStyle;\n    iconTintColor?: ColorValue;\n  };\n};\n\nexport type ActionSheetStyle = {\n  optionsItemStyle: {\n    containerStyle: ViewStyle;\n    iconStyle: ImageStyle;\n    iconContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n  };\n};\n\nexport type SuggestionListStyle = {\n  containerStyle: ViewStyle;\n  listItemStyle: {\n    containerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n  };\n  skeletonStyle: {\n    linearGradientColors: [string, string];\n    shimmerBackgroundColor: ColorValue;\n    shimmerOpacity: number;\n    speed: number;\n  };\n};\nexport interface CometChatTheme {\n  mode: string;\n  spacing: Spacing;\n  color: Color;\n  typography: Typography;\n  // buttonStyles: {\n  //   containerStyle: ViewStyle;\n  //   textStyle: TextStyle;\n  //   imageStyle: ImageStyle;\n  // };\n  // iconStyles: {\n  //   imageStyle: ImageStyle;\n  //   imageSource: ImageSourcePropType;\n  // };\n  // searchInputStyles: {\n  //   containerStyle: ViewStyle;\n  //   textStyle: TextStyle;\n  //   placeholderTextColor: TextInputProps[\"placeholderTextColor\"];\n  // };\n\n  messageHeaderStyles: MessageHeaderStyle;\n  callButtonStyles: CallButtonStyle;\n  deletedBubbleStyles?: DeletedBubbleStyle;\n  messageComposerStyles: DeepPartial<MessageComposerStyle>;\n  userStyles: UserStyle;\n  chatHistoryStyles: ChatHistoryStyle;\n  groupStyles: GroupStyle;\n  groupMemberStyle: GroupMemberStyle;\n  //messageOptionsStyles: ActionSheetStyle;\n  //attachmentOptionsStyles: ActionSheetStyle;\n  actionSheetStyle: ActionSheetStyle;\n  aiOptionsStyle: ActionSheetStyle;\n  mentionsListStyle: SuggestionListStyle;\n  outgoingCallStyle: OutgoingCallStyle;\n  incomingCallStyle: IncomingCallStyle;\n  callLogsItemStyle: CallLogsItemStyle;\n\n  receiptStyles: ReceiptStyles;\n  mediaRecorderStyle: {\n    animationStyle: {\n      innerAnimationContainerStyle: ViewStyle;\n      outerAnimationContainerStyle: ViewStyle;\n      animatedIconStyle?: {\n        icon?: JSX.Element | ImageSourcePropType;\n        iconStyle?: ImageStyle;\n        containerStyle?: ViewStyle;\n      };\n    };\n    recordIconStyle?: {\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      containerStyle?: ViewStyle;\n    };\n    playIconStyle?: {\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      containerStyle?: ViewStyle;\n    };\n    pauseIconStyle?: {\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      containerStyle?: ViewStyle;\n    };\n    deleteIconStyle?: {\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      containerStyle?: ViewStyle;\n    };\n    stopIconStyle?: {\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      containerStyle?: ViewStyle;\n    };\n    sendIconStyle?: {\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      containerStyle?: ViewStyle;\n    };\n  };\n  threadedMessageStyles?: {\n    containerStyle: ViewStyle;\n    indicatorTextStyle: TextStyle;\n    iconStyle: ImageStyle;\n    icon: ImageSourcePropType | JSX.Element;\n    unreadCountStyle: {\n      containerStyle: ViewStyle;\n      textStyle: TextStyle;\n    };\n  };\n  messageBubbleReactionStyles?: {\n    reactionContainerStyle: ViewStyle;\n    emojiStyle: {\n      containerStyle: ViewStyle;\n      emojitextStyle: TextStyle;\n      emojiCountTextStyle: TextStyle;\n    };\n    activeEmojiStyle: {\n      containerStyle: ViewStyle;\n      emojitextStyle: TextStyle;\n      emojiCountTextStyle: TextStyle;\n    };\n    extraReactionStyle: {\n      containerStyle: ViewStyle;\n      countTextStyle: TextStyle;\n      activeContainerStyle: ViewStyle;\n      activeCountTextStyle: TextStyle;\n    };\n  };\n  messageInformationStyles: {\n    containerStyle: ViewStyle;\n    titleContainerStyle: ViewStyle;\n    titleStyle: TextStyle;\n    messageBubbleContainerStyle: ViewStyle;\n    receiptItemStyle: {\n      containerStyle: ViewStyle;\n      titleStyle: TextStyle;\n      subtitleStyle: TextStyle;\n      avatarStyle: CometChatTheme[\"avatarStyle\"];\n      emojiStyle: TextStyle;\n      receiptStyles?: ReceiptStyles;\n    };\n    skeletonStyle: {\n      linearGradientColors: [string, string];\n      shimmerBackgroundColor: ColorValue;\n      shimmerOpacity: number;\n      speed: number;\n    };\n    errorStateStyle?: {\n      containerStyle?: ViewStyle;\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      iconContainerStyle?: ViewStyle;\n      titleStyle?: TextStyle;\n      subtitleStyle?: TextStyle;\n    };\n  };\n  reactionListStyles: {\n    tabStyle: {\n      containerStyle: ViewStyle;\n      itemStyle: ViewStyle;\n      selectedItemStyle: ViewStyle;\n      itemEmojiStyle: TextStyle;\n      selectedItemEmojiStyle: TextStyle;\n      itemTextStyle: TextStyle;\n      selectedItemTextStyle: TextStyle;\n    };\n    reactionListItemStyle: {\n      containerStyle: ViewStyle;\n      titleStyle: TextStyle;\n      subtitleStyle: TextStyle;\n      avatarStyle: CometChatTheme[\"avatarStyle\"];\n      emojiStyle: TextStyle;\n      titleContainerStyle: TextStyle;\n    };\n    skeletonStyle: {\n      linearGradientColors: [string, string];\n      shimmerBackgroundColor: ColorValue;\n      shimmerOpacity: number;\n      speed: number;\n    };\n    errorStateStyle?: {\n      containerStyle?: ViewStyle;\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      iconContainerStyle?: ViewStyle;\n      titleStyle?: TextStyle;\n      subtitleStyle?: TextStyle;\n    };\n  };\n  dateStyles: DateStyle;\n  dateSeparatorStyles: DateSeparatorStyle;\n  mentionsStyle?: {\n    textStyle: TextStyle;\n    selfTextStyle: TextStyle;\n  };\n  imageBubbleStyles?: {\n    containerStyle: ViewStyle;\n    threadedMessageStyle: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n    receiptStyles: CometChatTheme[\"receiptStyles\"];\n    reactionStyles: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles: TextStyle;\n    imageStyle: ImageStyle;\n    innerContainerStyle: ViewStyle;\n    dateReceiptContainerStyle: ViewStyle;\n    moderationStyle?: {\n      containerStyle?: ViewStyle;\n      textStyle?: TextStyle;\n      iconTintColor?: ColorValue;\n    };\n  };\n  threadHeaderStyles: {\n    containerStyle: ViewStyle;\n    messageBubbleContainerStyle: ViewStyle;\n    replyCountBarStyle: ViewStyle;\n    replyCountTextStyle: TextStyle;\n    incomingMessageBubbleStyles: DeepPartial<BubbleStyles>;\n    outgoingMessageBubbleStyles: DeepPartial<BubbleStyles>;\n  };\n  videoBubbleStyles?: {\n    containerStyle?: ViewStyle;\n    threadedMessageStyle?: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle?: CometChatTheme[\"avatarStyle\"];\n    receiptStyles?: CometChatTheme[\"receiptStyles\"];\n    reactionStyles?: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles?: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles?: TextStyle;\n    imageStyle?: ImageStyle;\n    dateReceiptContainerStyle?: ViewStyle;\n    playIcon?: JSX.Element | ImageSourcePropType;\n    playIconStyle?: ImageStyle;\n    playIconContainerStyle?: ViewStyle;\n    placeholderImage?: ImageSourcePropType;\n    moderationStyle?: {\n      containerStyle?: ViewStyle;\n      textStyle?: TextStyle;\n      iconTintColor?: ColorValue;\n    };\n  };\n  audioBubbleStyles?: {\n    threadedMessageStyle: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n    receiptStyles: CometChatTheme[\"receiptStyles\"];\n    reactionStyles: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles: TextStyle;\n    playIconStyle: ImageStyle;\n    playIconContainerStyle: ViewStyle;\n    waveStyle: ViewStyle;\n    waveContainerStyle: ViewStyle;\n    playProgressTextStyle: TextStyle;\n    containerStyle: ViewStyle;\n    playViewContainerStyle: ViewStyle;\n    dateReceiptContainerStyle: ViewStyle;\n  };\n  fileBubbleStyles?: {\n    containerStyle: ViewStyle;\n    threadedMessageStyle: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n    receiptStyles: CometChatTheme[\"receiptStyles\"];\n    reactionStyles: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles: TextStyle;\n    titleStyle: TextStyle;\n    subtitleStyle: TextStyle;\n    downloadIcon: ImageSourcePropType | JSX.Element;\n    downloadIconStyle: ImageStyle;\n    dateReceiptContainerStyle: ViewStyle;\n  };\n  collaborativeBubbleStyles?: {\n    containerStyle?: ViewStyle;\n    threadedMessageStyle?: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle?: CometChatTheme[\"avatarStyle\"];\n    receiptStyles?: CometChatTheme[\"receiptStyles\"];\n    reactionStyles?: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles?: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles?: TextStyle;\n    titleStyle?: TextStyle;\n    subtitleStyle?: TextStyle;\n    buttonViewStyle?: ViewStyle;\n    buttonTextStyle?: TextStyle;\n    dateReceiptContainerStyle?: ViewStyle;\n    imageCollaborativeDocument?: JSX.Element | ImageSourcePropType;\n    imageCollaborativeWhiteboard?: JSX.Element | ImageSourcePropType;\n    imageStyle?: ImageStyle;\n    imageContainerStyle?: ViewStyle;\n    iconCollaborativeDocument?: JSX.Element | ImageSourcePropType;\n    iconCollaborativeWhiteboard?: JSX.Element | ImageSourcePropType;\n    iconStyle?: ImageStyle;\n    iconContainerStyle?: ViewStyle;\n    dividerStyle?: ViewStyle;\n  };\n  meetCallBubbleStyles?: GroupCallBubbleStyles;\n  textBubbleStyles?: {\n    containerStyle: ViewStyle;\n    threadedMessageStyle: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n    receiptStyles: CometChatTheme[\"receiptStyles\"];\n    reactionStyles: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles: TextStyle;\n    textStyle: TextStyle;\n    textContainerStyle?: ViewStyle;\n    mentionsStyle: CometChatTheme[\"mentionsStyle\"];\n    dateReceiptContainerStyle: ViewStyle;\n    translatedTextStyle: TextStyle;\n    translatedTextContainerStyle: ViewStyle;\n    translatedTextDividerStyle: ViewStyle;\n  };\n  assistantBubbleStyles?: {\n    containerStyle: ViewStyle;\n    avatarStyle: CometChatTheme[\"avatarStyle\"];\n    textStyle: TextStyle;\n    textContainerStyle?: ViewStyle;\n    placeholderTextStyle: TextStyle;\n    copyButtonStyle: ViewStyle;\n    errorContainerStyle: ViewStyle;\n    errorTextStyle: TextStyle;\n  };\n  stickerBubbleStyles?: {\n    containerStyle?: ViewStyle;\n    threadedMessageStyle?: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle?: CometChatTheme[\"avatarStyle\"];\n    receiptStyles?: CometChatTheme[\"receiptStyles\"];\n    reactionStyles?: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles?: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles?: TextStyle;\n    dateReceiptContainerStyle?: ViewStyle;\n    imageStyle?: ImageStyle;\n  };\n  pollBubbleStyles?: {\n    containerStyle?: ViewStyle;\n    threadedMessageStyle?: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle?: CometChatTheme[\"avatarStyle\"];\n    receiptStyles?: CometChatTheme[\"receiptStyles\"];\n    reactionStyles?: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles?: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles?: TextStyle;\n    dateReceiptContainerStyle?: ViewStyle;\n    titleStyle?: TextStyle;\n    optionTextStyle?: TextStyle;\n    voteCountTextStyle?: TextStyle;\n    selectedIcon?: JSX.Element | ImageSourcePropType;\n    selectedIconStyle?: ImageStyle;\n    radioButtonStyle: ViewStyle;\n    progressBarStyle: ViewStyle;\n    activeProgressBarTint: ColorValue;\n    voteravatarStyle: CometChatTheme[\"avatarStyle\"];\n  };\n  groupActionBubbleStyles?: {\n    containerStyle: ViewStyle;\n    textStyle: TextStyle;\n    textContainerStyle?: ViewStyle;\n  };\n  linkPreviewBubbleStyles?: {\n    containerStyle: ViewStyle;\n    threadedMessageStyle?: CometChatTheme[\"threadedMessageStyles\"];\n    avatarStyle?: CometChatTheme[\"avatarStyle\"];\n    receiptStyles?: CometChatTheme[\"receiptStyles\"];\n    reactionStyles?: CometChatTheme[\"messageBubbleReactionStyles\"];\n    dateStyles?: CometChatTheme[\"dateStyles\"];\n    senderNameTextStyles?: TextStyle;\n    bodyStyle: {\n      containerStyle: ViewStyle;\n      titleStyle: TextStyle;\n      titleContainerStyle: ViewStyle;\n      subtitleTitle: TextStyle;\n      subtitleContainerStyle: TextStyle;\n      faviconStyle: ImageStyle;\n      faviconContainerStyle: ViewStyle;\n    };\n    headerImageStyle: ImageStyle;\n    headerImageContainerStyle: ViewStyle;\n  };\n  messageListStyles: {\n    containerStyle: ViewStyle;\n    scrollToBottomButtonStyle: {\n      containerStyle?: ViewStyle;\n      unreadCountBadgeStyle?: BadgeStyle;\n      icon?: JSX.Element | ImageSourcePropType;\n      iconContainerStyle?: ViewStyle;\n      iconStyle: ImageStyle;\n    };\n    emptyStateStyle?: {\n      containerStyle?: ViewStyle;\n      textStyle?: TextStyle;\n    };\n    errorStateStyle?: {\n      containerStyle?: ViewStyle;\n      icon?: JSX.Element | ImageSourcePropType;\n      iconStyle?: ImageStyle;\n      iconContainerStyle?: ViewStyle;\n      titleStyle?: TextStyle;\n      subtitleStyle?: TextStyle;\n    };\n    dateSeparatorStyle?: DateSeparatorStyle;\n    incomingMessageBubbleStyles: DeepPartial<BubbleStyles>;\n    outgoingMessageBubbleStyles: DeepPartial<OutgoingBubbleStyles>;\n    groupActionBubbleStyles: DeepPartial<CometChatTheme[\"groupActionBubbleStyles\"]>;\n    callActionBubbleStyles: DeepPartial<CallActionBubbleStyles>;\n    messageInformationStyles: DeepPartial<CometChatTheme[\"messageInformationStyles\"]>;\n    messageOptionsStyles: DeepPartial<ActionSheetStyle>;\n  };\n  avatarStyle: Partial<AvatarStyle>;\n  statusIndicatorStyle: StatusIndicatorStyles;\n  badgeStyle: BadgeStyle;\n  callLogsStyles: CallLogsStyle;\n  conversationStyles: ConversationStyle;\n  confirmDialogStyles: ConfirmDialogStyle;\n  reportDialogStyles: ReportDialogStyle;\n  quickReactionStyle: {\n    containerStyle: ViewStyle;\n    emojiContainerStyle: ViewStyle;\n  };\n}\n"
  },
  {
    "path": "packages/ChatUiKit/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"experimentalDecorators\": true,\n    \"jsx\": \"react-native\",\n    \"module\": \"ES6\",\n    \"moduleResolution\": \"node\",\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": false,\n    \"sourceMap\": true,\n    \"target\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\",\"ES6\"],\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true,\n    \"outDir\": \"lib\",\n    \"declaration\": true,\n    \"strict\": true,\n  },\n  \"include\": [\"src\"]\n}\n"
  }
]