Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .agent/rules/rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
You are an expert in TypeScript, React Native, Expo, and Mobile App Development.

Code Style and Structure:

- Write concise, type-safe TypeScript code.
- Use functional components and hooks over class components.
- Ensure components are modular, reusable, and maintainable.
- Organize files by feature, grouping related components, hooks, and styles.
- This is a mobile application, so ensure all components are mobile friendly and responsive and support both iOS and Android platforms and ensure that the app is optimized for both platforms.

Naming Conventions:

- Use camelCase for variable and function names (e.g., `isFetchingData`, `handleUserInput`).
- Use PascalCase for component names (e.g., `UserProfile`, `ChatScreen`).
- Directory and File names should be lowercase and hyphenated (e.g., `user-profile`, `chat-screen`).

TypeScript Usage:

- Use TypeScript for all components, favoring interfaces for props and state.
- Enable strict typing in `tsconfig.json`.
- Avoid using `any`; strive for precise types.
- Utilize `React.FC` for defining functional components with props.

Performance Optimization:

- Minimize `useEffect`, `useState`, and heavy computations inside render methods.
- Use `React.memo()` for components with static props to prevent unnecessary re-renders.
- Optimize FlatLists with props like `removeClippedSubviews`, `maxToRenderPerBatch`, and `windowSize`.
- Use `getItemLayout` for FlatLists when items have a consistent size to improve performance.
- Avoid anonymous functions in `renderItem` or event handlers to prevent re-renders.

UI and Styling:

- Use consistent styling leveraging `gluestack-ui`. If there isn't a Gluestack component in the `components/ui` directory for the component you are trying to use consistently style it either through `StyleSheet.create()` or Styled Components.
- Ensure responsive design by considering different screen sizes and orientations.
- Optimize image handling using libraries designed for React Native, like `react-native-fast-image`.

Best Practices:

- Follow React Native's threading model to ensure smooth UI performance.
- Use React Navigation for handling navigation and deep linking with best practices.
- Create and use Jest to test to validate all generated components
- Generate tests for all components, services and logic generated. Ensure tests run without errors and fix any issues.
- The app is multi-lingual, so ensure all text is wrapped in `t()` from `react-i18next` for translations with the dictonary files stored in `src/translations`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: "dictonary" should be "dictionary"

-- The app is multi-lingual, so ensure all text is wrapped in `t()` from `react-i18next` for translations with the dictonary files stored in `src/translations`.
+- The app is multi-lingual, so ensure all text is wrapped in `t()` from `react-i18next` for translations with the dictionary files stored in `src/translations`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- The app is multi-lingual, so ensure all text is wrapped in `t()` from `react-i18next` for translations with the dictonary files stored in `src/translations`.
- The app is multi-lingual, so ensure all text is wrapped in `t()` from `react-i18next` for translations with the dictionary files stored in `src/translations`.
🤖 Prompt for AI Agents
In @.agent/rules/rules.md at line 44, Fix the spelling mistake in the rule text:
replace the word "dictonary" with "dictionary" in the sentence that mentions
storing translation files in src/translations (the line containing "The app is
multi-lingual, so ensure all text is wrapped in `t()` from `react-i18next` for
translations with the dictonary files stored in `src/translations`.").

- Ensure support for dark mode and light mode.
- Ensure the app is accessible, following WCAG guidelines for mobile applications.
- Make sure the app is optimized for performance, especially for low-end devices.
- Handle errors gracefully and provide user feedback.
- Implement proper offline support.
- Ensure the user interface is intuitive and user-friendly and works seamlessly across different devices and screen sizes.
- This is an expo managed project that uses prebuild, do not make native code changes outside of expo prebuild capabilities.

Additional Rules:

- Use `yarn` as the package manager.
- Use Expo's secure store for sensitive data
- Implement proper offline support
- Use `zustand` for state management
- Use `react-hook-form` for form handling
- Use `react-query` for data fetching
- Use `react-i18next` for internationalization
- Use `react-native-mmkv` for local storage
- Use `axios` for API requests
- Use `@rnmapbox/maps` for maps, mapping or vehicle navigation
- Use `lucide-react-native` for icons and use those components directly in the markup and don't use the gluestack-ui icon component
- Use ? : for conditional rendering and not &&
4 changes: 2 additions & 2 deletions .github/workflows/react-native-cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ jobs:
# Create a backup
cp package.json package.json.bak
# Update the package.json
jq '.version = "7.${{ github.run_number }}.0"' package.json > package.json.tmp && mv package.json.tmp package.json
jq '.version = "7.${{ github.run_number }}"' package.json > package.json.tmp && mv package.json.tmp package.json
jq '.versionCode = "7${{ github.run_number }}"' package.json > package.json.tmp && mv package.json.tmp package.json
echo "Updated package.json versions"
cat package.json | grep "version"
Expand Down Expand Up @@ -522,7 +522,7 @@ jobs:
# Update the package.json
if [ -f ./package.json ]; then
cp package.json package.json.bak
jq '.version = "7.${{ github.run_number }}.0"' package.json > package.json.tmp && mv package.json.tmp package.json
jq '.version = "7.${{ github.run_number }}"' package.json > package.json.tmp && mv package.json.tmp package.json
echo "Updated package.json version"
cat package.json | grep "version"
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,57 @@
</Box>
)}

{/* System Audio Option */}
<Box className="mb-4">
<Text className="mb-2 text-sm text-neutral-600 dark:text-neutral-400">{t('bluetooth.audio_output')}</Text>
<Pressable
onPress={async () => {
try {
useBluetoothAudioStore.getState().setIsConnecting(true);
// We use a dummy ID for loading state tracking if needed, or just rely on global loading
setConnectingDeviceId('system-audio');

await bluetoothAudioService.connectToSystemAudio();

// Update preferred device manually here to ensure UI reflects it immediately
// preventing race conditions with store updates
await setPreferredDevice({ id: 'system-audio', name: 'System Audio' });

onClose();
} catch (error) {
logger.error({ message: 'Failed to select System Audio', context: { error } });
showMessage({
message: t('bluetooth.connection_error_title') || 'Selection Failed',
description: t('bluetooth.system_audio_error') || 'Could not switch to System Audio',
type: 'danger',
});
} finally {
useBluetoothAudioStore.getState().setIsConnecting(false);
setConnectingDeviceId(null);
}
}}
disabled={!!connectingDeviceId}
className={`rounded-lg border p-4 ${preferredDevice?.id === 'system-audio' ? 'border-primary-500 bg-primary-50 dark:bg-primary-950' : 'border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-800'} ${!!connectingDeviceId ? 'opacity-70' : ''}`}
>
<HStack className="items-center justify-between">
<HStack className="items-center">
<BluetoothIcon size={16} className="mr-2 text-primary-600" />
<VStack>
<Text className={`font-medium ${preferredDevice?.id === 'system-audio' ? 'text-primary-700 dark:text-primary-300' : 'text-neutral-900 dark:text-neutral-100'}`}>

Check warning on line 276 in src/components/settings/bluetooth-device-selection-bottom-sheet.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `⏎····················System·Audio⏎··················` with `System·Audio`
System Audio
</Text>
<Text className="text-xs text-neutral-500">AirPods, Car, Wired Headset</Text>
Comment on lines +276 to +279
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

User-facing text missing i18n wrapping.

"System Audio" and "AirPods, Car, Wired Headset" are hardcoded strings. Per coding guidelines, all user-facing text should be wrapped in t().

🌐 Proposed fix for i18n
-                  <Text className={`font-medium ${preferredDevice?.id === 'system-audio' ? 'text-primary-700 dark:text-primary-300' : 'text-neutral-900 dark:text-neutral-100'}`}>
-                    System Audio
-                  </Text>
-                  <Text className="text-xs text-neutral-500">AirPods, Car, Wired Headset</Text>
+                  <Text className={`font-medium ${preferredDevice?.id === 'system-audio' ? 'text-primary-700 dark:text-primary-300' : 'text-neutral-900 dark:text-neutral-100'}`}>
+                    {t('bluetooth.system_audio')}
+                  </Text>
+                  <Text className="text-xs text-neutral-500">{t('bluetooth.system_audio_description')}</Text>

Also addresses the static analysis hint about multiline formatting.

As per coding guidelines: "Ensure all text is wrapped in t() from react-i18next for translations with dictionary files stored in src/translations"

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Text className={`font-medium ${preferredDevice?.id === 'system-audio' ? 'text-primary-700 dark:text-primary-300' : 'text-neutral-900 dark:text-neutral-100'}`}>
System Audio
</Text>
<Text className="text-xs text-neutral-500">AirPods, Car, Wired Headset</Text>
<Text className={`font-medium ${preferredDevice?.id === 'system-audio' ? 'text-primary-700 dark:text-primary-300' : 'text-neutral-900 dark:text-neutral-100'}`}>
{t('bluetooth.system_audio')}
</Text>
<Text className="text-xs text-neutral-500">{t('bluetooth.system_audio_description')}</Text>
🧰 Tools
🪛 GitHub Check: test

[warning] 276-276:
Replace ⏎····················System·Audio⏎·················· with System·Audio

🤖 Prompt for AI Agents
In `@src/components/settings/bluetooth-device-selection-bottom-sheet.tsx` around
lines 276 - 279, In the BluetoothDeviceSelectionBottomSheet component, replace
the hardcoded user-facing strings in the two <Text> elements ("System Audio" and
"AirPods, Car, Wired Headset") with calls to the i18n translator (t()) from
react-i18next (ensure t is imported/available in the component), e.g.
t('settings.systemAudio') and t('settings.systemAudioExamples'), and
update/create corresponding keys in the translations files; also ensure the JSX
formatting uses single-line expressions or proper multilines to satisfy the
static analyzer (keep each <Text> content as a single JS expression calling
t()).

</VStack>
</HStack>
{preferredDevice?.id === 'system-audio' && (
<VStack className="items-end">
<Text className="text-sm font-medium text-primary-600 dark:text-primary-400">{t('bluetooth.selected')}</Text>
</VStack>
)}
</HStack>
</Pressable>
</Box>

{/* Scan Button */}
<HStack className="mb-4 w-full items-center justify-between">
<Text className="text-sm text-neutral-600 dark:text-neutral-400">{t('bluetooth.available_devices')}</Text>
Expand Down
36 changes: 36 additions & 0 deletions src/services/__tests__/bluetooth-audio.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ jest.mock('@/services/audio.service', () => ({
},
}));

jest.mock('@/stores/app/livekit-store', () => {
const actions = {
toggleMicrophone: jest.fn(),
setMicrophoneEnabled: jest.fn(),
};
return {
useLiveKitStore: {
getState: jest.fn(() => actions),
},
};
});

import { bluetoothAudioService } from '../bluetooth-audio.service';

describe('BluetoothAudioService Refactoring', () => {
Expand Down Expand Up @@ -159,4 +171,28 @@ describe('BluetoothAudioService Refactoring', () => {
expect(service.hasAttemptedPreferredDeviceConnection).toBe(false);
});
});
describe('Microphone Control Delegation', () => {
it('should delegate mute toggle to livekitStore', async () => {
const service = bluetoothAudioService as any;

// Call private method via casting to any
await service.handleMuteToggle();

const storeMock = require('@/stores/app/livekit-store').useLiveKitStore.getState();
expect(storeMock.toggleMicrophone).toHaveBeenCalled();
});

it('should delegate setMicrophoneEnabled to livekitStore', async () => {
const service = bluetoothAudioService as any;

// Call private method via casting to any
await service.setMicrophoneEnabled(true);

const storeMock = require('@/stores/app/livekit-store').useLiveKitStore.getState();
expect(storeMock.setMicrophoneEnabled).toHaveBeenCalledWith(true);

await service.setMicrophoneEnabled(false);
expect(storeMock.setMicrophoneEnabled).toHaveBeenCalledWith(false);
});
});
});
Loading
Loading